// 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 isStringer(obj types.Object) bool { switch obj := obj.(type) { case *types.Func: if obj.Name() != "String" { return false } sig, ok := obj.Type().(*types.Signature) if !ok { return false } if sig.Recv() == nil { return false } if sig.Params().Len() != 0 { return false } res := sig.Results() if res.Len() != 1 { return false } ret := res.At(0).Type() if ret != types.Universe.Lookup("string").Type() { return false } return true default: return false } return false }
// lookup returns the address of the named variable identified by obj // that is local to function f or one of its enclosing functions. // If escaping, the reference comes from a potentially escaping pointer // expression and the referent must be heap-allocated. // func (f *Function) lookup(obj types.Object, escaping bool) Value { if v, ok := f.objects[obj]; ok { if alloc, ok := v.(*Alloc); ok && escaping { alloc.Heap = true } return v // function-local var (address) } // Definition must be in an enclosing function; // plumb it through intervening closures. if f.parent == nil { panic("no Value for type.Object " + obj.Name()) } outer := f.parent.lookup(obj, true) // escaping v := &FreeVar{ name: obj.Name(), typ: outer.Type(), pos: outer.Pos(), outer: outer, parent: f, } f.objects[obj] = v f.FreeVars = append(f.FreeVars, v) return v }
func (r *Unexporter) selectionConflict(objsToUpdate map[types.Object]string, from types.Object, to string, delta int, syntax *ast.SelectorExpr, obj types.Object) { rename := r.errorf(from.Pos(), "renaming this %s %q to %q", objectKind(from), from.Name(), to) switch { case delta < 0: // analogous to sub-block conflict r.warn(from, rename, 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.warn(from, rename, 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.warn(from, rename, r.errorf(syntax.Sel.Pos(), "\twould shadow this selection"), r.errorf(obj.Pos(), "\tof the %s declared here", objectKind(obj))) } }
// checkInPackageBlock performs safety checks for renames of // func/var/const/type objects in the package block. func (e *Export) checkInPackageBlock(from types.Object, to string) { info := e.u.pkgInfo lexinfo := lexical.Structure(e.u.prog.Fset, from.Pkg(), &info.Info, info.Files) // We don't rename anything in the package block to init, as that might // conflict or otherwise break stuff if to == "init" { e.Conflicting = true return } // Check for conflicts between package block and all file blocks. for _, f := range info.Files { if _, b := lexinfo.Blocks[f].Lookup(to); b == lexinfo.Blocks[f] { e.Conflicting = true return } } if f, ok := from.(*types.Func); ok && recv(f) == nil { e.checkFunction(f, to) if e.Conflicting { return } } // Check for conflicts in lexical scope. // Do not need to check all imported packages: // Since it's unnecessarily exported, no one else is going to be sad if I unexport it! e.checkInLexicalScope(from, to) }
// 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 simpleObjInfo(obj types.Object) string { pkg := obj.Pkg() s := simpleType(obj.String()) if pkg != nil && pkg.Name() == "main" { return strings.Replace(s, simpleType(pkg.Path())+".", "", -1) } return s }
func (v *visitor) decl(obj types.Object) { key := getKey(obj) if _, ok := v.uses[key]; !ok { v.uses[key] = 0 } if _, ok := v.positions[key]; !ok { v.positions[key] = v.prog.Fset.Position(obj.Pos()) } }
// same reports whether x and y are identical, or both are PkgNames // that import the same Package. // func sameObj(x, y types.Object) bool { if x == y { return true } if x, ok := x.(*types.PkgName); ok { if y, ok := y.(*types.PkgName); ok { return x.Imported() == y.Imported() } } return false }
func (r *resolver) defineObject(b *Block, name string, obj types.Object) { if obj.Name() == "_" { return } i := len(b.bindings) b.bindings = append(b.bindings, obj) b.index[name] = i if trace { logf("def %s = %s in %s\n", name, types.ObjectString(obj, r.qualifier), b) } r.result.Defs[obj] = b }
func (e *Export) checkInLexicalScope(from types.Object, to string) { info := e.u.pkgInfo lexinfo := lexical.Structure(e.u.prog.Fset, info.Pkg, &info.Info, info.Files) b := lexinfo.Defs[from] // the block defining the 'from' object if b != nil { to, toBlock := b.Lookup(to) if toBlock == b { e.Conflicting = true return // same-block conflict } else if toBlock != nil { for _, ref := range lexinfo.Refs[to] { if obj, _ := ref.Env.Lookup(from.Name()); obj == from { e.Conflicting = true return // super-block conflict } } } } // Check for sub-block conflict. // Is there an intervening definition of r.to between // the block defining 'from' and some reference to it? for _, ref := range lexinfo.Refs[from] { _, fromBlock := ref.Env.Lookup(from.Name()) fromDepth := fromBlock.Depth() to, toBlock := ref.Env.Lookup(to) if to != nil { // sub-block conflict if toBlock.Depth() > fromDepth { e.Conflicting = true return } } } // 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 info.Uses { if obj == from { if field := info.Defs[id]; field != nil { e.check(field, to) } } } } }
func printObject(u *unexporter.Unexporter, o types.Object) { var objName string if simpleNamesFlag { objName = o.Name() } else { objName = o.String() } if showFilename { pos := u.PositionForObject(o) fmt.Printf("%s:%d:%d: %s\n", pos.Filename, pos.Line, pos.Column, objName) } else { fmt.Println(objName) } }
func (r *Unexporter) checkInLocalScope(objsToUpdate map[types.Object]string, from types.Object, to string) { info := r.packages[from.Pkg()] // Is this object an implicit local var for a type switch? // Each case has its own var, whose position is the decl of y, // but Ident in that decl does not appear in the Uses map. // // switch y := x.(type) { // Defs[Ident(y)] is undefined // case int: print(y) // Implicits[CaseClause(int)] = Var(y_int) // case string: print(y) // Implicits[CaseClause(string)] = Var(y_string) // } // var isCaseVar bool for syntax, obj := range info.Implicits { if _, ok := syntax.(*ast.CaseClause); ok && obj.Pos() == from.Pos() { isCaseVar = true r.check(objsToUpdate, obj, to) } } r.checkInLexicalScope(objsToUpdate, from, to, info) // Finally, if this was a type switch, change the variable y. if isCaseVar { _, path, _ := r.iprog.PathEnclosingInterval(from.Pos(), from.Pos()) path[0].(*ast.Ident).Name = to // path is [Ident AssignStmt TypeSwitchStmt...] } }
func getKey(obj types.Object) object { if obj == nil { return object{} } pkg := obj.Pkg() pkgPath := "" if pkg != nil { pkgPath = pkg.Path() } return object{ pkgPath: pkgPath, name: obj.Name(), } }
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.")) }
// getDoc returns the doc string associated with types.Object func (p *Package) getDoc(o types.Object) string { n := o.Name() switch o.(type) { case *types.Const: for _, c := range p.doc.Consts { for _, cn := range c.Names { if n == cn { return c.Doc } } } case *types.Var: for _, v := range p.doc.Vars { for _, vn := range v.Names { if n == vn { return v.Doc } } } case *types.Func: for _, f := range p.doc.Funcs { if n == f.Name { return f.Doc } } case *types.TypeName: for _, t := range p.doc.Types { if n == t.Name { return t.Doc } } default: // TODO(sbinet) panic(fmt.Errorf("not yet supported: %v (%T)", o, o)) } return "" }
func (c *funcContext) objectName(o types.Object) string { if isPkgLevel(o) { c.p.dependencies[o] = true if o.Pkg() != c.p.Pkg || (isVarOrConst(o) && o.Exported()) { return c.pkgVar(o.Pkg()) + "." + o.Name() } } name, ok := c.p.objectNames[o] if !ok { name = c.newVariableWithLevel(o.Name(), isPkgLevel(o)) c.p.objectNames[o] = name } if v, ok := o.(*types.Var); ok && c.p.escapingVars[v] { return name + "[0]" } return name }
// ssaValueForIdent returns the ssa.Value for the ast.Ident whose path // to the root of the AST is path. isAddr reports whether the // ssa.Value is the address denoted by the ast.Ident, not its value. // func ssaValueForIdent(prog *ssa.Program, qinfo *loader.PackageInfo, obj types.Object, path []ast.Node) (value ssa.Value, isAddr bool, err error) { switch obj := obj.(type) { case *types.Var: pkg := prog.Package(qinfo.Pkg) pkg.Build() if v, addr := prog.VarValue(obj, pkg, path); v != nil { return v, addr, nil } return nil, false, fmt.Errorf("can't locate SSA Value for var %s", obj.Name()) case *types.Func: fn := prog.FuncValue(obj) if fn == nil { return nil, false, fmt.Errorf("%s is an interface method", obj) } // TODO(adonovan): there's no point running PTA on a *Func ident. // Eliminate this feature. return fn, false, nil } panic(obj) }
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(), "\tto 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(), "\tto the %s declared here", objectKind(obj)) } }
func (f *Function) addParamObj(obj types.Object) *Parameter { name := obj.Name() if name == "" { name = fmt.Sprintf("arg%d", len(f.Params)) } param := f.addParam(name, obj.Type(), obj.Pos()) param.object = obj return param }
func checkNilFuncComparison(f *File, node ast.Node) { e := node.(*ast.BinaryExpr) // Only want == or != comparisons. if e.Op != token.EQL && e.Op != token.NEQ { return } // Only want comparisons with a nil identifier on one side. var e2 ast.Expr switch { case f.isNil(e.X): e2 = e.Y case f.isNil(e.Y): e2 = e.X default: return } // Only want identifiers or selector expressions. var obj types.Object switch v := e2.(type) { case *ast.Ident: obj = f.pkg.uses[v] case *ast.SelectorExpr: obj = f.pkg.uses[v.Sel] default: return } // Only want functions. if _, ok := obj.(*types.Func); !ok { return } f.Badf(e.Pos(), "comparison of function %v %v nil is always %v", obj.Name(), e.Op, e.Op == token.NEQ) }
func defKind(obj types.Object) string { switch obj := obj.(type) { case *types.PkgName: return definfo.Package case *types.Const: return definfo.Const case *types.TypeName: return definfo.Type case *types.Var: if obj.IsField() { return definfo.Field } return definfo.Var case *types.Func: sig := obj.Type().(*types.Signature) if sig.Recv() == nil { return definfo.Func } else { return definfo.Method } default: panic(fmt.Sprintf("unhandled obj type %T", obj)) } }
func (r *Unexporter) check(objsToUpdate map[types.Object]string, from types.Object, to string) { if _, ok := objsToUpdate[from]; ok { return } objsToUpdate[from] = to // NB: order of conditions is important. if from_, ok := from.(*types.PkgName); ok { r.checkInFileBlock(objsToUpdate, from_, to) } else if from_, ok := from.(*types.Label); ok { r.checkLabel(from_, to) } else if isPackageLevel(from) { r.checkInPackageBlock(objsToUpdate, from, to) } else if v, ok := from.(*types.Var); ok && v.IsField() { r.checkStructField(objsToUpdate, v, to) } else if f, ok := from.(*types.Func); ok && recv(f) != nil { r.checkMethod(objsToUpdate, f, to) } else if isLocal(from) { r.checkInLocalScope(objsToUpdate, from, to) } else { r.errorf(from.Pos(), "unexpected %s object %q (please report a bug)\n", objectKind(from), from) } }
func (p *printer) printObj(obj types.Object) { p.print(obj.Name()) typ, basic := obj.Type().Underlying().(*types.Basic) if basic && typ.Info()&types.IsUntyped != 0 { // don't write untyped types } else { p.print(" ") p.writeType(p.pkg, obj.Type()) } if obj, ok := obj.(*types.Const); ok { floatFmt := basic && typ.Info()&(types.IsFloat|types.IsComplex) != 0 p.print(" = ") p.print(valString(obj.Val(), floatFmt)) } }
// 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) } }
// addSpilledParam declares a parameter that is pre-spilled to the // stack; the function body will load/store the spilled location. // Subsequent lifting will eliminate spills where possible. // func (f *Function) addSpilledParam(obj types.Object) { param := f.addParamObj(obj) spill := &Alloc{Comment: obj.Name()} spill.setType(types.NewPointer(obj.Type())) spill.setPos(obj.Pos()) f.objects[obj] = spill f.Locals = append(f.Locals, spill) f.emit(spill) f.emit(&Store{Addr: spill, Val: param}) }
func wholePath(obj types.Object, path string, prog *loader.Program) string { if v, ok := obj.(*types.Var); ok && v.IsField() { structName := getDeclareStructOrInterface(prog, v) return fmt.Sprintf("(\"%s\".%s).%s", path, structName, obj.Name()) } else if f, ok := obj.(*types.Func); ok { if r := recv(f); r != nil { return fmt.Sprintf("(\"%s\".%s).%s", r.Pkg().Path(), typeName(r.Type()), obj.Name()) } } return fmt.Sprintf("\"%s\".%s", path, obj.Name()) }
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 this %s %q to %q would make it unexported", objectKind(from), from.Name(), r.to) r.errorf(id.Pos(), "\tbreaking references from packages such as %q", pkg.Path()) return false } return true }
func isExported(obj types.Object) bool { // https://golang.org/ref/spec#Exported_identifiers // An identifier is exported if both: // the first character of the identifier's name is a Unicode upper case letter (Unicode class "Lu"); and // the identifier is declared in the package block or it is a field name or method name. // All other identifiers are not exported. if !obj.Exported() { // does not start with an upper case letter return false } if v, ok := obj.(*types.Var); ok && v.IsField() { // is a field name return true } if sig, ok := obj.Type().(*types.Signature); ok && sig.Recv() != nil { // is a method name return true } // is declared in the package block return obj.Parent() == obj.Pkg().Scope() }
// 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 } } } }