// Copyright 2019 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. // // Taken from golang.org/x/tools/refactor/rename. package source import ( "fmt" "go/ast" "go/token" "go/types" "reflect" "strconv" "strings" "unicode" "golang.org/x/tools/go/ast/astutil" "golang.org/x/tools/refactor/satisfy" ) // errorf reports an error (e.g. conflict) and prevents file modification. func (r *renamer) errorf(pos token.Pos, format string, args ...interface{}) { r.hadConflicts = true r.errors += fmt.Sprintf(format, args...) } // check performs safety checks of the renaming of the 'from' object to r.to. func (r *renamer) check(from types.Object) { if r.objsToUpdate[from] { return } r.objsToUpdate[from] = true // NB: order of conditions is important. if from_, ok := from.(*types.PkgName); ok { r.checkInFileBlock(from_) } else if from_, ok := from.(*types.Label); ok { r.checkLabel(from_) } else if isPackageLevel(from) { r.checkInPackageBlock(from) } else if v, ok := from.(*types.Var); ok && v.IsField() { r.checkStructField(v) } else if f, ok := from.(*types.Func); ok && recv(f) != nil { r.checkMethod(f) } else if isLocal(from) { r.checkInLocalScope(from) } else { r.errorf(from.Pos(), "unexpected %s object %q (please report a bug)\n", objectKind(from), from) } } // checkInFileBlock performs safety checks for renames of objects in the file block, // i.e. imported package names. func (r *renamer) checkInFileBlock(from *types.PkgName) { // Check import name is not "init". if r.to == "init" { r.errorf(from.Pos(), "%q is not a valid imported package name", r.to) } // Check for conflicts between file and package block. if prev := from.Pkg().Scope().Lookup(r.to); prev != nil { r.errorf(from.Pos(), "renaming this %s %q to %q would conflict", objectKind(from), from.Name(), r.to) r.errorf(prev.Pos(), "\twith this package member %s", objectKind(prev)) return // since checkInPackageBlock would report redundant errors } // Check for conflicts in lexical scope. r.checkInLexicalScope(from, r.packages[from.Pkg()]) } // checkInPackageBlock performs safety checks for renames of // func/var/const/type objects in the package block. func (r *renamer) checkInPackageBlock(from types.Object) { // Check that there are no references to the name from another // package if the renaming would make it unexported. if ast.IsExported(from.Name()) && !ast.IsExported(r.to) { for typ, pkg := range r.packages { if typ == from.Pkg() { continue } if id := someUse(pkg.GetTypesInfo(), from); id != nil && !r.checkExport(id, typ, from) { break } } } pkg := r.packages[from.Pkg()] if pkg == nil { return } // Check that in the package block, "init" is a function, and never referenced. if r.to == "init" { kind := objectKind(from) if kind == "func" { // Reject if intra-package references to it exist. for id, obj := range pkg.GetTypesInfo().Uses { if obj == from { r.errorf(from.Pos(), "renaming this func %q to %q would make it a package initializer", from.Name(), r.to) r.errorf(id.Pos(), "\tbut references to it exist") break } } } else { r.errorf(from.Pos(), "you cannot have a %s at package level named %q", kind, r.to) } } // Check for conflicts between package block and all file blocks. for _, f := range pkg.GetSyntax() { fileScope := pkg.GetTypesInfo().Scopes[f] b, prev := fileScope.LookupParent(r.to, token.NoPos) if b == fileScope { r.errorf(from.Pos(), "renaming this %s %q to %q would conflict", objectKind(from), from.Name(), r.to) var prevPos token.Pos if prev != nil { prevPos = prev.Pos() } r.errorf(prevPos, "\twith this %s", objectKind(prev)) return // since checkInPackageBlock would report redundant errors } } // Check for conflicts in lexical scope. if from.Exported() { for _, pkg := range r.packages { r.checkInLexicalScope(from, pkg) } } else { r.checkInLexicalScope(from, pkg) } } func (r *renamer) checkInLocalScope(from types.Object) { pkg := r.packages[from.Pkg()] r.checkInLexicalScope(from, pkg) } // checkInLexicalScope performs safety checks that a renaming does not // change the lexical reference structure of the specified package. // // For objects in lexical scope, there are three kinds of conflicts: // same-, sub-, and super-block conflicts. We will illustrate all three // using this example: // // var x int // var z int // // func f(y int) { // print(x) // print(y) // } // // Renaming x to z encounters a SAME-BLOCK CONFLICT, because an object // with the new name already exists, defined in the same lexical block // as the old object. // // Renaming x to y encounters a SUB-BLOCK CONFLICT, because there exists // a reference to x from within (what would become) a hole in its scope. // The definition of y in an (inner) sub-block would cast a shadow in // the scope of the renamed variable. // // Renaming y to x encounters a SUPER-BLOCK CONFLICT. This is the // converse situation: there is an existing definition of the new name // (x) in an (enclosing) super-block, and the renaming would create a // hole in its scope, within which there exist references to it. The // new name casts a shadow in scope of the existing definition of x in // the super-block. // // Removing the old name (and all references to it) is always safe, and // requires no checks. // func (r *renamer) checkInLexicalScope(from types.Object, pkg Package) { b := from.Parent() // the block defining the 'from' object if b != nil { toBlock, to := b.LookupParent(r.to, from.Parent().End()) if toBlock == b { // same-block conflict r.errorf(from.Pos(), "renaming this %s %q to %q", objectKind(from), from.Name(), r.to) r.errorf(to.Pos(), "\tconflicts with %s in same block", objectKind(to)) return } else if toBlock != nil { // Check for super-block conflict. // The name r.to is defined in a superblock. // Is that name referenced from within this block? forEachLexicalRef(pkg, to, func(id *ast.Ident, block *types.Scope) bool { _, obj := lexicalLookup(block, from.Name(), id.Pos()) if obj == from { // super-block conflict r.errorf(from.Pos(), "renaming this %s %q to %q", objectKind(from), from.Name(), r.to) r.errorf(id.Pos(), "\twould shadow this reference") r.errorf(to.Pos(), "\tto the %s declared here", objectKind(to)) return false // stop } return true }) } } // Check for sub-block conflict. // Is there an intervening definition of r.to between // the block defining 'from' and some reference to it? forEachLexicalRef(pkg, from, func(id *ast.Ident, block *types.Scope) bool { // Find the block that defines the found reference. // It may be an ancestor. fromBlock, _ := lexicalLookup(block, from.Name(), id.Pos()) // See what r.to would resolve to in the same scope. toBlock, to := lexicalLookup(block, r.to, id.Pos()) if to != nil { // sub-block conflict if deeper(toBlock, fromBlock) { r.errorf(from.Pos(), "renaming this %s %q to %q", objectKind(from), from.Name(), r.to) r.errorf(id.Pos(), "\twould cause this reference to become shadowed") r.errorf(to.Pos(), "\tby this intervening %s definition", objectKind(to)) return false // stop } } return true }) // Renaming a type that is used as an embedded field // requires renaming the field too. e.g. // type T int // if we rename this to U.. // var s struct {T} // print(s.T) // ...this must change too if _, ok := from.(*types.TypeName); ok { for id, obj := range pkg.GetTypesInfo().Uses { if obj == from { if field := pkg.GetTypesInfo().Defs[id]; field != nil { r.check(field) } } } } } // lexicalLookup is like (*types.Scope).LookupParent but respects the // environment visible at pos. It assumes the relative position // information is correct with each file. func lexicalLookup(block *types.Scope, name string, pos token.Pos) (*types.Scope, types.Object) { for b := block; b != nil; b = b.Parent() { obj := b.Lookup(name) // The scope of a package-level object is the entire package, // so ignore pos in that case. // No analogous clause is needed for file-level objects // since no reference can appear before an import decl. if obj == nil || obj.Pkg() == nil { continue } if b == obj.Pkg().Scope() || obj.Pos() < pos { return b, obj } } return nil, nil } // deeper reports whether block x is lexically deeper than y. func deeper(x, y *types.Scope) bool { if x == y || x == nil { return false } else if y == nil { return true } else { return deeper(x.Parent(), y.Parent()) } } // forEachLexicalRef calls fn(id, block) for each identifier id in package // pkg that is a reference to obj in lexical scope. block is the // lexical block enclosing the reference. If fn returns false the // iteration is terminated and findLexicalRefs returns false. func forEachLexicalRef(pkg Package, obj types.Object, fn func(id *ast.Ident, block *types.Scope) bool) bool { ok := true var stack []ast.Node var visit func(n ast.Node) bool visit = func(n ast.Node) bool { if n == nil { stack = stack[:len(stack)-1] // pop return false } if !ok { return false // bail out } stack = append(stack, n) // push switch n := n.(type) { case *ast.Ident: if pkg.GetTypesInfo().Uses[n] == obj { block := enclosingBlock(pkg.GetTypesInfo(), stack) if !fn(n, block) { ok = false } } return visit(nil) // pop stack case *ast.SelectorExpr: // don't visit n.Sel ast.Inspect(n.X, visit) return visit(nil) // pop stack, don't descend case *ast.CompositeLit: // Handle recursion ourselves for struct literals // so we don't visit field identifiers. tv, ok := pkg.GetTypesInfo().Types[n] if !ok { return visit(nil) // pop stack, don't descend } if _, ok := Deref(tv.Type).Underlying().(*types.Struct); ok { if n.Type != nil { ast.Inspect(n.Type, visit) } for _, elt := range n.Elts { if kv, ok := elt.(*ast.KeyValueExpr); ok { ast.Inspect(kv.Value, visit) } else { ast.Inspect(elt, visit) } } return visit(nil) // pop stack, don't descend } } return true } for _, f := range pkg.GetSyntax() { ast.Inspect(f, visit) if len(stack) != 0 { panic(stack) } if !ok { break } } return ok } // enclosingBlock returns the innermost block enclosing the specified // AST node, specified in the form of a path from the root of the file, // [file...n]. func enclosingBlock(info *types.Info, stack []ast.Node) *types.Scope { for i := range stack { n := stack[len(stack)-1-i] // For some reason, go/types always associates a // function's scope with its FuncType. // TODO(adonovan): feature or a bug? switch f := n.(type) { case *ast.FuncDecl: n = f.Type case *ast.FuncLit: n = f.Type } if b := info.Scopes[n]; b != nil { return b } } panic("no Scope for *ast.File") } func (r *renamer) checkLabel(label *types.Label) { // Check there are no identical labels in the function's label block. // (Label blocks don't nest, so this is easy.) if prev := label.Parent().Lookup(r.to); prev != nil { r.errorf(label.Pos(), "renaming this label %q to %q", label.Name(), prev.Name()) r.errorf(prev.Pos(), "\twould conflict with this one") } } // checkStructField checks that the field renaming will not cause // conflicts at its declaration, or ambiguity or changes to any selection. func (r *renamer) checkStructField(from *types.Var) { // 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. fromPkg, ok := r.packages[from.Pkg()] if !ok { return } pkg, path, _ := pathEnclosingInterval(r.fset, fromPkg, from.Pos(), from.Pos()) if pkg == nil || path == nil { return } // 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 := pkg.GetTypesInfo().Defs[spec.Name].Type() prev, indices, _ := types.LookupFieldOrMethod(named, true, pkg.GetTypes(), r.to) if len(indices) == 1 { r.errorf(from.Pos(), "renaming this field %q to %q", from.Name(), r.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 := pkg.GetTypesInfo().Types[tStruct].Type.Underlying().(*types.Struct) for i := 0; i < T.NumFields(); i++ { if prev := T.Field(i); prev.Name() == r.to { r.errorf(from.Pos(), "renaming this field %q to %q", from.Name(), r.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(named.Obj()) } else if named, ok := Deref(from.Type()).(*types.Named); ok { r.check(named.Obj()) } } // Check integrity of existing (field and method) selections. r.checkSelections(from) } // checkSelection checks that all uses and selections that resolve to // the specified object would continue to do so after the renaming. func (r *renamer) checkSelections(from types.Object) { for typ, pkg := range r.packages { if id := someUse(pkg.GetTypesInfo(), from); id != nil { if !r.checkExport(id, typ, from) { return } } for syntax, sel := range pkg.GetTypesInfo().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(), r.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(from, delta, syntax, obj) return } } else if sel.Obj().Name() == r.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(from, -delta, syntax, sel.Obj()) return } } } } } func (r *renamer) selectionConflict(from types.Object, delta int, syntax *ast.SelectorExpr, obj types.Object) { r.errorf(from.Pos(), "renaming this %s %q to %q", objectKind(from), from.Name(), r.to) switch { case delta < 0: // analogous to sub-block conflict r.errorf(syntax.Sel.Pos(), "\twould change the referent of this selection") r.errorf(obj.Pos(), "\tof this %s", objectKind(obj)) case delta == 0: // analogous to same-block conflict r.errorf(syntax.Sel.Pos(), "\twould make this reference ambiguous") r.errorf(obj.Pos(), "\twith this %s", objectKind(obj)) case delta > 0: // analogous to super-block conflict r.errorf(syntax.Sel.Pos(), "\twould shadow this selection") r.errorf(obj.Pos(), "\tof the %s declared here", objectKind(obj)) } } // 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 *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 } // 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(), 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 _, pkg := range r.packages { // Start with named interface types (better errors) for _, obj := range pkg.GetTypesInfo().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 pkg.GetTypesInfo().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(), r.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 r.errorf(from.Pos(), "renaming this method %q to %q", from.Name(), r.to) if delta == 0 { // analogous to same-block conflict r.errorf(keyPos, "\twould make the %s method of %s invoked via interface %s ambiguous", r.to, key.RHS, key.LHS) r.errorf(rto.Pos(), "\twith (%s).%s", recv(rto).Type(), r.to) } else { // analogous to super-block conflict r.errorf(keyPos, "\twould change the %s method of %s invoked via interface %s", r.to, key.RHS, key.LHS) r.errorf(coupled.Pos(), "\tfrom (%s).%s", recv(coupled).Type(), r.to) r.errorf(rto.Pos(), "\tto (%s).%s", recv(rto).Type(), r.to) } return // one error is enough } } if !r.changeMethods { // This should be unreachable. 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(coupled) } } else { // Concrete method // declaration prev, indices, _ := types.LookupFieldOrMethod(R, 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 // // 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 { r.errorf(from.Pos(), "renaming this method %q to %q", from.Name(), r.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.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(imeth) } } // Check integrity of existing (field and method) selections. // We skip this if there were errors above, to avoid redundant errors. r.checkSelections(from) } func (r *renamer) checkExport(id *ast.Ident, pkg *types.Package, from types.Object) bool { // Reject cross-package references if r.to is unexported. // (Such references may be qualified identifiers or field/method // selections.) if !ast.IsExported(r.to) && pkg != from.Pkg() { r.errorf(from.Pos(), "renaming %q to %q would make it unexported", from.Name(), r.to) r.errorf(id.Pos(), "\tbreaking references from packages such as %q", pkg.Path()) return false } return true } // satisfy returns the set of interface satisfaction constraints. func (r *renamer) satisfy() map[satisfy.Constraint]bool { if r.satisfyConstraints == nil { // Compute on demand: it's expensive. var f satisfy.Finder for _, pkg := range r.packages { // From satisfy.Finder documentation: // // The package must be free of type errors, and // info.{Defs,Uses,Selections,Types} must have been populated by the // type-checker. // // Only proceed if all packages have no errors. if errs := pkg.GetErrors(); len(errs) > 0 { r.errorf(token.NoPos, // we don't have a position for this error. "renaming %q to %q not possible because %q has errors", r.from, r.to, pkg.PkgPath()) return nil } f.Find(pkg.GetTypesInfo(), pkg.GetSyntax()) } r.satisfyConstraints = f.Result } return r.satisfyConstraints } // -- helpers ---------------------------------------------------------- // recv returns the method's receiver. func recv(meth *types.Func) *types.Var { return meth.Type().(*types.Signature).Recv() } // someUse returns an arbitrary use of obj within info. func someUse(info *types.Info, obj types.Object) *ast.Ident { for id, o := range info.Uses { if o == obj { return id } } return nil } // pathEnclosingInterval returns the Package and ast.Node that // contain source interval [start, end), and all the node's ancestors // up to the AST root. It searches all ast.Files of all packages. // exact is defined as for astutil.PathEnclosingInterval. // // The zero value is returned if not found. // func pathEnclosingInterval(fset *token.FileSet, pkg Package, start, end token.Pos) (resPkg Package, path []ast.Node, exact bool) { pkgs := []Package{pkg} for _, f := range pkg.GetSyntax() { for _, imp := range f.Imports { if imp == nil { continue } importPath, err := strconv.Unquote(imp.Path.Value) if err != nil { continue } importPkg, err := pkg.GetImport(importPath) if err != nil { return nil, nil, false } pkgs = append(pkgs, importPkg) } } for _, p := range pkgs { for _, f := range p.GetSyntax() { if f.Pos() == token.NoPos { // This can happen if the parser saw // too many errors and bailed out. // (Use parser.AllErrors to prevent that.) continue } if !tokenFileContainsPos(fset.File(f.Pos()), start) { continue } if path, exact := astutil.PathEnclosingInterval(f, start, end); path != nil { return pkg, path, exact } } } return nil, nil, false } // TODO(adonovan): make this a method: func (*token.File) Contains(token.Pos) func tokenFileContainsPos(f *token.File, pos token.Pos) bool { p := int(pos) base := f.Base() return base <= p && p < base+f.Size() } func objectKind(obj types.Object) string { switch obj := obj.(type) { case *types.PkgName: return "imported package name" case *types.TypeName: return "type" case *types.Var: if obj.IsField() { return "field" } case *types.Func: if obj.Type().(*types.Signature).Recv() != nil { return "method" } } // label, func, var, const return strings.ToLower(strings.TrimPrefix(reflect.TypeOf(obj).String(), "*types.")) } // NB: for renamings, blank is not considered valid. func isValidIdentifier(id string) bool { if id == "" || id == "_" { return false } for i, r := range id { if !isLetter(r) && (i == 0 || !isDigit(r)) { return false } } return token.Lookup(id) == token.IDENT } // isLocal reports whether obj is local to some function. // Precondition: not a struct field or interface method. func isLocal(obj types.Object) bool { // [... 5=stmt 4=func 3=file 2=pkg 1=universe] var depth int for scope := obj.Parent(); scope != nil; scope = scope.Parent() { depth++ } return depth >= 4 } func isPackageLevel(obj types.Object) bool { return obj.Pkg().Scope().Lookup(obj.Name()) == obj } // -- Plundered from go/scanner: --------------------------------------- func isLetter(ch rune) bool { return 'a' <= ch && ch <= 'z' || 'A' <= ch && ch <= 'Z' || ch == '_' || ch >= 0x80 && unicode.IsLetter(ch) } func isDigit(ch rune) bool { return '0' <= ch && ch <= '9' || ch >= 0x80 && unicode.IsDigit(ch) }