Exemple #1
0
func grindFunc(ctxt *grinder.Context, pkg *grinder.Package, edit *grinder.EditBuffer, fn *ast.FuncDecl) {
	if fn.Name.Name == "evconst" {
		old := debug
		debug = true
		defer func() { debug = old }()
	}

	if pkg.TypesError != nil {
		// Without scoping information, we can't be sure code moves are okay.
		fmt.Printf("%s: cannot inline gotos without type information\n", fn.Name)
		return
	}

	if fn.Body == nil {
		return
	}
	blocks := block.Build(pkg.FileSet, fn.Body)
	for labelname, gotos := range blocks.Goto {
		target, ok := findTargetBlock(pkg, edit, fn, blocks, labelname)
		if debug {
			println("TARGET", ok, labelname, len(gotos), target.dead, target.short)
		}
		if ok && (len(gotos) == 1 && target.dead || target.short) {
			numReplaced := 0
			for _, g := range gotos {
				code := edit.TextAt(target.comment, target.start) + target.code
				if !objsMatch(pkg, fn, g.Pos(), target.objs, target.start, target.end) {
					if debug {
						println("OBJS DO NOT MATCH")
					}
					// Cannot inline code here; needed identifiers have different meanings.
					continue
				}
				if target.needReturn {
					// NOTE: Should really check to see if function results are shadowed.
					// If we screw up, the code won't compile, so we can put it off.
					code += "; return"
				}
				if target.needGoto != "" {
					code += "; goto " + target.needGoto
				}
				edit.Replace(g.Pos(), g.End(), code)
				numReplaced++
			}
			if numReplaced == len(gotos) {
				if len(gotos) == 1 && target.dead {
					edit.Delete(target.comment, target.end)
				} else {
					edit.DeleteLine(target.start, target.endLabel)
				}
			}
			// The code we move might itself have gotos to inline,
			// and we can't make that change until we get new line
			// number position, so return after each label change.
			if numReplaced > 0 {
				return
			}
		}
	}
}
Exemple #2
0
func hasType(pkg *grinder.Package, fn *ast.FuncDecl, edit *grinder.EditBuffer, x, v ast.Expr) bool {
	// Does x (by itself) default to v's type?
	// Find the scope in which x appears.
	xScope := pkg.Info.Scopes[fn.Type]
	ast.Inspect(fn.Body, func(z ast.Node) bool {
		if z == nil {
			return false
		}
		if x.Pos() < z.Pos() || z.End() <= x.Pos() {
			return false
		}
		scope := pkg.Info.Scopes[z]
		if scope != nil {
			xScope = scope
		}
		return true
	})
	xs := edit.TextAt(x.Pos(), x.End())
	xt, err := types.Eval(pkg.FileSet, pkg.Types, xScope.Pos(), xs)
	if err != nil {
		return false
	}
	vt := pkg.Info.Types[v]
	if types.Identical(xt.Type, vt.Type) {
		return true
	}

	// Might be untyped.
	vb, ok1 := vt.Type.(*types.Basic)
	xb, ok2 := xt.Type.(*types.Basic)
	if ok1 && ok2 {
		switch xb.Kind() {
		case types.UntypedInt:
			return vb.Kind() == types.Int
		case types.UntypedBool:
			return vb.Kind() == types.Bool
		case types.UntypedRune:
			return vb.Kind() == types.Rune
		case types.UntypedFloat:
			return vb.Kind() == types.Float64
		case types.UntypedComplex:
			return vb.Kind() == types.Complex128
		case types.UntypedString:
			return vb.Kind() == types.String
		}
	}
	return false
}
Exemple #3
0
func placeInit(edit *grinder.EditBuffer, start token.Pos, obj *ast.Object, decl *ast.DeclStmt, list []ast.Stmt) ast.Node {
	declPos := -1
	i := 0
	for i < len(list) && edit.End(list[i]) < start {
		if unlabel(list[i]) == decl {
			declPos = i
		}
		i++
	}
	if i >= len(list) {
		panic(fmt.Sprintf("unexpected start position"))
	}
	switch x := unlabel(list[i]).(type) {
	case *ast.AssignStmt:
		if canDeclare(x, obj) {
			return x
		}
	}

	if declPos >= 0 && allSimple(list[declPos:i]) {
		return decl
	}

	for j := i + 1; j < len(list); j++ {
		if unlabel(list[j]) == decl {
			if allSimple(list[i:j]) {
				return decl
			}
			break
		}
	}

	x := list[i]
	for {
		xx, ok := x.(*ast.LabeledStmt)
		if !ok || xx.Stmt.Pos() > start {
			break
		}
		x = xx.Stmt
	}

	return &ast.EmptyStmt{
		Semicolon: x.Pos(),
	}
}
Exemple #4
0
func isNilPtr(pkg *grinder.Package, edit *grinder.EditBuffer, x ast.Expr) (typ string, ok bool) {
	conv, ok := x.(*ast.CallExpr)
	if !ok || len(conv.Args) != 1 {
		return "", false
	}
	id, ok := unparen(conv.Args[0]).(*ast.Ident)
	if !ok || id.Name != "nil" {
		return "", false
	}
	if obj := pkg.Info.Uses[id]; obj == nil || obj.Pkg() != nil {
		return "", false
	}
	fn := unparen(conv.Fun)
	tv, ok := pkg.Info.Types[fn]
	if !ok || !tv.IsType() {
		return "", false
	}
	return edit.TextAt(fn.Pos(), fn.End()), true
}
Exemple #5
0
func isStructOrArrayLiteral(pkg *grinder.Package, edit *grinder.EditBuffer, x ast.Expr) (typ string, ok bool) {
	lit, ok := x.(*ast.CompositeLit)
	if !ok || len(lit.Elts) > 0 {
		return "", false
	}
	tv, ok := pkg.Info.Types[x]
	if !ok {
		return "", false
	}
	t := tv.Type
	if name, ok := t.(*types.Named); ok {
		t = name.Underlying()
	}
	switch t.(type) {
	default:
		return "", false
	case *types.Struct, *types.Array:
		// ok
	}
	return edit.TextAt(lit.Type.Pos(), lit.Type.End()), true
}
Exemple #6
0
func grindFunc(ctxt *grinder.Context, pkg *grinder.Package, edit *grinder.EditBuffer, fn *ast.FuncDecl) {
	if fn.Body == nil {
		return
	}
	blocks := block.Build(pkg.FileSet, fn.Body)
	ast.Inspect(fn.Body, func(x ast.Node) bool {
		var list []ast.Stmt
		switch x := x.(type) {
		default:
			return true
		case *ast.BlockStmt:
			list = x.List
		case *ast.CommClause:
			list = x.Body
		case *ast.CaseClause:
			list = x.Body
		}

		for i := 0; i < len(list); i++ {
			x := list[i]
			if grinder.IsTerminatingStmt(blocks, x) {
				end := i + 1
				for end < len(list) && !isGotoTarget(blocks, list[end]) {
					end++
				}
				if end > i+1 {
					edit.Delete(edit.End(x), edit.End(list[end-1]))
					i = end - 1 // after i++, next iteration starts at end
				}
			}
		}
		return true
	})
}
Exemple #7
0
func initToDecl(ctxt *grinder.Context, pkg *grinder.Package, edit *grinder.EditBuffer, fn *ast.FuncDecl) {
	// Rewrite x := T{} (for struct or array type T) and x := (*T)(nil) to var x T.
	ast.Inspect(fn.Body, func(x ast.Node) bool {
		list := grinder.BlockList(x)
		for _, stmt := range list {
			as, ok := stmt.(*ast.AssignStmt)
			if !ok || len(as.Lhs) > 1 || as.Tok != token.DEFINE {
				continue
			}
			var typ string
			if t, ok := isNilPtr(pkg, edit, as.Rhs[0]); ok {
				typ = t
			} else if t, ok := isStructOrArrayLiteral(pkg, edit, as.Rhs[0]); ok {
				typ = t
			}
			if typ != "" {
				edit.Replace(stmt.Pos(), stmt.End(), "var "+as.Lhs[0].(*ast.Ident).Name+" "+typ)
			}
		}
		return true
	})
}
Exemple #8
0
func grindFunc(ctxt *grinder.Context, pkg *grinder.Package, edit *grinder.EditBuffer, fn *ast.FuncDecl) {
	vars := analyzeFunc(pkg, edit, fn.Body)
	// fmt.Printf("%s", vardecl.PrintVars(conf.Fset, vars))
	for _, v := range vars {
		spec := v.Decl.Decl.(*ast.GenDecl).Specs[0].(*ast.ValueSpec)
		if len(spec.Names) > 1 {
			// TODO: Handle decls with multiple variables
			continue
		}
		if pkg.FileSet.Position(v.Decl.Pos()).Line != pkg.FileSet.Position(v.Decl.End()).Line {
			// Declaration spans line. Maybe not great to move or duplicate?
			continue
		}
		keepDecl := false
		for _, d := range v.Defs {
			if d.Init == v.Decl {
				keepDecl = true
				continue
			}
			switch x := d.Init.(type) {
			default:
				panic("unexpected init")
			case *ast.EmptyStmt:
				edit.CopyLine(v.Decl.Pos(), v.Decl.End(), x.Semicolon)
			case *ast.AssignStmt:
				edit.Insert(x.TokPos, ":")
				if !hasType(pkg, fn, edit, x.Rhs[0], x.Lhs[0]) {
					typ := edit.TextAt(spec.Type.Pos(), spec.Type.End())
					if strings.Contains(typ, " ") || typ == "interface{}" || typ == "struct{}" || strings.HasPrefix(typ, "*") {
						typ = "(" + typ + ")"
					}
					edit.Insert(x.Rhs[0].Pos(), typ+"(")
					edit.Insert(x.Rhs[0].End(), ")")
				}
			}
		}
		if !keepDecl {
			edit.DeleteLine(v.Decl.Pos(), v.Decl.End())
		}
	}

	if edit.NumEdits() == 0 {
		initToDecl(ctxt, pkg, edit, fn)
	}
}
Exemple #9
0
func findTargetBlock(pkg *grinder.Package, edit *grinder.EditBuffer, fn *ast.FuncDecl, blocks *block.Graph, labelname string) (target targetBlock, ok bool) {
	if debug {
		println("FINDTARGET", labelname)
	}
	lstmt := blocks.Label[labelname]
	if lstmt == nil {
		return
	}

	list := grinder.BlockList(blocks.Map[lstmt].Root)
	if list == nil {
		return
	}

	ulstmt := grinder.Unlabel(lstmt)
	for i := 0; i < len(list); i++ {
		if grinder.Unlabel(list[i]) == ulstmt {
			// Found statement. Find extent of block.
			if debug {
				println("FOUND")
			}
			end := i
			for ; ; end++ {
				if end >= len(list) {
					if debug {
						println("EARLY END")
					}
					// List ended without terminating statement.
					// Unless this is the top-most block, we can't hoist this code.
					if blocks.Map[lstmt].Root != fn.Body {
						return
					}
					// Top-most block. Implicit return at end of list.
					target.needReturn = true
					break
				}
				if end > i && grinder.IsGotoTarget(blocks, list[end]) {
					if debug {
						println("FOUND TARGET")
					}
					target.needGoto = list[end].(*ast.LabeledStmt).Label.Name
					break
				}
				if grinder.IsTerminatingStmt(blocks, list[end]) {
					if debug {
						println("TERMINATING")
					}
					end++
					break
				}
			}
			if end <= i {
				if debug {
					println("NOTHING")
				}
				return
			}
			if debug {
				println("OK")
			}
			target.dead = i > 0 && grinder.IsTerminatingStmt(blocks, list[i-1])
			target.start = lstmt.Pos()
			target.comment = edit.BeforeComments(target.start)
			target.endLabel = lstmt.Colon + 1
			target.end = edit.End(list[end-1])
			target.code = strings.TrimSpace(edit.TextAt(lstmt.Colon+1, target.end))
			target.short = end == i+1 && (isReturn(grinder.Unlabel(list[i])) || isEmpty(grinder.Unlabel(list[i])) && target.needReturn)
			target.objs = gatherObjs(pkg, fn, lstmt.Pos(), list[i:end])
			return target, true
		}
	}
	return
}