func IncDecStmt(stmt ast.Stmt, info *types.Info) ast.Stmt { if s, ok := stmt.(*ast.IncDecStmt); ok { t := info.TypeOf(s.X) if iExpr, isIExpr := s.X.(*ast.IndexExpr); isIExpr { switch u := info.TypeOf(iExpr.X).Underlying().(type) { case *types.Array: t = u.Elem() case *types.Slice: t = u.Elem() case *types.Map: t = u.Elem() } } tok := token.ADD_ASSIGN if s.Tok == token.DEC { tok = token.SUB_ASSIGN } one := &ast.BasicLit{Kind: token.INT} info.Types[one] = types.TypeAndValue{Type: t, Value: constant.MakeInt64(1)} return &ast.AssignStmt{ Lhs: []ast.Expr{s.X}, Tok: tok, Rhs: []ast.Expr{one}, } } return stmt }
func (b *builder) extractString(f *ast.File, info *types.Info, fset *token.FileSet) error { ast.Inspect(f, func(n ast.Node) bool { switch x := n.(type) { case *ast.CallExpr: if len(x.Args) > 0 { tc := info.TypeOf(x.Fun) if tc.String() == "github.com/nicksnyder/go-i18n/i18n.TranslateFunc" { str := x.Args[0].(*ast.BasicLit) b.str = append(b.str, str.Value[1:len(str.Value)-1]) //fmt.Printf("got string %s\n", str.Value[1:len(str.Value)-1]) } } } return true }) return nil }
// funcHasSingleReturnVal returns true if func called by e has a // single return value (and false if it has multiple return values). func funcHasSingleReturnVal(typeInfo *types.Info, e *ast.CallExpr) bool { // quick local pass if id, ok := e.Fun.(*ast.Ident); ok && id.Obj != nil { if fn, ok := id.Obj.Decl.(*ast.FuncDecl); ok { return len(fn.Type.Results.List) == 1 } } if typeInfo != nil { // look up in type info typ := typeInfo.TypeOf(e) if _, ok := typ.(*types.Tuple); ok { return false } return true } // conservatively return false if we don't have type info return false }
func (c *Suggester) analyzePackage(importer types.Importer, filename string, data []byte, cursor int) (*token.FileSet, token.Pos, *types.Package) { // If we're in trailing white space at the end of a scope, // sometimes go/types doesn't recognize that variables should // still be in scope there. filesemi := bytes.Join([][]byte{data[:cursor], []byte(";"), data[cursor:]}, nil) fset := token.NewFileSet() fileAST, err := parser.ParseFile(fset, filename, filesemi, parser.AllErrors) if err != nil && c.debug { logParseError("Error parsing input file (outer block)", err) } pos := fset.File(fileAST.Pos()).Pos(cursor) var otherASTs []*ast.File for _, otherName := range c.findOtherPackageFiles(filename, fileAST.Name.Name) { ast, err := parser.ParseFile(fset, otherName, nil, 0) if err != nil && c.debug { logParseError("Error parsing other file", err) } otherASTs = append(otherASTs, ast) } var cfg types.Config cfg.Importer = importer cfg.Error = func(err error) {} var info types.Info info.Scopes = make(map[ast.Node]*types.Scope) pkg, _ := cfg.Check("", fset, append(otherASTs, fileAST), &info) // Workaround golang.org/issue/15686. for node, scope := range info.Scopes { switch node := node.(type) { case *ast.RangeStmt: for _, name := range scope.Names() { setScopePos(scope.Lookup(name).(*types.Var), node.X.End()) } } } return fset, pos, pkg }
// NewTransformer returns a transformer based on the specified template, // a single-file package containing "before" and "after" functions as // described in the package documentation. // tmplInfo is the type information for tmplFile. // func NewTransformer(fset *token.FileSet, tmplPkg *types.Package, tmplFile *ast.File, tmplInfo *types.Info, verbose bool) (*Transformer, error) { // Check the template. beforeSig := funcSig(tmplPkg, "before") if beforeSig == nil { return nil, fmt.Errorf("no 'before' func found in template") } afterSig := funcSig(tmplPkg, "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) } for _, imp := range tmplFile.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(adonovan): document return nil, fmt.Errorf("dot-import (of %s) in template", imp.Path.Value) } } var beforeDecl, afterDecl *ast.FuncDecl for _, decl := range tmplFile.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 := tmplInfo.TypeOf(before) Ta := tmplInfo.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. tr.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, tmplInfo) // Compute set of imported objects required by after(). // TODO(adonovan): 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 }
// NewPackage returns a new Package struct, which can be // used to generate code related to the package. The package // might be given as either an absolute path or an import path. // If the package can't be found or the package is not compilable, // this function returns an error. func NewPackage(path string) (*Package, error) { p := &_package{Path: path, fset: token.NewFileSet()} pkg, err := findPackage(path) if err != nil { return nil, fmt.Errorf("could not find package: %s", err) } fileNames := packageFiles(pkg) if len(fileNames) == 0 { return nil, fmt.Errorf("no go files") } p.astFiles = make([]*ast.File, len(fileNames)) p.files = make(map[string]*file, len(fileNames)) for ii, v := range fileNames { f, err := parseFile(p.fset, v) if err != nil { return nil, fmt.Errorf("could not parse %s: %s", v, err) } p.files[v] = f p.astFiles[ii] = f.ast } context := &types.Config{ IgnoreFuncBodies: true, FakeImportC: true, Error: errorHandler, } ipath := pkg.ImportPath if ipath == "." { // Check won't accept a "." import abs, err := filepath.Abs(pkg.Dir) if err != nil { return nil, err } for _, v := range strings.Split(build.Default.GOPATH, ":") { src := filepath.Join(v, "src") if strings.HasPrefix(abs, src) { ipath = abs[len(src)+1:] break } } } var info types.Info info.Types = make(map[ast.Expr]types.TypeAndValue) info.Defs = make(map[*ast.Ident]types.Object) info.Uses = make(map[*ast.Ident]types.Object) info.Implicits = make(map[ast.Node]types.Object) info.Selections = make(map[*ast.SelectorExpr]*types.Selection) info.Scopes = make(map[ast.Node]*types.Scope) tpkg, err := context.Check(ipath, p.fset, p.astFiles, &info) if err != nil { // This error is caused by using fields in C structs, ignore it if !strings.Contains(err.Error(), "invalid type") { return nil, fmt.Errorf("error checking package: %s", err) } } return &Package{ Package: tpkg, dir: pkg.Dir, pkg: p, info: &info, }, nil }
func Assign(stmt ast.Stmt, info *types.Info, pkg *types.Package) ast.Stmt { if s, ok := stmt.(*ast.AssignStmt); ok && s.Tok != token.ASSIGN && s.Tok != token.DEFINE { var op token.Token switch s.Tok { case token.ADD_ASSIGN: op = token.ADD case token.SUB_ASSIGN: op = token.SUB case token.MUL_ASSIGN: op = token.MUL case token.QUO_ASSIGN: op = token.QUO case token.REM_ASSIGN: op = token.REM case token.AND_ASSIGN: op = token.AND case token.OR_ASSIGN: op = token.OR case token.XOR_ASSIGN: op = token.XOR case token.SHL_ASSIGN: op = token.SHL case token.SHR_ASSIGN: op = token.SHR case token.AND_NOT_ASSIGN: op = token.AND_NOT default: panic(s.Tok) } var list []ast.Stmt var viaTmpVars func(expr ast.Expr, name string) ast.Expr viaTmpVars = func(expr ast.Expr, name string) ast.Expr { switch e := astutil.RemoveParens(expr).(type) { case *ast.IndexExpr: return astutil.SetType(info, info.TypeOf(e), &ast.IndexExpr{ X: viaTmpVars(e.X, "_slice"), Index: viaTmpVars(e.Index, "_index"), }) case *ast.SelectorExpr: sel, ok := info.Selections[e] if !ok { // qualified identifier return e } newSel := &ast.SelectorExpr{ X: viaTmpVars(e.X, "_struct"), Sel: e.Sel, } info.Selections[newSel] = sel return astutil.SetType(info, info.TypeOf(e), newSel) case *ast.StarExpr: return astutil.SetType(info, info.TypeOf(e), &ast.StarExpr{ X: viaTmpVars(e.X, "_ptr"), }) case *ast.Ident, *ast.BasicLit: return e default: tmpVar := astutil.NewIdent(name, info.TypeOf(e), info, pkg) list = append(list, &ast.AssignStmt{ Lhs: []ast.Expr{tmpVar}, Tok: token.DEFINE, Rhs: []ast.Expr{e}, }) return tmpVar } } lhs := viaTmpVars(s.Lhs[0], "_val") list = append(list, &ast.AssignStmt{ Lhs: []ast.Expr{lhs}, Tok: token.ASSIGN, Rhs: []ast.Expr{ astutil.SetType(info, info.TypeOf(s.Lhs[0]), &ast.BinaryExpr{ X: lhs, Op: op, Y: astutil.SetType(info, info.TypeOf(s.Rhs[0]), &ast.ParenExpr{ X: s.Rhs[0], }), }), }, }) return &ast.BlockStmt{ List: list, } } return stmt }