]> Cypherpunks repositories - gostls13.git/commitdiff
gofix: add time+fileinfo fix
authorRuss Cox <rsc@golang.org>
Thu, 1 Dec 2011 18:59:57 +0000 (13:59 -0500)
committerRuss Cox <rsc@golang.org>
Thu, 1 Dec 2011 18:59:57 +0000 (13:59 -0500)
R=adg, rogpeppe, r, cw
CC=golang-dev
https://golang.org/cl/5450050

src/cmd/gofix/Makefile
src/cmd/gofix/timefileinfo.go [new file with mode: 0644]
src/cmd/gofix/timefileinfo_test.go [new file with mode: 0644]

index dc42b3055d2d54bd37cf13d5c1fd6a0ee3702860..e6b9503faf91b2d762e6252f97cbf7734bda694c 100644 (file)
@@ -33,6 +33,7 @@ GOFILES=\
        sortslice.go\
        stringssplit.go\
        template.go\
+       timefileinfo.go\
        typecheck.go\
        url.go\
 
diff --git a/src/cmd/gofix/timefileinfo.go b/src/cmd/gofix/timefileinfo.go
new file mode 100644 (file)
index 0000000..9a037d7
--- /dev/null
@@ -0,0 +1,298 @@
+// Copyright 2011 The Go Authors.  All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package main
+
+import (
+       "go/ast"
+       "go/token"
+       "strings"
+)
+
+func init() {
+       register(timefileinfoFix)
+}
+
+var timefileinfoFix = fix{
+       "time+fileinfo",
+       "2011-11-29",
+       timefileinfo,
+       `Rewrite for new time and os.FileInfo APIs.
+
+This fix applies some of the more mechanical changes,
+but most code will still need manual cleanup.
+
+http://codereview.appspot.com/5392041
+http://codereview.appspot.com/5416060
+`,
+}
+
+var timefileinfoTypeConfig = &TypeConfig{
+       Type: map[string]*Type{
+               "os.File": &Type{
+                       Method: map[string]string{
+                               "Readdir": "func() []*os.FileInfo",
+                               "Stat":    "func() (*os.FileInfo, error)",
+                       },
+               },
+               "time.Time": &Type{
+                       Method: map[string]string{
+                               "Seconds":     "time.raw",
+                               "Nanoseconds": "time.raw",
+                       },
+               },
+       },
+       Func: map[string]string{
+               "ioutil.ReadDir":              "([]*os.FileInfo, error)",
+               "os.Stat":                     "(*os.FileInfo, error)",
+               "os.Lstat":                    "(*os.FileInfo, error)",
+               "time.LocalTime":              "*time.Time",
+               "time.UTC":                    "*time.Time",
+               "time.SecondsToLocalTime":     "*time.Time",
+               "time.SecondsToUTC":           "*time.Time",
+               "time.NanosecondsToLocalTime": "*time.Time",
+               "time.NanosecondsToUTC":       "*time.Time",
+               "time.Parse":                  "(*time.Time, error)",
+               "time.Nanoseconds":            "time.raw",
+               "time.Seconds":                "time.raw",
+       },
+}
+
+// timefileinfoIsOld reports whether f has evidence of being
+// "old code", from before the API changes.  Evidence means:
+//
+//     a mention of *os.FileInfo (the pointer)
+//     a mention of *time.Time (the pointer)
+//     a mention of old functions from package time
+//     an attempt to call time.UTC
+//
+func timefileinfoIsOld(f *ast.File, typeof map[interface{}]string) bool {
+       old := false
+       
+       // called records the expressions that appear as
+       // the function part of a function call, so that
+       // we can distinguish a ref to the possibly new time.UTC
+       // from the definitely old time.UTC() function call.
+       called := make(map[interface{}]bool)
+
+       before := func(n interface{}) {
+               if old {
+                       return
+               }
+               if star, ok := n.(*ast.StarExpr); ok {
+                       if isPkgDot(star.X, "os", "FileInfo") || isPkgDot(star.X, "time", "Time") {
+                               old = true
+                               return
+                       }
+               }
+               if sel, ok := n.(*ast.SelectorExpr); ok {
+                       if isTopName(sel.X, "time") {
+                               if timefileinfoOldTimeFunc[sel.Sel.Name] {
+                                       old = true
+                                       return
+                               }
+                       }
+                       if typeof[sel.X] == "os.FileInfo" || typeof[sel.X] == "*os.FileInfo" {
+                               switch sel.Sel.Name {
+                               case "Mtime_ns", "IsDirectory", "IsRegular":
+                                       old = true
+                                       return
+                               case "Name", "Mode", "Size":
+                                       if !called[sel] {
+                                               old = true
+                                               return
+                                       }
+                               }
+                       }
+               }
+               call, ok := n.(*ast.CallExpr)
+               if ok && isPkgDot(call.Fun, "time", "UTC") {
+                       old = true
+                       return
+               }
+               if ok {
+                       called[call.Fun] = true
+               }
+       }
+       walkBeforeAfter(f, before, nop)
+       return old
+}
+
+var timefileinfoOldTimeFunc = map[string]bool{
+       "LocalTime":              true,
+       "SecondsToLocalTime":     true,
+       "SecondsToUTC":           true,
+       "NanosecondsToLocalTime": true,
+       "NanosecondsToUTC":       true,
+       "Seconds":                true,
+       "Nanoseconds":            true,
+}
+
+var isTimeNow = map[string]bool{
+       "LocalTime":   true,
+       "UTC":         true,
+       "Seconds":     true,
+       "Nanoseconds": true,
+}
+
+func timefileinfo(f *ast.File) bool {
+       if !imports(f, "os") && !imports(f, "time") && !imports(f, "io/ioutil") {
+               return false
+       }
+
+       typeof, _ := typecheck(timefileinfoTypeConfig, f)
+
+       if !timefileinfoIsOld(f, typeof) {
+               return false
+       }
+
+       fixed := false
+       walk(f, func(n interface{}) {
+               p, ok := n.(*ast.Expr)
+               if !ok {
+                       return
+               }
+               nn := *p
+
+               // Rewrite *os.FileInfo and *time.Time to drop the pointer.
+               if star, ok := nn.(*ast.StarExpr); ok {
+                       if isPkgDot(star.X, "os", "FileInfo") || isPkgDot(star.X, "time", "Time") {
+                               fixed = true
+                               *p = star.X
+                               return
+                       }
+               }
+
+               // Rewrite old time API calls to new calls.
+               // The code will still not compile after this edit,
+               // but the compiler will catch that, and the replacement
+               // code will be the correct functions to use in the new API.
+               if sel, ok := nn.(*ast.SelectorExpr); ok && isTopName(sel.X, "time") {
+                       fn := sel.Sel.Name
+                       if fn == "LocalTime" || fn == "Seconds" || fn == "Nanoseconds" {
+                               fixed = true
+                               sel.Sel.Name = "Now"
+                               return
+                       }
+               }
+
+               if call, ok := nn.(*ast.CallExpr); ok {
+                       if sel, ok := call.Fun.(*ast.SelectorExpr); ok {
+                               // Rewrite time.UTC but only when called (there's a new time.UTC var now).
+                               if isPkgDot(sel, "time", "UTC") {
+                                       fixed = true
+                                       sel.Sel.Name = "Now"
+                                       // rewrite time.Now() into time.Now().UTC()
+                                       *p = &ast.CallExpr{
+                                               Fun: &ast.SelectorExpr{
+                                                       X:   call,
+                                                       Sel: ast.NewIdent("UTC"),
+                                               },
+                                       }
+                                       return
+                               }
+
+                               // Rewrite conversions.
+                               if ok && isTopName(sel.X, "time") && len(call.Args) == 1 {
+                                       fn := sel.Sel.Name
+                                       switch fn {
+                                       case "SecondsToLocalTime", "SecondsToUTC",
+                                               "NanosecondsToLocalTime", "NanosecondsToUTC":
+                                               fixed = true
+                                               sel.Sel.Name = "Unix"
+                                               call.Args = append(call.Args, nil)
+                                               if strings.HasPrefix(fn, "Seconds") {
+                                                       // Unix(sec, 0)
+                                                       call.Args[1] = ast.NewIdent("0")
+                                               } else {
+                                                       // Unix(0, nsec)
+                                                       call.Args[1] = call.Args[0]
+                                                       call.Args[0] = ast.NewIdent("0")
+                                               }
+                                               if strings.HasSuffix(fn, "ToUTC") {
+                                                       // rewrite call into call.UTC()
+                                                       *p = &ast.CallExpr{
+                                                               Fun: &ast.SelectorExpr{
+                                                                       X:   call,
+                                                                       Sel: ast.NewIdent("UTC"),
+                                                               },
+                                                       }
+                                               }
+                                               return
+                                       }
+                               }
+
+                               // Rewrite method calls.
+                               switch typeof[sel.X] {
+                               case "*time.Time", "time.Time":
+                                       switch sel.Sel.Name {
+                                       case "Seconds":
+                                               fixed = true
+                                               sel.Sel.Name = "Unix"
+                                               return
+                                       case "Nanoseconds":
+                                               fixed = true
+                                               sel.Sel.Name = "UnixNano"
+                                               return
+                                       }
+
+                               case "*os.FileInfo", "os.FileInfo":
+                                       switch sel.Sel.Name {
+                                       case "IsDirectory":
+                                               fixed = true
+                                               sel.Sel.Name = "IsDir"
+                                               return
+                                       case "IsRegular":
+                                               fixed = true
+                                               sel.Sel.Name = "IsDir"
+                                               *p = &ast.UnaryExpr{
+                                                       Op: token.NOT,
+                                                       X:  call,
+                                               }
+                                               return
+                                       }
+                               }
+                       }
+               }
+
+               // Rewrite subtraction of two times.
+               // Cannot handle +=/-=.
+               if bin, ok := nn.(*ast.BinaryExpr); ok &&
+                       bin.Op == token.SUB &&
+                       (typeof[bin.X] == "time.raw" || typeof[bin.Y] == "time.raw") {
+                       fixed = true
+                       *p = &ast.CallExpr{
+                               Fun: &ast.SelectorExpr{
+                                       X:   bin.X,
+                                       Sel: ast.NewIdent("Sub"),
+                               },
+                               Args: []ast.Expr{bin.Y},
+                       }
+               }
+
+               // Rewrite field references for os.FileInfo.
+               if sel, ok := nn.(*ast.SelectorExpr); ok {
+                       if typ := typeof[sel.X]; typ == "*os.FileInfo" || typ == "os.FileInfo" {
+                               addCall := false
+                               switch sel.Sel.Name {
+                               case "Name", "Size", "Mode":
+                                       fixed = true
+                                       addCall = true
+                               case "Mtime_ns":
+                                       fixed = true
+                                       sel.Sel.Name = "ModTime"
+                                       addCall = true
+                               }
+                               if addCall {
+                                       *p = &ast.CallExpr{
+                                               Fun: sel,
+                                       }
+                                       return
+                               }
+                       }
+               }
+       })
+
+       return true
+}
diff --git a/src/cmd/gofix/timefileinfo_test.go b/src/cmd/gofix/timefileinfo_test.go
new file mode 100644 (file)
index 0000000..76d5c1f
--- /dev/null
@@ -0,0 +1,161 @@
+// Copyright 2011 The Go Authors.  All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package main
+
+func init() {
+       addTestCases(timefileinfoTests, timefileinfo)
+}
+
+var timefileinfoTests = []testCase{
+       {
+               Name: "timefileinfo.0",
+               In: `package main
+
+import "os"
+
+func main() {
+       st, _ := os.Stat("/etc/passwd")
+       _ = st.Name
+}
+`,
+               Out: `package main
+
+import "os"
+
+func main() {
+       st, _ := os.Stat("/etc/passwd")
+       _ = st.Name()
+}
+`,
+       },
+       {
+               Name: "timefileinfo.1",
+               In: `package main
+
+import "os"
+
+func main() {
+       st, _ := os.Stat("/etc/passwd")
+       _ = st.Size
+       _ = st.Mode
+       _ = st.Mtime_ns
+       _ = st.IsDirectory()
+       _ = st.IsRegular()
+}
+`,
+               Out: `package main
+
+import "os"
+
+func main() {
+       st, _ := os.Stat("/etc/passwd")
+       _ = st.Size()
+       _ = st.Mode()
+       _ = st.ModTime()
+       _ = st.IsDir()
+       _ = !st.IsDir()
+}
+`,
+       },
+       {
+               Name: "timefileinfo.2",
+               In: `package main
+
+import "os"
+
+func f(st *os.FileInfo) {
+       _ = st.Name
+       _ = st.Size
+       _ = st.Mode
+       _ = st.Mtime_ns
+       _ = st.IsDirectory()
+       _ = st.IsRegular()
+}
+`,
+               Out: `package main
+
+import "os"
+
+func f(st os.FileInfo) {
+       _ = st.Name()
+       _ = st.Size()
+       _ = st.Mode()
+       _ = st.ModTime()
+       _ = st.IsDir()
+       _ = !st.IsDir()
+}
+`,
+       },
+       {
+               Name: "timefileinfo.3",
+               In: `package main
+
+import "time"
+
+func main() {
+       _ = time.Seconds()
+       _ = time.Nanoseconds()
+       _ = time.LocalTime()
+       _ = time.UTC()
+       _ = time.SecondsToLocalTime(sec)
+       _ = time.SecondsToUTC(sec)
+       _ = time.NanosecondsToLocalTime(nsec)
+       _ = time.NanosecondsToUTC(nsec)
+}
+`,
+               Out: `package main
+
+import "time"
+
+func main() {
+       _ = time.Now()
+       _ = time.Now()
+       _ = time.Now()
+       _ = time.Now().UTC()
+       _ = time.Unix(sec, 0)
+       _ = time.Unix(sec, 0).UTC()
+       _ = time.Unix(0, nsec)
+       _ = time.Unix(0, nsec).UTC()
+}
+`,
+       },
+       {
+               Name: "timefileinfo.4",
+               In: `package main
+
+import "time"
+
+func f(*time.Time)
+
+func main() {
+       t := time.LocalTime()
+       _ = t.Seconds()
+       _ = t.Nanoseconds()
+
+       t1 := time.Nanoseconds()
+       f(nil)
+       t2 := time.Nanoseconds()
+       dt := t2 - t1
+}
+`,
+               Out: `package main
+
+import "time"
+
+func f(time.Time)
+
+func main() {
+       t := time.Now()
+       _ = t.Unix()
+       _ = t.UnixNano()
+
+       t1 := time.Now()
+       f(nil)
+       t2 := time.Now()
+       dt := t2.Sub(t1)
+}
+`,
+       },
+}