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