func packageFromInfo(prog *loader.Program, pkgInfo *loader.PackageInfo) *Package { files := map[string]*ast.File{} for _, f := range pkgInfo.Files { files[prog.Fset.File(f.Pos()).Name()] = f } // Ignore (perhaps) "unresolved identifier" errors astPkg, _ := ast.NewPackage(prog.Fset, files, nil, nil) var mode doc.Mode docPkg := doc.New(astPkg, pkgInfo.String(), mode) return NewPackage(prog.Fset, docPkg, pkgInfo.Pkg) }
func findSourceFile(pkg *loader.PackageInfo, typeName string) (*ast.File, *ast.GenDecl, *ast.TypeSpec, error) { for _, file := range pkg.Files { for _, decl := range file.Decls { if gd, ok := decl.(*ast.GenDecl); ok { for _, gs := range gd.Specs { if ts, ok := gs.(*ast.TypeSpec); ok { strfmtNme, isStrfmt := strfmtName(gd.Doc) if (isStrfmt && strfmtNme == typeName) || ts.Name != nil && ts.Name.Name == typeName { return file, gd, ts, nil } } } } } } return nil, nil, nil, fmt.Errorf("unable to find %s in %s", typeName, pkg.String()) }
// 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...) } } } } }
func clearInfoFields(info *loader.PackageInfo) { // TODO(adonovan): opt: save memory by eliminating unneeded scopes/objects. // (Requires go/types change for Go 1.7.) // info.Pkg.Scope().ClearChildren() // Discard the file ASTs and their accumulated type // information to save memory. info.Files = nil info.Defs = make(map[*ast.Ident]types.Object) info.Uses = make(map[*ast.Ident]types.Object) info.Implicits = make(map[ast.Node]types.Object) // Also, disable future collection of wholly unneeded // type information for the package in case there is // more type-checking to do (augmentation). info.Types = nil info.Scopes = nil info.Selections = nil }
// 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 }
func (scp *schemaParser) parseIdentProperty(pkg *loader.PackageInfo, expr *ast.Ident, prop swaggerTypable) error { // find the file this selector points to file, gd, ts, err := findSourceFile(pkg, expr.Name) if err != nil { err := swaggerSchemaForType(expr.Name, prop) if err != nil { return fmt.Errorf("package %s, error is: %v", pkg.String(), err) } return nil } if at, ok := ts.Type.(*ast.ArrayType); ok { // the swagger spec defines strfmt base64 as []byte. // in that case we don't actually want to turn it into an array // but we want to turn it into a string if _, ok := at.Elt.(*ast.Ident); ok { if strfmtName, ok := strfmtName(gd.Doc); ok { prop.Typed("string", strfmtName) return nil } } // this is a selector, so most likely not base64 if strfmtName, ok := strfmtName(gd.Doc); ok { prop.Items().Typed("string", strfmtName) return nil } } // look at doc comments for swagger:strfmt [name] // when found this is the format name, create a schema with that name if strfmtName, ok := strfmtName(gd.Doc); ok { prop.Typed("string", strfmtName) return nil } switch tpe := ts.Type.(type) { case *ast.ArrayType: switch atpe := tpe.Elt.(type) { case *ast.Ident: return scp.parseIdentProperty(pkg, atpe, prop.Items()) case *ast.SelectorExpr: return scp.typeForSelector(file, atpe, prop.Items()) case *ast.StarExpr: return parseProperty(scp, file, atpe.X, prop.Items()) default: return fmt.Errorf("unknown selector type: %#v", atpe) } case *ast.StructType: sd := newSchemaDecl(file, gd, ts) sd.inferNames() ref, err := spec.NewRef("#/definitions/" + sd.Name) if err != nil { return err } prop.SetRef(ref) scp.postDecls = append(scp.postDecls, *sd) return nil case *ast.Ident: return scp.parseIdentProperty(pkg, tpe, prop) case *ast.SelectorExpr: return scp.typeForSelector(file, tpe, prop) case *ast.InterfaceType: sd := newSchemaDecl(file, gd, ts) sd.inferNames() ref, err := spec.NewRef("#/definitions/" + sd.Name) if err != nil { return err } prop.SetRef(ref) scp.postDecls = append(scp.postDecls, *sd) return nil default: err := swaggerSchemaForType(expr.Name, prop) if err != nil { return fmt.Errorf("package %s, error is: %v", pkg.String(), err) } return nil } }
// 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 }
// NewTransformer returns a transformer based on the specified template, // a package containing "before" and "after" functions as described // in the package documentation. // func NewTransformer(fset *token.FileSet, template *loader.PackageInfo, verbose bool) (*Transformer, error) { // Check the template. beforeSig := funcSig(template.Pkg, "before") if beforeSig == nil { return nil, fmt.Errorf("no 'before' func found in template") } afterSig := funcSig(template.Pkg, "after") if afterSig == nil { return nil, fmt.Errorf("no 'after' func found in template") } // TODO(adonovan): should we also check the names of the params match? if !types.Identical(afterSig, beforeSig) { return nil, fmt.Errorf("before %s and after %s functions have different signatures", beforeSig, afterSig) } templateFile := template.Files[0] for _, imp := range templateFile.Imports { if imp.Name != nil && imp.Name.Name == "." { // Dot imports are currently forbidden. We // make the simplifying assumption that all // imports are regular, without local renames. //TODO document return nil, fmt.Errorf("dot-import (of %s) in template", imp.Path.Value) } } var beforeDecl, afterDecl *ast.FuncDecl for _, decl := range templateFile.Decls { if decl, ok := decl.(*ast.FuncDecl); ok { switch decl.Name.Name { case "before": beforeDecl = decl case "after": afterDecl = decl } } } before, err := soleExpr(beforeDecl) if err != nil { return nil, fmt.Errorf("before: %s", err) } after, err := soleExpr(afterDecl) if err != nil { return nil, fmt.Errorf("after: %s", err) } wildcards := make(map[*types.Var]bool) for i := 0; i < beforeSig.Params().Len(); i++ { wildcards[beforeSig.Params().At(i)] = true } // checkExprTypes returns an error if Tb (type of before()) is not // safe to replace with Ta (type of after()). // // Only superficial checks are performed, and they may result in both // false positives and negatives. // // Ideally, we would only require that the replacement be assignable // to the context of a specific pattern occurrence, but the type // checker doesn't record that information and it's complex to deduce. // A Go type cannot capture all the constraints of a given expression // context, which may include the size, constness, signedness, // namedness or constructor of its type, and even the specific value // of the replacement. (Consider the rule that array literal keys // must be unique.) So we cannot hope to prove the safety of a // transformation in general. Tb := template.TypeOf(before) Ta := template.TypeOf(after) if types.AssignableTo(Tb, Ta) { // safe: replacement is assignable to pattern. } else if tuple, ok := Tb.(*types.Tuple); ok && tuple.Len() == 0 { // safe: pattern has void type (must appear in an ExprStmt). } else { return nil, fmt.Errorf("%s is not a safe replacement for %s", Ta, Tb) } tr := &Transformer{ fset: fset, verbose: verbose, wildcards: wildcards, allowWildcards: true, seenInfos: make(map[*types.Info]bool), importedObjs: make(map[types.Object]*ast.SelectorExpr), before: before, after: after, } // Combine type info from the template and input packages, and // type info for the synthesized ASTs too. This saves us // having to book-keep where each ast.Node originated as we // construct the resulting hybrid AST. // // TODO(adonovan): move type utility methods of PackageInfo to // types.Info, or at least into go/types.typeutil. tr.info.Info = types.Info{ Types: make(map[ast.Expr]types.TypeAndValue), Defs: make(map[*ast.Ident]types.Object), Uses: make(map[*ast.Ident]types.Object), Selections: make(map[*ast.SelectorExpr]*types.Selection), } mergeTypeInfo(&tr.info.Info, &template.Info) // Compute set of imported objects required by after(). // TODO reject dot-imports in pattern ast.Inspect(after, func(n ast.Node) bool { if n, ok := n.(*ast.SelectorExpr); ok { if _, ok := tr.info.Selections[n]; !ok { // qualified ident obj := tr.info.Uses[n.Sel] tr.importedObjs[obj] = n return false // prune } } return true // recur }) return tr, nil }