diff options
Diffstat (limited to 'src/cmd/fix/error.go')
-rw-r--r-- | src/cmd/fix/error.go | 353 |
1 files changed, 353 insertions, 0 deletions
diff --git a/src/cmd/fix/error.go b/src/cmd/fix/error.go new file mode 100644 index 000000000..55613210a --- /dev/null +++ b/src/cmd/fix/error.go @@ -0,0 +1,353 @@ +// 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" + "regexp" + "strings" +) + +func init() { + register(errorFix) +} + +var errorFix = fix{ + "error", + "2011-11-02", + errorFn, + `Use error instead of os.Error. + +This fix rewrites code using os.Error to use error: + + os.Error -> error + os.NewError -> errors.New + os.EOF -> io.EOF + +Seeing the old names above (os.Error and so on) triggers the following +heuristic rewrites. The heuristics can be forced using the -force=error flag. + +A top-level function, variable, or constant named error is renamed error_. + +Error implementations—those types used as os.Error or named +XxxError—have their String methods renamed to Error. Any existing +Error field or method is renamed to Err. + +Error values—those with type os.Error or named e, err, error, err1, +and so on—have method calls and field references rewritten just +as the types do (String to Error, Error to Err). Also, a type assertion +of the form err.(*os.Waitmsg) becomes err.(*exec.ExitError). + +http://codereview.appspot.com/5305066 +`, +} + +// At minimum, this fix applies the following rewrites: +// +// os.Error -> error +// os.NewError -> errors.New +// os.EOF -> io.EOF +// +// However, if can apply any of those rewrites, it assumes that the +// file predates the error type and tries to update the code to use +// the new definition for error - an Error method, not a String method. +// This more heuristic procedure may not be 100% accurate, so it is +// only run when the file needs updating anyway. The heuristic can +// be forced to run using -force=error. +// +// First, we must identify the implementations of os.Error. +// These include the type of any value returned as or assigned to an os.Error. +// To that set we add any type whose name contains "Error" or "error". +// The heuristic helps for implementations that are not used as os.Error +// in the file in which they are defined. +// +// In any implementation of os.Error, we rename an existing struct field +// or method named Error to Err and rename the String method to Error. +// +// Second, we must identify the values of type os.Error. +// These include any value that obviously has type os.Error. +// To that set we add any variable whose name is e or err or error +// possibly followed by _ or a numeric or capitalized suffix. +// The heuristic helps for variables that are initialized using calls +// to functions in other packages. The type checker does not have +// information about those packages available, and in general cannot +// (because the packages may themselves not compile). +// +// For any value of type os.Error, we replace a call to String with a call to Error. +// We also replace type assertion err.(*os.Waitmsg) with err.(*exec.ExitError). + +// Variables matching this regexp are assumed to have type os.Error. +var errVar = regexp.MustCompile(`^(e|err|error)_?([A-Z0-9].*)?$`) + +// Types matching this regexp are assumed to be implementations of os.Error. +var errType = regexp.MustCompile(`^\*?([Ee]rror|.*Error)$`) + +// Type-checking configuration: tell the type-checker this basic +// information about types, functions, and variables in external packages. +var errorTypeConfig = &TypeConfig{ + Type: map[string]*Type{ + "os.Error": {}, + }, + Func: map[string]string{ + "fmt.Errorf": "os.Error", + "os.NewError": "os.Error", + }, + Var: map[string]string{ + "os.EPERM": "os.Error", + "os.ENOENT": "os.Error", + "os.ESRCH": "os.Error", + "os.EINTR": "os.Error", + "os.EIO": "os.Error", + "os.ENXIO": "os.Error", + "os.E2BIG": "os.Error", + "os.ENOEXEC": "os.Error", + "os.EBADF": "os.Error", + "os.ECHILD": "os.Error", + "os.EDEADLK": "os.Error", + "os.ENOMEM": "os.Error", + "os.EACCES": "os.Error", + "os.EFAULT": "os.Error", + "os.EBUSY": "os.Error", + "os.EEXIST": "os.Error", + "os.EXDEV": "os.Error", + "os.ENODEV": "os.Error", + "os.ENOTDIR": "os.Error", + "os.EISDIR": "os.Error", + "os.EINVAL": "os.Error", + "os.ENFILE": "os.Error", + "os.EMFILE": "os.Error", + "os.ENOTTY": "os.Error", + "os.EFBIG": "os.Error", + "os.ENOSPC": "os.Error", + "os.ESPIPE": "os.Error", + "os.EROFS": "os.Error", + "os.EMLINK": "os.Error", + "os.EPIPE": "os.Error", + "os.EAGAIN": "os.Error", + "os.EDOM": "os.Error", + "os.ERANGE": "os.Error", + "os.EADDRINUSE": "os.Error", + "os.ECONNREFUSED": "os.Error", + "os.ENAMETOOLONG": "os.Error", + "os.EAFNOSUPPORT": "os.Error", + "os.ETIMEDOUT": "os.Error", + "os.ENOTCONN": "os.Error", + }, +} + +func errorFn(f *ast.File) bool { + if !imports(f, "os") && !force["error"] { + return false + } + + // Fix gets called once to run the heuristics described above + // when we notice that this file definitely needs fixing + // (it mentions os.Error or something similar). + var fixed bool + var didHeuristic bool + heuristic := func() { + if didHeuristic { + return + } + didHeuristic = true + + // We have identified a necessary fix (like os.Error -> error) + // but have not applied it or any others yet. Prepare the file + // for fixing and apply heuristic fixes. + + // Rename error to error_ to make room for error. + fixed = renameTop(f, "error", "error_") || fixed + + // Use type checker to build list of error implementations. + typeof, assign := typecheck(errorTypeConfig, f) + + isError := map[string]bool{} + for _, val := range assign["os.Error"] { + t := typeof[val] + if strings.HasPrefix(t, "*") { + t = t[1:] + } + if t != "" && !strings.HasPrefix(t, "func(") { + isError[t] = true + } + } + + // We use both the type check results and the "Error" name heuristic + // to identify implementations of os.Error. + isErrorImpl := func(typ string) bool { + return isError[typ] || errType.MatchString(typ) + } + + isErrorVar := func(x ast.Expr) bool { + if typ := typeof[x]; typ != "" { + return isErrorImpl(typ) || typ == "os.Error" + } + if sel, ok := x.(*ast.SelectorExpr); ok { + return sel.Sel.Name == "Error" || sel.Sel.Name == "Err" + } + if id, ok := x.(*ast.Ident); ok { + return errVar.MatchString(id.Name) + } + return false + } + + walk(f, func(n interface{}) { + // In method declaration on error implementation type, + // rename String() to Error() and Error() to Err(). + fn, ok := n.(*ast.FuncDecl) + if ok && + fn.Recv != nil && + len(fn.Recv.List) == 1 && + isErrorImpl(typeName(fn.Recv.List[0].Type)) { + // Rename. + switch fn.Name.Name { + case "String": + fn.Name.Name = "Error" + fixed = true + case "Error": + fn.Name.Name = "Err" + fixed = true + } + return + } + + // In type definition of an error implementation type, + // rename Error field to Err to make room for method. + // Given type XxxError struct { ... Error T } rename field to Err. + d, ok := n.(*ast.GenDecl) + if ok { + for _, s := range d.Specs { + switch s := s.(type) { + case *ast.TypeSpec: + if isErrorImpl(typeName(s.Name)) { + st, ok := s.Type.(*ast.StructType) + if ok { + for _, f := range st.Fields.List { + for _, n := range f.Names { + if n.Name == "Error" { + n.Name = "Err" + fixed = true + } + } + } + } + } + } + } + } + + // For values that are an error implementation type, + // rename .Error to .Err and .String to .Error + sel, selok := n.(*ast.SelectorExpr) + if selok && isErrorImpl(typeof[sel.X]) { + switch sel.Sel.Name { + case "Error": + sel.Sel.Name = "Err" + fixed = true + case "String": + sel.Sel.Name = "Error" + fixed = true + } + } + + // Assume x.Err is an error value and rename .String to .Error + // Children have been processed so the rewrite from Error to Err + // has already happened there. + if selok { + if subsel, ok := sel.X.(*ast.SelectorExpr); ok && subsel.Sel.Name == "Err" && sel.Sel.Name == "String" { + sel.Sel.Name = "Error" + fixed = true + } + } + + // For values that are an error variable, rename .String to .Error. + if selok && isErrorVar(sel.X) && sel.Sel.Name == "String" { + sel.Sel.Name = "Error" + fixed = true + } + + // Rewrite composite literal of error type to turn Error: into Err:. + lit, ok := n.(*ast.CompositeLit) + if ok && isErrorImpl(typeof[lit]) { + for _, e := range lit.Elts { + if kv, ok := e.(*ast.KeyValueExpr); ok && isName(kv.Key, "Error") { + kv.Key.(*ast.Ident).Name = "Err" + fixed = true + } + } + } + + // Rename os.Waitmsg to exec.ExitError + // when used in a type assertion on an error. + ta, ok := n.(*ast.TypeAssertExpr) + if ok && isErrorVar(ta.X) && isPtrPkgDot(ta.Type, "os", "Waitmsg") { + addImport(f, "exec") + sel := ta.Type.(*ast.StarExpr).X.(*ast.SelectorExpr) + sel.X.(*ast.Ident).Name = "exec" + sel.Sel.Name = "ExitError" + fixed = true + } + + }) + } + + fix := func() { + if fixed { + return + } + fixed = true + heuristic() + } + + if force["error"] { + heuristic() + } + + walk(f, func(n interface{}) { + p, ok := n.(*ast.Expr) + if !ok { + return + } + sel, ok := (*p).(*ast.SelectorExpr) + if !ok { + return + } + switch { + case isPkgDot(sel, "os", "Error"): + fix() + *p = &ast.Ident{NamePos: sel.Pos(), Name: "error"} + case isPkgDot(sel, "os", "NewError"): + fix() + addImport(f, "errors") + sel.X.(*ast.Ident).Name = "errors" + sel.Sel.Name = "New" + case isPkgDot(sel, "os", "EOF"): + fix() + addImport(f, "io") + sel.X.(*ast.Ident).Name = "io" + } + }) + + if fixed && !usesImport(f, "os") { + deleteImport(f, "os") + } + + return fixed +} + +func typeName(typ ast.Expr) string { + if p, ok := typ.(*ast.StarExpr); ok { + typ = p.X + } + id, ok := typ.(*ast.Ident) + if ok { + return id.Name + } + sel, ok := typ.(*ast.SelectorExpr) + if ok { + return typeName(sel.X) + "." + sel.Sel.Name + } + return "" +} |