// 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]*LLVMValue, 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 != nil { 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: if pkginfo.ObjectOf(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: // For f in 'interface {f()}', return the interface type, for now. if _, ok := path[1].(*ast.Field); ok { _ = path[2].(*ast.FieldList) // assertion if _, ok := path[3].(*ast.InterfaceType); ok { return path[3:], actionType } } 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 (go/types bug?) // or y in "switch y := x.(type)" (go/types bug?) // or code in a _test.go file that's not part of the package. fmt.Printf("unknown reference %s in %T\n", n, path[1]) return path, actionUnknown } case *ast.StarExpr: if pkginfo.IsType(n) { 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 }
// 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 }