| // 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) |
| } |