// checkSelection checks that all uses and selections that resolve to // the specified object would continue to do so after the renaming. func (r *Unexporter) checkSelections(objsToUpdate map[types.Object]string, from types.Object, to string) { for pkg, info := range r.packages { if id := someUse(info, from); id != nil { if !r.checkExport(id, pkg, from, to) { return } } for syntax, sel := range info.Selections { // There may be extant selections of only the old // name or only the new name, so we must check both. // (If neither, the renaming is sound.) // // In both cases, we wish to compare the lengths // of the implicit field path (Selection.Index) // to see if the renaming would change it. // // If a selection that resolves to 'from', when renamed, // would yield a path of the same or shorter length, // this indicates ambiguity or a changed referent, // analogous to same- or sub-block lexical conflict. // // If a selection using the name 'to' would // yield a path of the same or shorter length, // this indicates ambiguity or shadowing, // analogous to same- or super-block lexical conflict. // TODO(adonovan): fix: derive from Types[syntax.X].Mode // TODO(adonovan): test with pointer, value, addressable value. isAddressable := true if sel.Obj() == from { if obj, indices, _ := types.LookupFieldOrMethod(sel.Recv(), isAddressable, from.Pkg(), to); obj != nil { // Renaming this existing selection of // 'from' may block access to an existing // type member named 'to'. delta := len(indices) - len(sel.Index()) if delta > 0 { continue // no ambiguity } r.selectionConflict(objsToUpdate, from, to, delta, syntax, obj) return } } else if sel.Obj().Name() == to { if obj, indices, _ := types.LookupFieldOrMethod(sel.Recv(), isAddressable, from.Pkg(), from.Name()); obj == from { // Renaming 'from' may cause this existing // selection of the name 'to' to change // its meaning. delta := len(indices) - len(sel.Index()) if delta > 0 { continue // no ambiguity } r.selectionConflict(objsToUpdate, from, to, -delta, syntax, sel.Obj()) return } } } } }
func (u *Unexporter) isInterfaceMethod(e Export) map[string][]*types.Package { if _, ok := e.obj.(*types.Func); !ok { return nil } f := e.obj.(*types.Func) r := f.Type().(*types.Signature).Recv() if r == nil { return nil } if u.f == nil { calculateConstraints(u) } interfaces := make(map[string][]*types.Package) for constraint := range u.f { if constraint.RHS == r.Type() && types.IsInterface(constraint.LHS) { sure, _, _ := types.LookupFieldOrMethod(constraint.LHS, true, f.Pkg(), f.Name()) if sure != nil { interfaces[constraint.LHS.String()] = append(interfaces[constraint.LHS.String()], f.Pkg()) } } } return interfaces }
func (g *Grapher) buildStructFields(pkgInfo *loader.PackageInfo) { for _, obj := range pkgInfo.Defs { if tn, ok := obj.(*types.TypeName); ok { typ := tn.Type().Underlying() if st, ok := typ.(*types.Struct); ok { for i := 0; i < st.NumFields(); i++ { sf := st.Field(i) g.structFields[sf] = &structField{sf, tn.Type()} } } } } for selExpr, sel := range pkgInfo.Selections { switch sel.Kind() { case types.FieldVal: rt := derefType(sel.Recv()) var pkg *types.Package switch rt := rt.(type) { case *types.Named: pkg = rt.Obj().Pkg() case *types.Struct: pkg = sel.Obj().Pkg() default: panic("unhandled field recv type " + rt.String()) } sfobj, _, _ := types.LookupFieldOrMethod(derefType(sel.Recv()), false, pkg, selExpr.Sel.Name) // Record that this field is in this struct so we can construct the // right def path to the field. sf, _ := sfobj.(*types.Var) g.structFields[sf] = &structField{sf, rt} } } }
func main() { flag.Parse() exitStatus := 0 importPaths := gotool.ImportPaths(flag.Args()) if len(importPaths) == 0 { importPaths = []string{"."} } for _, pkgPath := range importPaths { visitor := &visitor{ info: types.Info{ Types: make(map[ast.Expr]types.TypeAndValue), Defs: make(map[*ast.Ident]types.Object), Selections: make(map[*ast.SelectorExpr]*types.Selection), }, m: make(map[types.Type]map[string]int), skip: make(map[types.Type]struct{}), } fset, astFiles := check.ASTFilesForPackage(pkgPath, *loadTestFiles) imp := importer.New() // Preliminary cgo support. imp.Config = importer.Config{UseGcFallback: true} config := types.Config{Import: imp.Import} var err error visitor.pkg, err = config.Check(pkgPath, fset, astFiles, &visitor.info) if err != nil { fmt.Fprintf(os.Stderr, "%s: %v\n", pkgPath, err) continue } for _, f := range astFiles { ast.Walk(visitor, f) } for t := range visitor.m { if _, skip := visitor.skip[t]; skip { continue } for fieldName, v := range visitor.m[t] { if !*reportExported && ast.IsExported(fieldName) { continue } if v == 0 { field, _, _ := types.LookupFieldOrMethod(t, false, visitor.pkg, fieldName) if fieldName == "XMLName" { if named, ok := field.Type().(*types.Named); ok && named.Obj().Pkg().Path() == "encoding/xml" { continue } } pos := fset.Position(field.Pos()) fmt.Printf("%s: %s:%d:%d: %s.%s\n", pkgPath, pos.Filename, pos.Line, pos.Column, types.TypeString(t, nil), fieldName, ) exitStatus = 1 } } } } os.Exit(exitStatus) }
func ext۰sync۰Pool۰Get(fr *frame, args []value) value { Pool := fr.i.prog.ImportedPackage("sync").Type("Pool").Object() _, newIndex, _ := types.LookupFieldOrMethod(Pool.Type(), false, Pool.Pkg(), "New") if New := (*args[0].(*value)).(structure)[newIndex[0]]; New != nil { return call(fr.i, fr, 0, New, nil) } return nil }
func (tr *Transformer) matchSelectorExpr(x, y *ast.SelectorExpr) bool { if xobj, ok := tr.wildcardObj(x.X); ok { field := x.Sel.Name yt := tr.info.TypeOf(y.X) o, _, _ := types.LookupFieldOrMethod(yt, true, tr.currentPkg, field) if o != nil { tr.env[xobj.Name()] = y.X // record binding return true } } return tr.matchExpr(x.X, y.X) }
// checkSelection checks that all uses and selections that resolve to // the specified object would continue to do so after the renaming. func (e *Export) checkSelections(from types.Object, to string) { info := e.u.pkgInfo if id := someUse(info, from); id != nil { e.Conflicting = true return } for _, sel := range info.Selections { if sel.Obj() == from { if obj, indices, _ := types.LookupFieldOrMethod(sel.Recv(), true, from.Pkg(), to); obj != nil { // Renaming this existing selection of // 'from' may block access to an existing // type member named 'to'. delta := len(indices) - len(sel.Index()) if delta > 0 { continue // no ambiguity } e.Conflicting = true return } } else if sel.Obj().Name() == to { if obj, indices, _ := types.LookupFieldOrMethod(sel.Recv(), true, from.Pkg(), from.Name()); obj == from { // Renaming 'from' may cause this existing // selection of the name 'to' to change // its meaning. delta := len(indices) - len(sel.Index()) if delta > 0 { continue // no ambiguity } e.Conflicting = true return } } } }
// checkStructField checks that the field renaming will not cause // conflicts at its declaration, or ambiguity or changes to any selection. func (e *Export) checkStructField(from *types.Var, to string) { // Check that the struct declaration is free of field conflicts, // and field/method conflicts. t := getEnclosingStruct(from) if t != t.Underlying() { // This struct is also a named type. // We must check for direct (non-promoted) field/field // and method/field conflicts. _, indices, _ := types.LookupFieldOrMethod(t, true, e.u.pkgInfo.Pkg, to) if len(indices) == 1 { e.Conflicting = true return } } else { // This struct is not a named type. // We need only check for direct (non-promoted) field/field conflicts. T := t.Underlying().(*types.Struct) for i := 0; i < T.NumFields(); i++ { if prev := T.Field(i); prev.Name() == to { e.Conflicting = true return } } } // Renaming an anonymous field requires renaming the type too. e.g. // print(s.T) // if we rename T to U, // type T int // this and // var s struct {T} // this must change too. if from.Anonymous() { if named, ok := from.Type().(*types.Named); ok { e.check(named.Obj(), to) } else if named, ok := deref(from.Type()).(*types.Named); ok { e.check(named.Obj(), to) } } // Check integrity of existing (field and method) selections. e.checkSelections(from, to) }
// checkMethod performs safety checks for renaming a method. // There are three hazards: // - declaration conflicts // - selection ambiguity/changes // - entailed renamings of assignable concrete/interface types. // We reject renamings initiated at concrete methods if it would // change the assignability relation. For renamings of abstract // methods, we rename all methods transitively coupled to it via // assignability. func (r *Unexporter) checkMethod(objsToUpdate map[types.Object]string, from *types.Func, to string) { // e.g. error.Error if from.Pkg() == nil { r.warn(from, r.errorf(from.Pos(), "you cannot rename built-in method %s", from)) return } // ASSIGNABILITY: We reject renamings of concrete methods that // would break a 'satisfy' constraint; but renamings of abstract // methods are allowed to proceed, and we rename affected // concrete and abstract methods as necessary. It is the // initial method that determines the policy. // Check for conflict at point of declaration. // Check to ensure preservation of assignability requirements. R := recv(from).Type() if isInterface(R) { // Abstract method // declaration prev, _, _ := types.LookupFieldOrMethod(R, false, from.Pkg(), to) if prev != nil { r.warn(from, r.errorf(from.Pos(), "renaming this interface method %q to %q", from.Name(), to), r.errorf(prev.Pos(), "\twould conflict with this method")) return } // Check all interfaces that embed this one for // declaration conflicts too. for _, info := range r.packages { // Start with named interface types (better errors) for _, obj := range info.Defs { if obj, ok := obj.(*types.TypeName); ok && isInterface(obj.Type()) { f, _, _ := types.LookupFieldOrMethod( obj.Type(), false, from.Pkg(), from.Name()) if f == nil { continue } t, _, _ := types.LookupFieldOrMethod( obj.Type(), false, from.Pkg(), to) if t == nil { continue } r.warn(from, r.errorf(from.Pos(), "renaming this interface method %q to %q", from.Name(), to), r.errorf(t.Pos(), "\twould conflict with this method"), r.errorf(obj.Pos(), "\tin named interface type %q", obj.Name())) } } // Now look at all literal interface types (includes named ones again). for e, tv := range info.Types { if e, ok := e.(*ast.InterfaceType); ok { _ = e _ = tv.Type.(*types.Interface) // TODO(adonovan): implement same check as above. } } } // assignability // // Find the set of concrete or abstract methods directly // coupled to abstract method 'from' by some // satisfy.Constraint, and rename them too. for key := range r.satisfy() { // key = (lhs, rhs) where lhs is always an interface. lsel := r.msets.MethodSet(key.LHS).Lookup(from.Pkg(), from.Name()) if lsel == nil { continue } rmethods := r.msets.MethodSet(key.RHS) rsel := rmethods.Lookup(from.Pkg(), from.Name()) if rsel == nil { continue } // If both sides have a method of this name, // and one of them is m, the other must be coupled. var coupled *types.Func switch from { case lsel.Obj(): coupled = rsel.Obj().(*types.Func) case rsel.Obj(): coupled = lsel.Obj().(*types.Func) default: continue } // We must treat concrete-to-interface // constraints like an implicit selection C.f of // each interface method I.f, and check that the // renaming leaves the selection unchanged and // unambiguous. // // Fun fact: the implicit selection of C.f // type I interface{f()} // type C struct{I} // func (C) g() // var _ I = C{} // here // yields abstract method I.f. This can make error // messages less than obvious. // if !isInterface(key.RHS) { // The logic below was derived from checkSelections. rtosel := rmethods.Lookup(from.Pkg(), to) if rtosel != nil { rto := rtosel.Obj().(*types.Func) delta := len(rsel.Index()) - len(rtosel.Index()) if delta < 0 { continue // no ambiguity } // TODO(adonovan): record the constraint's position. keyPos := token.NoPos rename := r.errorf(from.Pos(), "renaming this method %q to %q", from.Name(), to) if delta == 0 { // analogous to same-block conflict r.warn(from, rename, r.errorf(keyPos, "\twould make the %s method of %s invoked via interface %s ambiguous", to, key.RHS, key.LHS), r.errorf(rto.Pos(), "\twith (%s).%s", recv(rto).Type(), to)) } else { // analogous to super-block conflict r.warn(from, rename, r.errorf(keyPos, "\twould change the %s method of %s invoked via interface %s", to, key.RHS, key.LHS), r.errorf(coupled.Pos(), "\tfrom (%s).%s", recv(coupled).Type(), to), r.errorf(rto.Pos(), "\tto (%s).%s", recv(rto).Type(), to)) } return // one error is enough } } if !r.changeMethods { // This should be unreachable. r.warn(from, r.errorf(from.Pos(), "internal error: during renaming of abstract method %s", from), r.errorf(coupled.Pos(), "\tchangedMethods=false, coupled method=%s", coupled), r.errorf(from.Pos(), "\tPlease file a bug report")) return } // Rename the coupled method to preserve assignability. r.check(objsToUpdate, coupled, to) } } else { // Concrete method // declaration prev, indices, _ := types.LookupFieldOrMethod(R, true, from.Pkg(), to) if prev != nil && len(indices) == 1 { r.warn(from, r.errorf(from.Pos(), "renaming this method %q to %q", from.Name(), to), r.errorf(prev.Pos(), "\twould conflict with this %s", objectKind(prev))) return } // assignability // // Find the set of abstract methods coupled to concrete // method 'from' by some satisfy.Constraint, and rename // them too. // // Coupling may be indirect, e.g. I.f <-> C.f via type D. // // type I interface {f()} // type C int // type (C) f() // type D struct{C} // var _ I = D{} // for key := range r.satisfy() { // key = (lhs, rhs) where lhs is always an interface. if isInterface(key.RHS) { continue } rsel := r.msets.MethodSet(key.RHS).Lookup(from.Pkg(), from.Name()) if rsel == nil || rsel.Obj() != from { continue // rhs does not have the method } lsel := r.msets.MethodSet(key.LHS).Lookup(from.Pkg(), from.Name()) if lsel == nil { continue } imeth := lsel.Obj().(*types.Func) // imeth is the abstract method (e.g. I.f) // and key.RHS is the concrete coupling type (e.g. D). if !r.changeMethods { rename := r.errorf(from.Pos(), "renaming this method %q to %q", from.Name(), to) var pos token.Pos var iface string i := recv(imeth).Type() if named, ok := i.(*types.Named); ok { pos = named.Obj().Pos() iface = "interface " + named.Obj().Name() } else { pos = from.Pos() iface = i.String() } r.warn(from, rename, r.errorf(pos, "\twould make %s no longer assignable to %s", key.RHS, iface), r.errorf(imeth.Pos(), "\t(rename %s.%s if you intend to change both types)", i, from.Name())) return // one error is enough } // Rename the coupled interface method to preserve assignability. r.check(objsToUpdate, imeth, to) } } // Check integrity of existing (field and method) selections. // We skip this if there were errors above, to avoid redundant errors. r.checkSelections(objsToUpdate, from, to) }
// checkStructField checks that the field renaming will not cause // conflicts at its declaration, or ambiguity or changes to any selection. func (r *Unexporter) checkStructField(objsToUpdate map[types.Object]string, from *types.Var, to string) { // Check that the struct declaration is free of field conflicts, // and field/method conflicts. // go/types offers no easy way to get from a field (or interface // method) to its declaring struct (or interface), so we must // ascend the AST. info, path, _ := r.iprog.PathEnclosingInterval(from.Pos(), from.Pos()) // path matches this pattern: // [Ident SelectorExpr? StarExpr? Field FieldList StructType ParenExpr* ... File] // Ascend to FieldList. var i int for { if _, ok := path[i].(*ast.FieldList); ok { break } i++ } i++ tStruct := path[i].(*ast.StructType) i++ // Ascend past parens (unlikely). for { _, ok := path[i].(*ast.ParenExpr) if !ok { break } i++ } if spec, ok := path[i].(*ast.TypeSpec); ok { // This struct is also a named type. // We must check for direct (non-promoted) field/field // and method/field conflicts. named := info.Defs[spec.Name].Type() prev, indices, _ := types.LookupFieldOrMethod(named, true, info.Pkg, to) if len(indices) == 1 { r.warn(from, r.errorf(from.Pos(), "renaming this field %q to %q", from.Name(), to), r.errorf(prev.Pos(), "\twould conflict with this %s", objectKind(prev))) return // skip checkSelections to avoid redundant errors } } else { // This struct is not a named type. // We need only check for direct (non-promoted) field/field conflicts. t := info.Types[tStruct].Type.Underlying().(*types.Struct) for i := 0; i < t.NumFields(); i++ { if prev := t.Field(i); prev.Name() == to { r.warn(from, r.errorf(from.Pos(), "renaming this field %q to %q", from.Name(), to), r.errorf(prev.Pos(), "\twould conflict with this field")) return // skip checkSelections to avoid redundant errors } } } // Renaming an anonymous field requires renaming the type too. e.g. // print(s.T) // if we rename T to U, // type T int // this and // var s struct {T} // this must change too. if from.Anonymous() { if named, ok := from.Type().(*types.Named); ok { r.check(objsToUpdate, named.Obj(), to) } else if named, ok := deref(from.Type()).(*types.Named); ok { r.check(objsToUpdate, named.Obj(), to) } } // Check integrity of existing (field and method) selections. r.checkSelections(objsToUpdate, from, to) }
// On success, findObjects returns the list of objects named // spec.fromName matching the spec. On success, the result has exactly // one element unless spec.searchFor!="", in which case it has at least one // element. // func findObjects(info *loader.PackageInfo, spec *spec) ([]types.Object, error) { if spec.pkgMember == "" { if spec.searchFor == "" { panic(spec) } objects := searchDefs(&info.Info, spec.searchFor) if objects == nil { return nil, fmt.Errorf("no object %q declared in package %q", spec.searchFor, info.Pkg.Path()) } return objects, nil } pkgMember := info.Pkg.Scope().Lookup(spec.pkgMember) if pkgMember == nil { return nil, fmt.Errorf("package %q has no member %q", info.Pkg.Path(), spec.pkgMember) } var searchFunc *types.Func if spec.typeMember == "" { // package member if spec.searchFor == "" { return []types.Object{pkgMember}, nil } // Search within pkgMember, which must be a function. searchFunc, _ = pkgMember.(*types.Func) if searchFunc == nil { return nil, fmt.Errorf("cannot search for %q within %s %q", spec.searchFor, objectKind(pkgMember), pkgMember) } } else { // field/method of type // e.g. (encoding/json.Decoder).Decode // or ::x within it. tName, _ := pkgMember.(*types.TypeName) if tName == nil { return nil, fmt.Errorf("%s.%s is a %s, not a type", info.Pkg.Path(), pkgMember.Name(), objectKind(pkgMember)) } // search within named type. obj, _, _ := types.LookupFieldOrMethod(tName.Type(), true, info.Pkg, spec.typeMember) if obj == nil { return nil, fmt.Errorf("cannot find field or method %q of %s %s.%s", spec.typeMember, typeKind(tName.Type()), info.Pkg.Path(), tName.Name()) } if spec.searchFor == "" { return []types.Object{obj}, nil } searchFunc, _ = obj.(*types.Func) if searchFunc == nil { return nil, fmt.Errorf("cannot search for local name %q within %s (%s.%s).%s; need a function", spec.searchFor, objectKind(obj), info.Pkg.Path(), tName.Name(), obj.Name()) } if isInterface(tName.Type()) { return nil, fmt.Errorf("cannot search for local name %q within abstract method (%s.%s).%s", spec.searchFor, info.Pkg.Path(), tName.Name(), searchFunc.Name()) } } // -- search within function or method -- decl := funcDecl(info, searchFunc) if decl == nil { return nil, fmt.Errorf("cannot find syntax for %s", searchFunc) // can't happen? } var objects []types.Object for _, obj := range searchDefs(&info.Info, spec.searchFor) { // We use positions, not scopes, to determine whether // the obj is within searchFunc. This is clumsy, but the // alternative, using the types.Scope tree, doesn't // account for non-lexical objects like fields and // interface methods. if decl.Pos() <= obj.Pos() && obj.Pos() < decl.End() && obj != searchFunc { objects = append(objects, obj) } } if objects == nil { return nil, fmt.Errorf("no local definition of %q within %s", spec.searchFor, searchFunc) } return objects, nil }
func (e *Export) checkMethod(from *types.Func, to string) { // e.g. error.Error if from.Pkg() == nil { e.Conflicting = true return } // ASSIGNABILITY: We reject renamings of concrete methods that // would break a 'satisfy' constraint; but renamings of abstract // methods are allowed to proceed, and we rename affected // concrete and abstract methods as necessary. It is the // initial method that determines the policy. // Check for conflict at point of declaration. // Check to ensure preservation of assignability requirements. R := recv(from).Type() if isInterface(R) { // Abstract method // declaration prev, _, _ := types.LookupFieldOrMethod(R, false, from.Pkg(), to) if prev != nil { e.Conflicting = true return } // Check all interfaces that embed this one for // declaration conflicts too. for _, info := range e.u.prog.AllPackages { // Start with named interface types (better errors) for _, obj := range info.Defs { if obj, ok := obj.(*types.TypeName); ok && isInterface(obj.Type()) { f, _, _ := types.LookupFieldOrMethod( obj.Type(), false, from.Pkg(), from.Name()) if f == nil { continue } t, _, _ := types.LookupFieldOrMethod(obj.Type(), false, from.Pkg(), to) if t == nil { continue } e.Conflicting = true return } } } // assignability // // Find the set of concrete or abstract methods directly // coupled to abstract method 'from' by some // satisfy.Constraint, and rename them too. for key := range e.u.satisfy() { // key = (lhs, rhs) where lhs is always an interface. lsel := e.u.msets.MethodSet(key.LHS).Lookup(from.Pkg(), from.Name()) if lsel == nil { continue } rmethods := e.u.msets.MethodSet(key.RHS) rsel := rmethods.Lookup(from.Pkg(), from.Name()) if rsel == nil { continue } // If both sides have a method of this name, // and one of them is m, the other must be coupled. var coupled *types.Func switch from { case lsel.Obj(): coupled = rsel.Obj().(*types.Func) case rsel.Obj(): coupled = lsel.Obj().(*types.Func) default: continue } // We must treat concrete-to-interface // constraints like an implicit selection C.f of // each interface method I.f, and check that the // renaming leaves the selection unchanged and // unambiguous. // // Fun fact: the implicit selection of C.f // type I interface{f()} // type C struct{I} // func (C) g() // var _ I = C{} // here // yields abstract method I.f. This can make error // messages less than obvious. // if !isInterface(key.RHS) { // The logic below was derived from checkSelections. rtosel := rmethods.Lookup(from.Pkg(), to) if rtosel != nil { delta := len(rsel.Index()) - len(rtosel.Index()) if delta < 0 { continue // no ambiguity } e.Conflicting = true return } } // Rename the coupled method to preserve assignability. e.check(coupled, to) } } // Concrete method // declaration prev, indices, _ := types.LookupFieldOrMethod(R, true, from.Pkg(), to) if prev != nil && len(indices) == 1 { e.Conflicting = true return } // assignability // // Find the set of abstract methods coupled to concrete // method 'from' by some satisfy.Constraint, and rename // them too. // // Coupling may be indirect, e.g. I.f <-> C.f via type D. // // type I interface {f()} // type C int // type (C) f() // type D struct{C} // var _ I = D{} // for key := range e.u.satisfy() { // key = (lhs, rhs) where lhs is always an interface. if isInterface(key.RHS) { continue } rsel := e.u.msets.MethodSet(key.RHS).Lookup(from.Pkg(), from.Name()) if rsel == nil || rsel.Obj() != from { continue // rhs does not have the method } lsel := e.u.msets.MethodSet(key.LHS).Lookup(from.Pkg(), from.Name()) if lsel == nil { continue } imeth := lsel.Obj().(*types.Func) e.check(imeth, to) } // Check integrity of existing (field and method) selections. // We skip this if there were errors above, to avoid redundant errors. e.checkSelections(from, to) }
// hasMethod reports whether the type contains a method with the given name. // It is part of the workaround for Formatters and should be deleted when // that workaround is no longer necessary. // TODO: This could be better once issue 6259 is fixed. func (f *File) hasMethod(typ types.Type, name string) bool { // assume we have an addressable variable of type typ obj, _, _ := types.LookupFieldOrMethod(typ, true, f.pkg.typesPkg, name) _, ok := obj.(*types.Func) return ok }
// checkExample walks the documentation example functions checking for common // mistakes of misnamed functions, failure to map functions to existing // identifiers, etc. func checkExample(f *File, node ast.Node) { if !f.IsTest() { return } var ( pkg = f.pkg pkgName = pkg.typesPkg.Name() scopes = []*types.Scope{pkg.typesPkg.Scope()} lookup = func(name string) types.Object { for _, scope := range scopes { if o := scope.Lookup(name); o != nil { return o } } return nil } ) if strings.HasSuffix(pkgName, "_test") { // Treat 'package foo_test' as an alias for 'package foo'. var ( basePkg = strings.TrimSuffix(pkgName, "_test") pkg = f.pkg ) for _, p := range pkg.typesPkg.Imports() { if p.Name() == basePkg { scopes = append(scopes, p.Scope()) break } } } fn, ok := node.(*ast.FuncDecl) if !ok { // Ignore non-functions. return } var ( fnName = fn.Name.Name report = func(format string, args ...interface{}) { f.Badf(node.Pos(), format, args...) } ) if fn.Recv != nil || !strings.HasPrefix(fnName, "Example") { // Ignore methods and types not named "Example". return } if params := fn.Type.Params; len(params.List) != 0 { report("%s should be niladic", fnName) } if results := fn.Type.Results; results != nil && len(results.List) != 0 { report("%s should return nothing", fnName) } if fnName == "Example" { // Nothing more to do. return } if filesRun && !includesNonTest { // The coherence checks between a test and the package it tests // will report false positives if no non-test files have // been provided. return } var ( exName = strings.TrimPrefix(fnName, "Example") elems = strings.SplitN(exName, "_", 3) ident = elems[0] obj = lookup(ident) ) if ident != "" && obj == nil { // Check ExampleFoo and ExampleBadFoo. report("%s refers to unknown identifier: %s", fnName, ident) // Abort since obj is absent and no subsequent checks can be performed. return } if elemCnt := strings.Count(exName, "_"); elemCnt == 0 { // Nothing more to do. return } mmbr := elems[1] if ident == "" { // Check Example_suffix and Example_BadSuffix. if residual := strings.TrimPrefix(exName, "_"); !isExampleSuffix(residual) { report("%s has malformed example suffix: %s", fnName, residual) } return } if !isExampleSuffix(mmbr) { // Check ExampleFoo_Method and ExampleFoo_BadMethod. if obj, _, _ := types.LookupFieldOrMethod(obj.Type(), true, obj.Pkg(), mmbr); obj == nil { report("%s refers to unknown field or method: %s.%s", fnName, ident, mmbr) } } if len(elems) == 3 && !isExampleSuffix(elems[2]) { // Check ExampleFoo_Method_suffix and ExampleFoo_Method_Badsuffix. report("%s has malformed example suffix: %s", fnName, elems[2]) } return }
func main() { flag.Parse() exitStatus := 0 importPaths := gotool.ImportPaths(flag.Args()) if len(importPaths) == 0 { importPaths = []string{"."} } ctx := build.Default for _, pkgPath := range importPaths { visitor := &visitor{ m: make(map[types.Type]map[string]int), skip: make(map[types.Type]struct{}), } loadcfg := loader.Config{ Build: &ctx, } rest, err := loadcfg.FromArgs([]string{pkgPath}, *loadTestFiles) if err != nil { fmt.Fprintf(os.Stderr, "could not parse arguments: %s", err) continue } if len(rest) > 0 { fmt.Fprintf(os.Stderr, "unhandled extra arguments: %v", rest) continue } program, err := loadcfg.Load() if err != nil { fmt.Fprintf(os.Stderr, "could not type check: %s", err) continue } pkg := program.InitialPackages()[0] visitor.prog = program visitor.pkg = pkg for _, f := range pkg.Files { ast.Walk(visitor, f) } for t := range visitor.m { if _, skip := visitor.skip[t]; skip { continue } for fieldName, v := range visitor.m[t] { if !*reportExported && ast.IsExported(fieldName) { continue } if v == 0 { field, _, _ := types.LookupFieldOrMethod(t, false, pkg.Pkg, fieldName) if fieldName == "XMLName" { if named, ok := field.Type().(*types.Named); ok && named.Obj().Pkg().Path() == "encoding/xml" { continue } } pos := program.Fset.Position(field.Pos()) fmt.Printf("%s: %s:%d:%d: %s.%s\n", pkgPath, pos.Filename, pos.Line, pos.Column, types.TypeString(t, nil), fieldName, ) exitStatus = 1 } } } } os.Exit(exitStatus) }
// checkMethod performs safety checks for renaming a method. // There are three hazards: // - declaration conflicts // - selection ambiguity/changes // - entailed renamings of assignable concrete/interface types (for now, just reject) func (r *renamer) checkMethod(from *types.Func) { // e.g. error.Error if from.Pkg() == nil { r.errorf(from.Pos(), "you cannot rename built-in method %s", from) return } // As always, having to support concrete methods with pointer // and non-pointer receivers, and named vs unnamed types with // methods, makes tooling fun. // ASSIGNABILITY // // For now, if any method renaming breaks a required // assignability to another type, we reject it. // // TODO(adonovan): probably we should compute the entailed // renamings so that an interface method renaming causes // concrete methods to change too. But which ones? // // There is no correct answer, only heuristics, because Go's // "duck typing" doesn't distinguish intentional from contingent // assignability. There are two obvious approaches: // // (1) Update the minimum set of types to preserve the // assignability of types all syntactic assignments // (incl. implicit ones in calls, returns, sends, etc). // The satisfy.Finder enumerates these. // This is likely to be an underapproximation. // // (2) Update all types that are assignable to/from the changed // type. This requires computing the "implements" relation // for all pairs of types (as godoc and oracle do). // This is likely to be an overapproximation. // // If a concrete type is renamed, we probably do not want to // rename corresponding interfaces; interface renamings should // probably be initiated at the interface. (But what if a // concrete type implements multiple interfaces with the same // method? Then the user is stuck.) // // We need some experience before we decide how to implement this. // Check for conflict at point of declaration. // Check to ensure preservation of assignability requirements. recv := from.Type().(*types.Signature).Recv().Type() if isInterface(recv) { // Abstract method // declaration prev, _, _ := types.LookupFieldOrMethod(recv, false, from.Pkg(), r.to) if prev != nil { r.errorf(from.Pos(), "renaming this interface method %q to %q", from.Name(), r.to) r.errorf(prev.Pos(), "\twould conflict with this method") return } // Check all interfaces that embed this one for // declaration conflicts too. for _, info := range r.packages { // Start with named interface types (better errors) for _, obj := range info.Defs { if obj, ok := obj.(*types.TypeName); ok && isInterface(obj.Type()) { f, _, _ := types.LookupFieldOrMethod( obj.Type(), false, from.Pkg(), from.Name()) if f == nil { continue } t, _, _ := types.LookupFieldOrMethod( obj.Type(), false, from.Pkg(), r.to) if t == nil { continue } r.errorf(from.Pos(), "renaming this interface method %q to %q", from.Name(), r.to) r.errorf(t.Pos(), "\twould conflict with this method") r.errorf(obj.Pos(), "\tin named interface type %q", obj.Name()) } } // Now look at all literal interface types (includes named ones again). for e, tv := range info.Types { if e, ok := e.(*ast.InterfaceType); ok { _ = e _ = tv.Type.(*types.Interface) // TODO(adonovan): implement same check as above. } } } // assignability for T := range r.findAssignments(recv) { if obj, _, _ := types.LookupFieldOrMethod(T, false, from.Pkg(), from.Name()); obj == nil { continue } r.errorf(from.Pos(), "renaming this method %q to %q", from.Name(), r.to) var pos token.Pos var other string if named, ok := T.(*types.Named); ok { pos = named.Obj().Pos() other = named.Obj().Name() } else { pos = from.Pos() other = T.String() } r.errorf(pos, "\twould make %s no longer assignable to it", other) return } } else { // Concrete method // declaration prev, indices, _ := types.LookupFieldOrMethod(recv, true, from.Pkg(), r.to) if prev != nil && len(indices) == 1 { r.errorf(from.Pos(), "renaming this method %q to %q", from.Name(), r.to) r.errorf(prev.Pos(), "\twould conflict with this %s", objectKind(prev)) return } // assignability (of both T and *T) recvBase := deref(recv) for _, R := range []types.Type{recvBase, types.NewPointer(recvBase)} { for I := range r.findAssignments(R) { if obj, _, _ := types.LookupFieldOrMethod(I, true, from.Pkg(), from.Name()); obj == nil { continue } r.errorf(from.Pos(), "renaming this method %q to %q", from.Name(), r.to) var pos token.Pos var iface string if named, ok := I.(*types.Named); ok { pos = named.Obj().Pos() iface = "interface " + named.Obj().Name() } else { pos = from.Pos() iface = I.String() } r.errorf(pos, "\twould make it no longer assignable to %s", iface) return // one is enough } } } // Check integrity of existing (field and method) selections. // We skip this if there were errors above, to avoid redundant errors. r.checkSelections(from) }