// processAnnotations takes an *ssa.Package and a // *importer.PackageInfo, and processes all of the // llgo source annotations attached to each top-level // function and global variable. func (c *compiler) processAnnotations(u *unit, pkginfo *loader.PackageInfo) { members := make(map[types.Object]llvm.Value, len(u.globals)) for k, v := range u.globals { members[k.(ssa.Member).Object()] = v } applyAttributes := func(attrs []Attribute, idents ...*ast.Ident) { if len(attrs) == 0 { return } for _, ident := range idents { if v := members[pkginfo.ObjectOf(ident)]; !v.IsNil() { for _, attr := range attrs { attr.Apply(v) } } } } for _, f := range pkginfo.Files { for _, decl := range f.Decls { switch decl := decl.(type) { case *ast.FuncDecl: attrs := parseAttributes(decl.Doc) applyAttributes(attrs, decl.Name) case *ast.GenDecl: if decl.Tok != token.VAR { continue } for _, spec := range decl.Specs { varspec := spec.(*ast.ValueSpec) attrs := parseAttributes(decl.Doc) applyAttributes(attrs, varspec.Names...) } } } } }
// findInterestingNode classifies the syntax node denoted by path as one of: // - an expression, part of an expression or a reference to a constant // or variable; // - a type, part of a type, or a reference to a named type; // - a statement, part of a statement, or a label referring to a statement; // - part of a package declaration or import spec. // - none of the above. // and returns the most "interesting" associated node, which may be // the same node, an ancestor or a descendent. // func findInterestingNode(pkginfo *loader.PackageInfo, path []ast.Node) ([]ast.Node, action) { // TODO(adonovan): integrate with go/types/stdlib_test.go and // apply this to every AST node we can find to make sure it // doesn't crash. // TODO(adonovan): audit for ParenExpr safety, esp. since we // traverse up and down. // TODO(adonovan): if the users selects the "." in // "fmt.Fprintf()", they'll get an ambiguous selection error; // we won't even reach here. Can we do better? // TODO(adonovan): describing a field within 'type T struct {...}' // describes the (anonymous) struct type and concludes "no methods". // We should ascend to the enclosing type decl, if any. for len(path) > 0 { switch n := path[0].(type) { case *ast.GenDecl: if len(n.Specs) == 1 { // Descend to sole {Import,Type,Value}Spec child. path = append([]ast.Node{n.Specs[0]}, path...) continue } return path, actionUnknown // uninteresting case *ast.FuncDecl: // Descend to function name. path = append([]ast.Node{n.Name}, path...) continue case *ast.ImportSpec: return path, actionPackage case *ast.ValueSpec: if len(n.Names) == 1 { // Descend to sole Ident child. path = append([]ast.Node{n.Names[0]}, path...) continue } return path, actionUnknown // uninteresting case *ast.TypeSpec: // Descend to type name. path = append([]ast.Node{n.Name}, path...) continue case ast.Stmt: return path, actionStmt case *ast.ArrayType, *ast.StructType, *ast.FuncType, *ast.InterfaceType, *ast.MapType, *ast.ChanType: return path, actionType case *ast.Comment, *ast.CommentGroup, *ast.File, *ast.KeyValueExpr, *ast.CommClause: return path, actionUnknown // uninteresting case *ast.Ellipsis: // Continue to enclosing node. // e.g. [...]T in ArrayType // f(x...) in CallExpr // f(x...T) in FuncType case *ast.Field: // TODO(adonovan): this needs more thought, // since fields can be so many things. if len(n.Names) == 1 { // Descend to sole Ident child. path = append([]ast.Node{n.Names[0]}, path...) continue } // Zero names (e.g. anon field in struct) // or multiple field or param names: // continue to enclosing field list. case *ast.FieldList: // Continue to enclosing node: // {Struct,Func,Interface}Type or FuncDecl. case *ast.BasicLit: if _, ok := path[1].(*ast.ImportSpec); ok { return path[1:], actionPackage } return path, actionExpr case *ast.SelectorExpr: // TODO(adonovan): use Selections info directly. if pkginfo.Uses[n.Sel] == nil { // TODO(adonovan): is this reachable? return path, actionUnknown } // Descend to .Sel child. path = append([]ast.Node{n.Sel}, path...) continue case *ast.Ident: switch pkginfo.ObjectOf(n).(type) { case *types.PkgName: return path, actionPackage case *types.Const: return path, actionExpr case *types.Label: return path, actionStmt case *types.TypeName: return path, actionType case *types.Var: // For x in 'struct {x T}', return struct type, for now. if _, ok := path[1].(*ast.Field); ok { _ = path[2].(*ast.FieldList) // assertion if _, ok := path[3].(*ast.StructType); ok { return path[3:], actionType } } return path, actionExpr case *types.Func: return path, actionExpr case *types.Builtin: // For reference to built-in function, return enclosing call. path = path[1:] // ascend to enclosing function call continue case *types.Nil: return path, actionExpr } // No object. switch path[1].(type) { case *ast.SelectorExpr: // Return enclosing selector expression. return path[1:], actionExpr case *ast.Field: // TODO(adonovan): test this. // e.g. all f in: // struct { f, g int } // interface { f() } // func (f T) method(f, g int) (f, g bool) // // switch path[3].(type) { // case *ast.FuncDecl: // case *ast.StructType: // case *ast.InterfaceType: // } // // return path[1:], actionExpr // // Unclear what to do with these. // Struct.Fields -- field // Interface.Methods -- field // FuncType.{Params.Results} -- actionExpr // FuncDecl.Recv -- actionExpr case *ast.File: // 'package foo' return path, actionPackage case *ast.ImportSpec: // TODO(adonovan): fix: why no package object? go/types bug? return path[1:], actionPackage default: // e.g. blank identifier // or y in "switch y := x.(type)" // or code in a _test.go file that's not part of the package. log.Printf("unknown reference %s in %T\n", n, path[1]) return path, actionUnknown } case *ast.StarExpr: if pkginfo.Types[n].IsType() { return path, actionType } return path, actionExpr case ast.Expr: // All Expr but {BasicLit,Ident,StarExpr} are // "true" expressions that evaluate to a value. return path, actionExpr } // Ascend to parent. path = path[1:] } return nil, actionUnknown // unreachable }
// IdentDoc attempts to get the documentation for a *ast.Ident. func IdentDoc(ctxt *build.Context, id *ast.Ident, info *loader.PackageInfo, prog *loader.Program) (*Doc, error) { // get definition of identifier obj := info.ObjectOf(id) var pos string if p := obj.Pos(); p.IsValid() { pos = prog.Fset.Position(p).String() } pkgPath := "" if obj.Pkg() != nil { pkgPath = obj.Pkg().Path() } // handle packages imported under a different name if p, ok := obj.(*types.PkgName); ok { return PackageDoc(ctxt, prog.Fset, "", p.Imported().Path()) // SRCDIR TODO TODO } _, nodes, _ := prog.PathEnclosingInterval(obj.Pos(), obj.Pos()) if len(nodes) == 0 { // special case - builtins doc, decl := findInBuiltin(obj.Name(), obj, prog) if doc != "" { return &Doc{ Import: "builtin", Name: obj.Name(), Doc: doc, Decl: decl, Pos: pos, }, nil } return nil, fmt.Errorf("No documentation found for %s", obj.Name()) } var doc *Doc for _, node := range nodes { switch node.(type) { case *ast.FuncDecl, *ast.GenDecl, *ast.Field: default: continue } doc = &Doc{ Import: stripVendorFromImportPath(pkgPath), Name: obj.Name(), Decl: formatNode(node, obj, prog), Pos: pos, } break } if doc == nil { // This shouldn't happen return nil, fmt.Errorf("No documentation found for %s", obj.Name()) } for i, node := range nodes { //fmt.Printf("for %s: found %T\n%#v\n", id.Name, node, node) switch n := node.(type) { case *ast.FuncDecl: doc.Doc = n.Doc.Text() return doc, nil case *ast.GenDecl: constValue := "" if c, ok := obj.(*types.Const); ok { constValue = c.Val().ExactString() } if len(n.Specs) > 0 { switch n.Specs[0].(type) { case *ast.TypeSpec: spec := findTypeSpec(n, nodes[i-1].Pos()) if spec.Doc != nil { doc.Doc = spec.Doc.Text() } else if spec.Comment != nil { doc.Doc = spec.Comment.Text() } case *ast.ValueSpec: spec := findVarSpec(n, nodes[i-1].Pos()) if spec.Doc != nil { doc.Doc = spec.Doc.Text() } else if spec.Comment != nil { doc.Doc = spec.Comment.Text() } } } // if we didn't find doc for the spec, check the overall GenDecl if doc.Doc == "" && n.Doc != nil { doc.Doc = n.Doc.Text() } if constValue != "" { doc.Doc += fmt.Sprintf("\nConstant Value: %s", constValue) } return doc, nil case *ast.Field: // check the doc first, if not present, then look for a comment if n.Doc != nil { doc.Doc = n.Doc.Text() return doc, nil } else if n.Comment != nil { doc.Doc = n.Comment.Text() return doc, nil } } } return doc, nil }