// 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 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() }
func (g *Grapher) path(obj types.Object) (path []string) { if path, present := g.paths[obj]; present { return path } var scope *types.Scope pkgInfo, astPath, _ := g.program.PathEnclosingInterval(obj.Pos(), obj.Pos()) if astPath != nil { for _, node := range astPath { if s, hasScope := pkgInfo.Scopes[node]; hasScope { scope = s } } } if scope == nil { scope = obj.Parent() } if scope == nil { // TODO(sqs): make this actually handle cases like the one described in // https://github.com/sourcegraph/sourcegraph.com/issues/218 log.Printf("Warning: no scope for object %s at pos %s", obj.String(), g.program.Fset.Position(obj.Pos())) return nil } prefix, hasPath := g.scopePaths[scope] if !hasPath { panic("no scope path for scope " + scope.String()) } path = append([]string{}, prefix...) p := g.program.Fset.Position(obj.Pos()) path = append(path, obj.Name()+uniqID(p)) return path panic("no scope node for object " + obj.String()) }
func isPkgLevel(o types.Object) bool { return o.Parent() != nil && o.Parent().Parent() == types.Universe }
// getDoc returns the doc string associated with types.Object // parent is the name of the containing scope ("" for global scope) func (p *Package) getDoc(parent string, 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: doc := func() string { if o.Parent() == nil || (o.Parent() != nil && parent != "") { for _, typ := range p.doc.Types { if typ.Name != parent { continue } if o.Parent() == nil { for _, m := range typ.Methods { if m.Name == n { return m.Doc } } } else { for _, m := range typ.Funcs { if m.Name == n { return m.Doc } } } } } else { for _, f := range p.doc.Funcs { if n == f.Name { return f.Doc } } } return "" }() sig := o.Type().(*types.Signature) parseFn := func(tup *types.Tuple) []string { params := []string{} if tup == nil { return params } for i := 0; i < tup.Len(); i++ { paramVar := tup.At(i) paramType := p.syms.symtype(paramVar.Type()).pysig if paramVar.Name() != "" { paramType = fmt.Sprintf("%s %s", paramType, paramVar.Name()) } params = append(params, paramType) } return params } params := parseFn(sig.Params()) results := parseFn(sig.Results()) paramString := strings.Join(params, ", ") resultString := strings.Join(results, ", ") //FIXME(sbinet): add receiver for methods? docSig := fmt.Sprintf("%s(%s) %s", o.Name(), paramString, resultString) if doc != "" { doc = fmt.Sprintf("%s\n\n%s", docSig, doc) } else { doc = docSig } return 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 "" }
// 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, info *loader.PackageInfo) { 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(info, 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(info, 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 info.Uses { if obj == from { if field := info.Defs[id]; field != nil { r.check(field) } } } } }