Beispiel #1
0
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
}
Beispiel #2
0
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
}
Beispiel #3
0
// 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
}
Beispiel #4
0
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
}
Beispiel #5
0
// 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
}
Beispiel #6
0
// 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
}
Beispiel #7
0
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
}