// Sets multiLine to true if the function body spans multiple lines. func (p *printer) funcBody(b *ast.BlockStmt, headerSize int, isLit bool, multiLine *bool) { if b == nil { return } p.nesting++ defer func() { p.nesting-- }() if p.isOneLineFunc(b, headerSize) { sep := vtab if isLit { sep = blank } p.print(sep, b.Pos(), token.LBRACE) if len(b.List) > 0 { p.print(blank) for i, s := range b.List { if i > 0 { p.print(token.SEMICOLON, blank) } p.stmt(s, i == len(b.List)-1, ignoreMultiLine) } p.print(blank) } p.print(b.Rbrace, token.RBRACE) return } p.print(blank) p.block(b, 1) *multiLine = true }
func rewriteFnWithRecovers(body *ast.BlockStmt, fnType *ast.FuncType) (wrapped *ast.FuncLit) { // The formatting of the channel declaration is ugly, but it's presented this way here to show how it will look in the actual output. // As far as I know, I would need to set the token.Pos values for the left and right braces of the struct and interface type literals // in order to get them on one line, but I don't think I can do that without computing all of the other token.Pos values for everything // else I generate. // TODO: These identifiers will probably conflict if there is a nested function that also has unnamed outputs. Should probably make a better gensym. outputDecls, outputs := inputsOrOutputs(fnType.Results, idents.result) if len(outputs) > 0 { body.List = astPrintf(`%s = func() (%s) {%s}()`, outputs, fnType.Results, body.List) } body.List = astPrintf(` {{%s}} _r := make(chan chan interface { }) recovers, panicChan := godebug.EnterFuncWithRecovers(_r, func(ctx *godebug.Context) { %s }) for recoverChan := range recovers { recoverChan <- recover() } if panicVal, ok := <-panicChan; ok { panic(panicVal) } {{return %s}}`, outputDecls, body.List, outputs) body.Rbrace = token.NoPos // without this I was getting extra whitespace at the end of the function return wrapped }
// labels checks correct label use in body. func (check *Checker) labels(body *ast.BlockStmt) { // set of all labels in this body all := NewScope(nil, body.Pos(), body.End(), "label") fwdJumps := check.blockBranches(all, nil, nil, body.List) // If there are any forward jumps left, no label was found for // the corresponding goto statements. Either those labels were // never defined, or they are inside blocks and not reachable // for the respective gotos. for _, jmp := range fwdJumps { var msg string name := jmp.Label.Name if alt := all.Lookup(name); alt != nil { msg = "goto %s jumps into block" alt.(*Label).used = true // avoid another error } else { msg = "label %s not declared" } check.errorf(jmp.Label.Pos(), msg, name) } // spec: "It is illegal to define a label that is never used." for _, obj := range all.elems { if lbl := obj.(*Label); !lbl.used { check.softErrorf(lbl.pos, "label %s declared but not used", lbl.name) } } }
func (p *parser) parseBlock(n *parse.Node, scope *ast.Scope) *ast.BlockStmt { block := ast.BlockStmt{ Lbrace: token.Pos(n.Child(0).Pos()), Rbrace: token.Pos(n.LastChild().Pos()), } eachListItem(stmt, n.Child(1), func(item *parse.Node) { block.List = append(block.List, p.parseStmt(item)) }) return &block }
func (c *compiler) createBlockMetadata(stmt *ast.BlockStmt) llvm.DebugDescriptor { uniqueId++ file := c.fileset.File(stmt.Pos()) fd := llvm.FileDescriptor(file.Name()) return &llvm.BlockDescriptor{ File: &fd, Line: uint32(file.Line(stmt.Pos())), Context: c.currentDebugContext(), Id: uniqueId, } }
// block prints an *ast.BlockStmt; it always spans at least two lines. func (p *printer) block(s *ast.BlockStmt, indent int, moveComments bool) { if moveComments { p.print(p.beforeComment(s.Pos())) } else { p.print(s.Pos()) } p.print(token.LBRACE) p.stmtList(s.List, indent) p.linebreak(s.Rbrace.Line, 1, maxStmtNewlines, ignore, true) p.print(s.Rbrace, token.RBRACE) }
// lastComment returns the last comment inside the provided block. func lastComment(b *ast.BlockStmt, c []*ast.CommentGroup) (i int, last *ast.CommentGroup) { pos, end := b.Pos(), b.End() for j, cg := range c { if cg.Pos() < pos { continue } if cg.End() > end { break } i, last = j, cg } return }
func (v *visitor) finalizeLoop(pos token.Pos, body *ast.BlockStmt) { if body == nil { return } line := pos2line(pos) if len(v.newIdents) == 0 { call := newCall(idents.godebug, "Line", ast.NewIdent(idents.ctx), ast.NewIdent(v.scopeVar), newInt(line)) body.List = append(body.List, &ast.ExprStmt{X: call}) } else { body.List = append([]ast.Stmt{ astPrintf(`godebug.Line(ctx, scope, %s)`, strconv.Itoa(line))[0], newDeclareCall(idents.scope, v.newIdents), }, body.List...) } }
func (lp *linePrinter) trimBlock(stmt *ast.BlockStmt) *ast.BlockStmt { if !lp.trim(stmt) { return lp.emptyBlock(stmt) } stmt.Rbrace = stmt.Lbrace return stmt }
// New returns a new control-flow graph for the specified function body, // which must be non-nil. // // The CFG builder calls mayReturn to determine whether a given function // call may return. For example, calls to panic, os.Exit, and log.Fatal // do not return, so the builder can remove infeasible graph edges // following such calls. The builder calls mayReturn only for a // CallExpr beneath an ExprStmt. func New(body *ast.BlockStmt, mayReturn func(*ast.CallExpr) bool) *CFG { b := builder{ mayReturn: mayReturn, cfg: new(CFG), } b.current = b.newBlock("entry") b.stmt(body) // Does control fall off the end of the function's body? // Make implicit return explicit. if b.current != nil && !b.current.unreachable { b.add(&ast.ReturnStmt{ Return: body.End() - 1, }) } return b.cfg }
func (check *Checker) funcBody(decl *declInfo, name string, sig *Signature, body *ast.BlockStmt) { if trace { if name == "" { name = "<function literal>" } fmt.Printf("--- %s: %s {\n", name, sig) defer fmt.Println("--- <end>") } // set function scope extent sig.scope.pos = body.Pos() sig.scope.end = body.End() // save/restore current context and setup function context // (and use 0 indentation at function start) defer func(ctxt context, indent int) { check.context = ctxt check.indent = indent }(check.context, check.indent) check.context = context{ decl: decl, scope: sig.scope, sig: sig, } check.indent = 0 check.stmtList(0, body.List) if check.hasLabel { check.labels(body) } if sig.results.Len() > 0 && !check.isTerminating(body, "") { check.error(body.Rbrace, "missing return") } // spec: "Implementation restriction: A compiler may make it illegal to // declare a variable inside a function body if the variable is never used." // (One could check each scope after use, but that distributes this check // over several places because CloseScope is not always called explicitly.) check.usage(sig.scope) }
// filterBlocks keeps the statements that are need to calculate MappedCells. func filterBlock(blk *ast.BlockStmt, filterIDs map[string]bool, imports map[string]string) (*ast.BlockStmt, []dictKey) { dicts := dictionaries(blk, imports) usedDks := make(map[dictKey]bool) filtered := make([]ast.Stmt, 0, len(blk.List)) for i := len(blk.List) - 1; i >= 0; i-- { switch s := blk.List[i].(type) { // TODO(soheil): Support switches. case *ast.SwitchStmt: // TODO(soheil): Add support for ifs. case *ast.IfStmt: default: // TODO(soheil): It's actually more complicated that. What about // functional calls, what about multiple return values, ...? dks, yes := accessesDict(s, dicts) if yes { for _, dk := range dks { filterIDs[dk.k] = true usedDks[dk] = true } continue } dirty := false rIDs, wIDs := ids(s) for _, id := range wIDs { if filterIDs[id] { dirty = true break } } if !dirty { continue } for _, id := range rIDs { filterIDs[id] = true } filtered = append([]ast.Stmt{s}, filtered...) } } blk.List = filtered keys := make([]dictKey, 0, len(usedDks)) for dk, _ := range usedDks { keys = append(keys, dk) } return blk, keys }
// bodySize is like nodeSize but it is specialized for *ast.BlockStmt's. func (p *printer) bodySize(b *ast.BlockStmt, maxSize int) int { pos1 := b.Pos() pos2 := b.Rbrace if pos1.IsValid() && pos2.IsValid() && p.lineFor(pos1) != p.lineFor(pos2) { // opening and closing brace are on different lines - don't make it a one-liner return maxSize + 1 } if len(b.List) > 5 || p.commentBefore(p.posFor(pos2)) { // too many statements or there is a comment inside - don't make it a one-liner return maxSize + 1 } // otherwise, estimate body size bodySize := 0 for i, s := range b.List { if i > 0 { bodySize += 2 // space for a semicolon and blank } bodySize += p.nodeSize(s, maxSize) } return bodySize }
/* * Given a test func named TestDoesSomethingNeat, rewrites it as * It("does something neat", func() { __test_body_here__ }) and adds it * to the Describe's list of statements */ func rewriteTestFuncAsItStatement(testFunc *ast.FuncDecl, rootNode *ast.File, describe *ast.CallExpr) { var funcIndex int = -1 for index, child := range rootNode.Decls { if child == testFunc { funcIndex = index break } } if funcIndex < 0 { panic(fmt.Sprintf("Assert failed: Error finding index for test node %s\n", testFunc.Name.Name)) } var block *ast.BlockStmt = blockStatementFromDescribe(describe) block.List = append(block.List, createItStatementForTestFunc(testFunc)) replaceTestingTsWithGinkgoT(block, namedTestingTArg(testFunc)) // remove the old test func from the root node's declarations rootNode.Decls = append(rootNode.Decls[:funcIndex], rootNode.Decls[funcIndex+1:]...) return }
func (p *printer) isOneLineFunc(b *ast.BlockStmt, headerSize int) bool { pos1 := b.Pos() pos2 := b.Rbrace if pos1.IsValid() && pos2.IsValid() && pos1.Line != pos2.Line { // opening and closing brace are on different lines - don't make it a one-liner return false } if len(b.List) > 5 || p.commentBefore(pos2) { // too many statements or there is a comment inside - don't make it a one-liner return false } // otherwise, estimate body size const maxSize = 100 bodySize := 0 for i, s := range b.List { if i > 0 { bodySize += 2 // space for a semicolon and blank } bodySize += p.nodeSize(s, maxSize) } return headerSize+bodySize <= maxSize }
func (c *compiler) VisitBlockStmt(stmt *ast.BlockStmt, createNewBlock bool) { c.pushDebugContext(c.createBlockMetadata(stmt)) defer c.popDebugContext() c.setDebugLine(stmt.Pos()) // This is a little awkward, but it makes dealing with branching easier. // A free-standing block statement (i.e. one not attached to a control // statement) will splice in a new block. var doneBlock llvm.BasicBlock if createNewBlock { currBlock := c.builder.GetInsertBlock() doneBlock = llvm.InsertBasicBlock(currBlock, "") doneBlock.MoveAfter(currBlock) newBlock := llvm.InsertBasicBlock(doneBlock, "") c.builder.CreateBr(newBlock) c.builder.SetInsertPointAtEnd(newBlock) } // Visit each statement in the block. When we have a terminator, // ignore everything until we get to a labeled statement. for _, stmt := range stmt.List { currBlock := c.builder.GetInsertBlock() in := currBlock.LastInstruction() if in.IsNil() || in.IsATerminatorInst().IsNil() { c.VisitStmt(stmt) } else if _, ok := stmt.(*ast.LabeledStmt); ok { // FIXME we might end up with a labeled statement // with no predecessors, due to dead code elimination. c.VisitStmt(stmt) } } if createNewBlock { c.maybeImplicitBranch(doneBlock) c.builder.SetInsertPointAtEnd(doneBlock) } }
// Sets multiLine to true if the function body spans multiple lines. func (p *printer) funcBody(b *ast.BlockStmt, isLit bool, multiLine *bool) { if b == nil { return } if (oneLineFuncDecls || isLit) && p.isOneLiner(b) { sep := vtab if isLit { sep = blank } if len(b.List) > 0 { p.print(sep, b.Pos(), token.LBRACE, blank) p.stmt(b.List[0], ignoreMultiLine) p.print(blank, b.Rbrace, token.RBRACE) } else { p.print(sep, b.Pos(), token.LBRACE, b.Rbrace, token.RBRACE) } return } p.print(blank) p.block(b, 1) *multiLine = true }
func rewriteStructLiteralAsIdentifierAtTopOfBlock(newFile *ast.File, literalToReplace *ast.CompositeLit, name string) { var ( foundStmtNode ast.Stmt blocksSeen []*ast.BlockStmt stmtsSeen []ast.Stmt deleteOriginalStatement bool newAssignStmtToken = token.DEFINE ) ast.Inspect(newFile, func(node ast.Node) bool { switch node := node.(type) { case *ast.BlockStmt: if foundStmtNode == nil { blocksSeen = append(blocksSeen, node) } case ast.Stmt: stmtsSeen = append(stmtsSeen, node) } return true }) var block *ast.BlockStmt var insertionIndex int for i := len(blocksSeen) - 1; i >= 0; i-- { b := blocksSeen[i] for j, stmt := range b.List { ast.Inspect(stmt, func(parentNode ast.Node) bool { switch parentNode := parentNode.(type) { case *ast.CompositeLit: if parentNode == literalToReplace { block = b insertionIndex = j return false } } return true }) } } for i := len(stmtsSeen) - 1; i >= 0; i-- { s := stmtsSeen[i] ast.Inspect(s, func(node ast.Node) bool { switch node := node.(type) { case *ast.CompositeLit: if node == literalToReplace { foundStmtNode = s return false } } return true }) } switch foundStmtNode := foundStmtNode.(type) { case *ast.AssignStmt: expr := foundStmtNode.Rhs[0] if expr == literalToReplace { deleteOriginalStatement = true switch ident := foundStmtNode.Lhs[0].(type) { case *ast.Ident: name = ident.Name } if foundStmtNode.Tok == token.ASSIGN { newAssignStmtToken = token.ASSIGN } } } lhsExpr := []ast.Expr{ast.NewIdent(name)} rhsExpr := []ast.Expr{&ast.CompositeLit{Type: literalToReplace.Type}} block.List = insert(block.List, insertionIndex, ast.AssignStmt{ Lhs: lhsExpr, Rhs: rhsExpr, Tok: newAssignStmtToken, }) insertionIndex++ for _, elt := range literalToReplace.Elts { keyVal := elt.(*ast.KeyValueExpr) fieldName := keyVal.Key.(*ast.Ident) selector := &ast.SelectorExpr{ X: ast.NewIdent(name), Sel: ast.NewIdent(fieldName.Name), } innerLhs := []ast.Expr{selector} innerRhs := []ast.Expr{keyVal.Value} block.List = insert(block.List, insertionIndex, ast.AssignStmt{ Lhs: innerLhs, Rhs: innerRhs, Tok: token.ASSIGN, }) insertionIndex++ } if deleteOriginalStatement { copy(block.List[insertionIndex:], block.List[insertionIndex+1:]) block.List[len(block.List)-1] = &ast.EmptyStmt{} block.List = block.List[:len(block.List)-1] } else { replaceStructLiteralWithIdentifier(foundStmtNode, literalToReplace, name) } }
// playExample synthesizes a new *ast.File based on the provided // file with the provided function body as the body of main. func playExample(file *ast.File, body *ast.BlockStmt) *ast.File { if !strings.HasSuffix(file.Name.Name, "_test") { // We don't support examples that are part of the // greater package (yet). return nil } // Find top-level declarations in the file. topDecls := make(map[*ast.Object]bool) for _, decl := range file.Decls { switch d := decl.(type) { case *ast.FuncDecl: topDecls[d.Name.Obj] = true case *ast.GenDecl: for _, spec := range d.Specs { switch s := spec.(type) { case *ast.TypeSpec: topDecls[s.Name.Obj] = true case *ast.ValueSpec: for _, id := range s.Names { topDecls[id.Obj] = true } } } } } // Find unresolved identifiers and uses of top-level declarations. unresolved := make(map[string]bool) usesTopDecl := false var inspectFunc func(ast.Node) bool inspectFunc = func(n ast.Node) bool { // For selector expressions, only inspect the left hand side. // (For an expression like fmt.Println, only add "fmt" to the // set of unresolved names, not "Println".) if e, ok := n.(*ast.SelectorExpr); ok { ast.Inspect(e.X, inspectFunc) return false } // For key value expressions, only inspect the value // as the key should be resolved by the type of the // composite literal. if e, ok := n.(*ast.KeyValueExpr); ok { ast.Inspect(e.Value, inspectFunc) return false } if id, ok := n.(*ast.Ident); ok { if id.Obj == nil { unresolved[id.Name] = true } else if topDecls[id.Obj] { usesTopDecl = true } } return true } ast.Inspect(body, inspectFunc) if usesTopDecl { // We don't support examples that are not self-contained (yet). return nil } // Remove predeclared identifiers from unresolved list. for n := range unresolved { if predeclaredTypes[n] || predeclaredConstants[n] || predeclaredFuncs[n] { delete(unresolved, n) } } // Use unresolved identifiers to determine the imports used by this // example. The heuristic assumes package names match base import // paths for imports w/o renames (should be good enough most of the time). namedImports := make(map[string]string) // [name]path var blankImports []ast.Spec // _ imports for _, s := range file.Imports { p, err := strconv.Unquote(s.Path.Value) if err != nil { continue } n := path.Base(p) if s.Name != nil { n = s.Name.Name switch n { case "_": blankImports = append(blankImports, s) continue case ".": // We can't resolve dot imports (yet). return nil } } if unresolved[n] { namedImports[n] = p delete(unresolved, n) } } // If there are other unresolved identifiers, give up because this // synthesized file is not going to build. if len(unresolved) > 0 { return nil } // Include documentation belonging to blank imports. var comments []*ast.CommentGroup for _, s := range blankImports { if c := s.(*ast.ImportSpec).Doc; c != nil { comments = append(comments, c) } } // Include comments that are inside the function body. for _, c := range file.Comments { if body.Pos() <= c.Pos() && c.End() <= body.End() { comments = append(comments, c) } } // Strip "Output:" commment and adjust body end position. body, comments = stripOutputComment(body, comments) // Synthesize import declaration. importDecl := &ast.GenDecl{ Tok: token.IMPORT, Lparen: 1, // Need non-zero Lparen and Rparen so that printer Rparen: 1, // treats this as a factored import. } for n, p := range namedImports { s := &ast.ImportSpec{Path: &ast.BasicLit{Value: strconv.Quote(p)}} if path.Base(p) != n { s.Name = ast.NewIdent(n) } importDecl.Specs = append(importDecl.Specs, s) } importDecl.Specs = append(importDecl.Specs, blankImports...) // Synthesize main function. funcDecl := &ast.FuncDecl{ Name: ast.NewIdent("main"), Type: &ast.FuncType{}, Body: body, } // Synthesize file. return &ast.File{ Name: ast.NewIdent("main"), Decls: []ast.Decl{importDecl, funcDecl}, Comments: comments, } }
// buildFunction takes a function Value, a list of parameters, and a body, // and generates code for the function. func (c *compiler) buildFunction(f *LLVMValue, context, params, results *types.Tuple, body *ast.BlockStmt) { if currblock := c.builder.GetInsertBlock(); !currblock.IsNil() { defer c.builder.SetInsertPointAtEnd(currblock) } llvm_fn := llvm.ConstExtractValue(f.LLVMValue(), []uint32{0}) entry := llvm.AddBasicBlock(llvm_fn, "entry") c.builder.SetInsertPointAtEnd(entry) // For closures, context is the captured context values. var paramoffset int if context != nil { paramoffset++ // Store the existing values. We're going to temporarily // replace the values with offsets into the context param. oldvalues := make([]*LLVMValue, context.Len()) for i := range oldvalues { v := context.At(i) oldvalues[i] = c.objectdata[v].Value } defer func() { for i := range oldvalues { v := context.At(i) c.objectdata[v].Value = oldvalues[i] } }() // The context parameter is a pointer to a struct // whose elements are pointers to captured values. arg0 := llvm_fn.Param(0) for i := range oldvalues { v := context.At(i) argptr := c.builder.CreateStructGEP(arg0, i, "") argptr = c.builder.CreateLoad(argptr, "") ptrtyp := oldvalues[i].pointer.Type() newvalue := c.NewValue(argptr, ptrtyp) c.objectdata[v].Value = newvalue.makePointee() } } // Bind receiver, arguments and return values to their // identifiers/objects. We'll store each parameter on the stack so // they're addressable. nparams := int(params.Len()) for i := 0; i < nparams; i++ { v := params.At(i) name := v.Name() if !isBlank(name) { value := llvm_fn.Param(i + paramoffset) c.newArgStackVar(i+1, f, v, value, name) } } funcstate := &function{LLVMValue: f, results: results} c.functions.push(funcstate) hasdefer := hasDefer(funcstate, body) // Allocate space on the stack for named results. for i := 0; i < results.Len(); i++ { v := results.At(i) name := v.Name() allocstack := !isBlank(name) if !allocstack && hasdefer { c.objectdata[v] = &ObjectData{} allocstack = true } if allocstack { typ := v.Type() llvmtyp := c.types.ToLLVM(typ) c.newStackVar(f, v, llvm.ConstNull(llvmtyp), name) } } // Create the function body. if hasdefer { c.makeDeferBlock(funcstate, body) } c.VisitBlockStmt(body, false) c.functions.pop() c.setDebugLine(body.End()) // If the last instruction in the function is not a terminator, then // we either have unreachable code or a missing optional return statement // (the latter case is allowable only for functions without results). // // Use GetInsertBlock rather than LastBasicBlock, since the // last basic block might actually be a "defer" block. last := c.builder.GetInsertBlock() if in := last.LastInstruction(); in.IsNil() || in.IsATerminatorInst().IsNil() { c.builder.SetInsertPointAtEnd(last) if results.Len() == 0 { if funcstate.deferblock.IsNil() { c.builder.CreateRetVoid() } else { c.builder.CreateBr(funcstate.deferblock) } } else { c.builder.CreateUnreachable() } } }
func (f *File) validFuncBody(block *ast.BlockStmt) ([]*ast.DeclStmt, []ast.Stmt, *Error) { var declStmts []*ast.DeclStmt var initStmts []ast.Stmt var finishStmts []ast.Stmt declSection, initSection, forSection, finishSection, retSection := true, false, false, false, false // empty func bodies not allowed if block.List == nil { return nil, nil, &Error{errors.New("Empty func bodies not allowed"), block.Pos()} } for _, stmt := range block.List { if declSection { decl, ok := stmt.(*ast.DeclStmt) if ok { if err := f.validDeclStmt(decl); err != nil { return nil, nil, err } declStmts = append(declStmts, decl) continue } else { declSection = false initSection = true } } if initSection { if err := f.validStmt(stmt); err == nil { initStmts = append(initStmts, stmt) continue } else { initSection = false forSection = true } } if forSection { if stmt, ok := stmt.(*ast.ForStmt); ok { if !f.validForStmt(stmt) { return nil, nil, &Error{errors.New("Invalid for statement"), stmt.Pos()} } } forSection = false finishSection = true } if finishSection { if f.validFinishStmt(stmt) { finishStmts = append(finishStmts, stmt) continue } else { finishSection = false retSection = true } } if retSection { if ret, ok := stmt.(*ast.ReturnStmt); ok { if err := f.validRetStmt(ret); err != nil { return nil, nil, err } } else { return nil, nil, &Error{errors.New("Expected return statement in retSection"), stmt.Pos()} } } pos := f.fs.Position(stmt.Pos()) end := f.fs.Position(stmt.End()) panic(fmt.Sprintf("PANIC: NO SECTION, stmt: (begin, end) - (%v, %v)", pos, end)) } return declStmts, initStmts, nil }
// We are not at a branch, employ the following algorithm: // * Traverse tree, storing any loop as a parent // * Find next source line after the given line // * Check and see if we've passed the scope of any parent we've // stored. If so, pop them off the stack. The last parent that // is left get's appending to our list of lines since we could // end up at the top of the loop again. func (s *Searcher) parseDefaultBlock(ifRoot ast.Node, parsedFile *ast.File, line int) []int { var ( found bool lines []int parents []*ast.BlockStmt parentBlockBeginLine int deferEndLine int ) ast.Inspect(parsedFile, func(n ast.Node) bool { if found || n == nil { return false } pos := s.fileset.Position(n.Pos()) if line < pos.Line && deferEndLine != 0 { p := s.fileset.Position(n.Pos()) if deferEndLine < p.Line { found = true lines = append(lines, p.Line) return false } } if stmt, ok := n.(*ast.ForStmt); ok { parents = append(parents, stmt.Body) pos := s.fileset.Position(stmt.Pos()) parentBlockBeginLine = pos.Line } if _, ok := n.(*ast.GenDecl); ok { return true } if dn, ok := n.(*ast.DeferStmt); ok && line < pos.Line { endpos := s.fileset.Position(dn.End()) deferEndLine = endpos.Line return false } if st, ok := n.(*ast.DeclStmt); ok { beginpos := s.fileset.Position(st.Pos()) endpos := s.fileset.Position(st.End()) if beginpos.Line < endpos.Line { return true } } // Check to see if we've found the "next" line. if line < pos.Line { if _, ok := n.(*ast.BlockStmt); ok { return true } var ( parent *ast.BlockStmt parentEndLine int ) for len(parents) > 0 { parent = parents[len(parents)-1] // Grab the line number of the right brace of the parent block. parentEndLine = s.fileset.Position(parent.Rbrace).Line // Check to see if we're still within the parents block. // If we are, we're done and that is our parent. if parentEndLine > line { parentBlockBeginLine = s.fileset.Position(parent.Pos()).Line break } // If we weren't, and there is only 1 parent, we no longer have one. if len(parents) == 1 { parent = nil break } // Remove that parent from the stack. parents = parents[0 : len(parents)-1] } if parent != nil { var ( endfound bool beginFound bool beginLine int ) ast.Inspect(parsedFile, func(n ast.Node) bool { if n == nil || endfound { return false } if _, ok := n.(*ast.BlockStmt); ok { return true } pos := s.fileset.Position(n.Pos()) if parentBlockBeginLine < pos.Line && !beginFound { beginFound = true beginLine = pos.Line return true } if parentEndLine < pos.Line { if _, ok := n.(*ast.FuncDecl); !ok { lines = append(lines, beginLine, pos.Line) } endfound = true return false } return true }) lines = append(lines, parentBlockBeginLine) } switch n.(type) { case *ast.BranchStmt, *ast.FuncDecl: default: lines = append(lines, pos.Line) } found = true return false } return true }) if len(lines) == 0 && 0 < len(parents) { parent := parents[len(parents)-1] lbrace := s.fileset.Position(parent.Lbrace).Line pos := s.fileset.Position(parent.List[0].Pos()) lines = append(lines, lbrace, pos.Line) } return lines }
// playExample synthesizes a new *ast.File based on the provided // file with the provided function body as the body of main. func playExample(file *ast.File, body *ast.BlockStmt) *ast.File { if !strings.HasSuffix(file.Name.Name, "_test") { // We don't support examples that are part of the // greater package (yet). return nil } // Find unresolved identifiers unresolved := make(map[string]bool) ast.Inspect(body, func(n ast.Node) bool { // For an expression like fmt.Println, only add "fmt" to the // set of unresolved names. if e, ok := n.(*ast.SelectorExpr); ok { if id, ok := e.X.(*ast.Ident); ok && id.Obj == nil { unresolved[id.Name] = true } return false } if id, ok := n.(*ast.Ident); ok && id.Obj == nil { unresolved[id.Name] = true } return true }) // Remove predeclared identifiers from unresolved list. for n := range unresolved { if predeclaredTypes[n] || predeclaredConstants[n] || predeclaredFuncs[n] { delete(unresolved, n) } } // Use unresolved identifiers to determine the imports used by this // example. The heuristic assumes package names match base import // paths. (Should be good enough most of the time.) imports := make(map[string]string) // [name]path for _, s := range file.Imports { p, err := strconv.Unquote(s.Path.Value) if err != nil { continue } n := path.Base(p) if s.Name != nil { if s.Name.Name == "." { // We can't resolve dot imports (yet). return nil } n = s.Name.Name } if unresolved[n] { imports[n] = p delete(unresolved, n) } } // If there are other unresolved identifiers, give up because this // synthesized file is not going to build. if len(unresolved) > 0 { return nil } // Filter out comments that are outside the function body. var comments []*ast.CommentGroup for _, c := range file.Comments { if c.Pos() < body.Pos() || c.Pos() >= body.End() { continue } comments = append(comments, c) } // Strip "Output:" commment and adjust body end position. body, comments = stripOutputComment(body, comments) // Synthesize import declaration. importDecl := &ast.GenDecl{ Tok: token.IMPORT, Lparen: 1, // Need non-zero Lparen and Rparen so that printer Rparen: 1, // treats this as a factored import. } for n, p := range imports { s := &ast.ImportSpec{Path: &ast.BasicLit{Value: strconv.Quote(p)}} if path.Base(p) != n { s.Name = ast.NewIdent(n) } importDecl.Specs = append(importDecl.Specs, s) } // Synthesize main function. funcDecl := &ast.FuncDecl{ Name: ast.NewIdent("main"), Type: &ast.FuncType{}, Body: body, } // Synthesize file. return &ast.File{ Name: ast.NewIdent("main"), Decls: []ast.Decl{importDecl, funcDecl}, Comments: comments, } }
// block prints an *ast.BlockStmt; it always spans at least two lines. func (p *printer) block(s *ast.BlockStmt, indent int) { p.print(s.Pos(), token.LBRACE) p.stmtList(s.List, indent, true) p.linebreak(s.Rbrace.Line, 1, ignore, true) p.print(s.Rbrace, token.RBRACE) }
// Returns all possible lines that could be executed after the given file:line, // within the same source file. func (s *Searcher) NextLines(fname string, line int) (lines []int, err error) { var found bool n, err := s.FirstNodeAt(fname, line) if err != nil { return lines, nil } defer func() { if e := recover(); e != nil { e = e.(Done) nl := make([]int, 0, len(lines)) fnd := make(map[int]bool) for _, l := range lines { if _, ok := fnd[l]; !ok { fnd[l] = true nl = append(nl, l) } } lines = nl } }() switch x := n.(type) { // Check if we are at an 'if' statement. // // If we are at an 'if' statement, employ the following algorithm: // * Follow all 'else if' statements, appending their line number // * Follow any 'else' statement if it exists, appending the line // number of the statement following the 'else'. // * If there is no 'else' statement, append line of first statement // following the entire 'if' block. case *ast.IfStmt: var rbrace int p := x.Body.List[0].Pos() pos := s.fileset.Position(p) lines = append(lines, pos.Line) if x.Else == nil { // Grab first line after entire 'if' block rbrace = s.fileset.Position(x.Body.Rbrace).Line f, err := s.parse(fname) if err != nil { return nil, err } ast.Inspect(f, func(n ast.Node) bool { if n == nil { return true } pos := s.fileset.Position(n.Pos()) if rbrace < pos.Line { lines = append(lines, pos.Line) panic(Done("done")) } return true }) } else { // Follow any 'else' statements for { if stmt, ok := x.Else.(*ast.IfStmt); ok { pos := s.fileset.Position(stmt.Pos()) lines = append(lines, pos.Line) x = stmt continue } if x.Else != nil { pos := s.fileset.Position(x.Else.Pos()) ast.Inspect(x, func(n ast.Node) bool { if found { panic(Done("done")) } if n == nil { return false } p := s.fileset.Position(n.Pos()) if pos.Line < p.Line { lines = append(lines, p.Line) found = true return false } return true }) } } } // Follow case statements. // // Append line for first statement following each 'case' condition. case *ast.SwitchStmt: ast.Inspect(x, func(n ast.Node) bool { if stmt, ok := n.(*ast.SwitchStmt); ok { ast.Inspect(stmt, func(n ast.Node) bool { if stmt, ok := n.(*ast.CaseClause); ok { p := stmt.Body[0].Pos() pos := s.fileset.Position(p) lines = append(lines, pos.Line) return false } return true }) panic(Done("done")) } return true }) // Default case - find next source line. // // We are not at a branch, employ the following algorithm: // * Traverse tree, storing any loop as a parent // * Find next source line after the given line // * Check and see if we've passed the scope of any parent we've // stored. If so, pop them off the stack. The last parent that // is left get's appending to our list of lines since we could // end up at the top of the loop again. default: var ( parents []*ast.BlockStmt parentBlockBeginLine int deferEndLine int ) f, err := s.parse(fname) if err != nil { return nil, err } ast.Inspect(f, func(n ast.Node) bool { if found { panic(Done("done")) } if n == nil { return true } pos := s.fileset.Position(n.Pos()) if line < pos.Line && deferEndLine != 0 { p := s.fileset.Position(n.Pos()) if deferEndLine < p.Line { found = true lines = append(lines, p.Line) return false } } if stmt, ok := n.(*ast.ForStmt); ok { parents = append(parents, stmt.Body) pos := s.fileset.Position(stmt.Pos()) parentBlockBeginLine = pos.Line } if _, ok := n.(*ast.GenDecl); ok { return true } if dn, ok := n.(*ast.DeferStmt); ok && line < pos.Line { endpos := s.fileset.Position(dn.End()) deferEndLine = endpos.Line return false } if st, ok := n.(*ast.DeclStmt); ok { beginpos := s.fileset.Position(st.Pos()) endpos := s.fileset.Position(st.End()) if beginpos.Line < endpos.Line { return true } } // Check to see if we've found the "next" line. if line < pos.Line { if _, ok := n.(*ast.BlockStmt); ok { return true } var ( parent *ast.BlockStmt parentEndLine int ) for len(parents) > 0 { parent = parents[len(parents)-1] // Grab the line number of the right brace of the parent block. parentEndLine = s.fileset.Position(parent.Rbrace).Line // Check to see if we're still within the parents block. // If we are, we're done and that is our parent. if parentEndLine > line { parentBlockBeginLine = s.fileset.Position(parent.Pos()).Line break } // If we weren't, and there is only 1 parent, we no longer have one. if len(parents) == 1 { parent = nil break } // Remove that parent from the stack. parents = parents[0 : len(parents)-1] } if parent != nil { var ( endfound bool beginFound bool beginLine int ) ast.Inspect(f, func(n ast.Node) bool { if n == nil || endfound { return false } if _, ok := n.(*ast.BlockStmt); ok { return true } pos := s.fileset.Position(n.Pos()) if parentBlockBeginLine < pos.Line && !beginFound { beginFound = true beginLine = pos.Line return true } if parentEndLine < pos.Line { if _, ok := n.(*ast.FuncDecl); !ok { lines = append(lines, beginLine, pos.Line) } endfound = true return false } return true }) lines = append(lines, parentBlockBeginLine) } switch n.(type) { case *ast.BranchStmt, *ast.FuncDecl: default: lines = append(lines, pos.Line) } found = true return false } return true }) if len(lines) == 0 && 0 < len(parents) { parent := parents[len(parents)-1] lbrace := s.fileset.Position(parent.Lbrace).Line pos := s.fileset.Position(parent.List[0].Pos()) lines = append(lines, lbrace, pos.Line) } } return lines, nil }