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 } } } }
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 }
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(), } }
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 }
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 }
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 }) }
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 }) }
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) } }
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 }