// insertInWithin places before/after advice around a statement func (wb *WithinBlock) insertInWithin(a ast.Stmt, w *Weave) string { rout := "" mName := grabMethodName(a) // begin line begin := wb.fset.Position(a.Pos()).Line - 1 after := wb.fset.Position(a.End()).Line + 1 // until this is refactored - any lines we add in our // advice need to be accounted for w/begin before_advice := formatAdvice(wb.aspect.advize.before, mName) after_advice := formatAdvice(wb.aspect.advize.after, mName) if before_advice != "" { rout = w.writeAtLine(wb.fname, begin+wb.linecnt, before_advice) wb.linecnt += strings.Count(before_advice, "\n") + 1 } if after_advice != "" { rout = w.writeAtLine(wb.fname, after+wb.linecnt-1, after_advice) wb.linecnt += strings.Count(after_advice, "\n") + 1 } for t := 0; t < len(wb.aspect.importz); t++ { wb.importsNeeded = append(wb.importsNeeded, wb.aspect.importz[t]) } return rout }
func (c *compiler) VisitStmt(stmt ast.Stmt) { if c.logger != nil { c.logger.Println("Compile statement:", reflect.TypeOf(stmt), "@", c.fileset.Position(stmt.Pos())) } switch x := stmt.(type) { case *ast.ReturnStmt: c.VisitReturnStmt(x) case *ast.AssignStmt: c.VisitAssignStmt(x) case *ast.IncDecStmt: c.VisitIncDecStmt(x) case *ast.IfStmt: c.VisitIfStmt(x) case *ast.ForStmt: c.VisitForStmt(x) case *ast.ExprStmt: c.VisitExpr(x.X) case *ast.BlockStmt: c.VisitBlockStmt(x) case *ast.DeclStmt: c.VisitDecl(x.Decl) case *ast.GoStmt: c.VisitGoStmt(x) case *ast.SwitchStmt: c.VisitSwitchStmt(x) default: panic(fmt.Sprintf("Unhandled Stmt node: %s", reflect.TypeOf(stmt))) } }
// statementBoundary finds the location in s that terminates the current basic // block in the source. func (f *File) statementBoundary(s ast.Stmt) token.Pos { // Control flow statements are easy. switch s := s.(type) { case *ast.BlockStmt: // Treat blocks like basic blocks to avoid overlapping counters. return s.Lbrace case *ast.IfStmt: return s.Body.Lbrace case *ast.ForStmt: return s.Body.Lbrace case *ast.LabeledStmt: return f.statementBoundary(s.Stmt) case *ast.RangeStmt: return s.Body.Lbrace case *ast.SwitchStmt: return s.Body.Lbrace case *ast.SelectStmt: return s.Body.Lbrace case *ast.TypeSwitchStmt: return s.Body.Lbrace } // If not a control flow statement, it is a declaration, expression, call, etc. and it may have a function literal. // If it does, that's tricky because we want to exclude the body of the function from this block. // Draw a line at the start of the body of the first function literal we find. // TODO: what if there's more than one? Probably doesn't matter much. var literal funcLitFinder ast.Walk(&literal, s) if literal.found() { return token.Pos(literal) } return s.End() }
func emitTraceStmt(f *Function, event TraceEvent, syntax ast.Stmt) Value { t := &Trace{ Event: event, Start: syntax.Pos(), End: syntax.End(), Breakpoint: false, syntax: syntax, } return emitTraceCommon(f, t) }
func (p *parser) makeExpr(s ast.Stmt) ast.Expr { if s == nil { return nil } if es, isExpr := s.(*ast.ExprStmt); isExpr { return p.checkExpr(es.X) } p.Error(s.Pos(), "expected condition, found simple statement") return &ast.BadExpr{s.Pos()} }
func (v *ShortError) VisitStmt(scope *ast.Scope, stmt ast.Stmt) ScopeVisitor { v.stmt = stmt switch stmt := stmt.(type) { case *ast.BlockStmt: return &ShortError{v.file, v.patches, v.stmt, stmt, 0, new([]byte)} case *ast.ExprStmt: if call := calltomust(stmt.X); call != nil { // TODO(elazarl): depends on number of variables it returns, currently we assume one pos := v.file.Fset.Position(stmt.Pos()) fmt.Printf("%s:%d:%d: 'must' builtin must be assigned into variable\n", pos.Filename, pos.Line, pos.Column) } case *ast.AssignStmt: if len(stmt.Rhs) != 1 { return v } if rhs, ok := stmt.Rhs[0].(*ast.CallExpr); ok { if fun, ok := rhs.Fun.(*ast.Ident); ok && fun.Name == MustKeyword { if stmt.Tok == token.DEFINE { tmpVar := v.tempVar("assignerr_", scope) *v.patches = append(*v.patches, patch.Insert(stmt.TokPos, ", "+tmpVar+" "), patch.Replace(fun, ""), patch.Insert(stmt.End(), "; if "+tmpVar+" != nil "+ "{ panic("+tmpVar+") };"), ) for _, arg := range rhs.Args { v.VisitExpr(scope, arg) } return nil } else if stmt.Tok == token.ASSIGN { vars := []string{} for i := 0; i < len(stmt.Lhs); i++ { vars = append(vars, v.tempVar(fmt.Sprint("assgn", i, "_"), scope)) } assgnerr := v.tempVar("assgnErr_", scope) *v.patches = append(*v.patches, patch.Insert(stmt.Pos(), strings.Join(append(vars, assgnerr), ", ")+":="), patch.InsertNode(stmt.Pos(), rhs.Args[0]), patch.Insert(stmt.Pos(), "; if "+assgnerr+" != nil "+ "{ panic("+assgnerr+") };"), patch.Replace(rhs, strings.Join(vars, ", ")), ) v.VisitExpr(scope, rhs.Args[0]) return nil } } } } return v }
func (v *visitor) wrapLoop(node ast.Stmt, body *ast.BlockStmt) (block *ast.BlockStmt, loop ast.Stmt) { block = astPrintf(` { scope := %s.EnteringNewChildScope() _ = scope // placeholder godebug.Line(ctx, scope, %s) }`, v.scopeVar, pos2lineString(node.Pos()))[0].(*ast.BlockStmt) block.List[1] = node loop = node return }
func (f *File) validStmt(stmt ast.Stmt) *Error { if stmt == nil { return nil } if assign, ok := stmt.(*ast.AssignStmt); ok { if len(assign.Lhs) != 1 || len(assign.Rhs) != 1 { return &Error{errors.New("Invalid assigment statment"), assign.Pos()} } return nil } if ifstmt, ok := stmt.(*ast.IfStmt); ok { if err := f.validExpr(ifstmt.Cond); err != nil { return err } // initialization clause not allowed for if statements if ifstmt.Init != nil { return &Error{errors.New("ifstmt cannot have initialization clause"), ifstmt.Init.Pos()} } if err := f.validStmt(ifstmt.Body); err != nil { return err } if err := f.validStmt(ifstmt.Else); err != nil { return err } } if blk, ok := stmt.(*ast.BlockStmt); ok { for _, s := range blk.List { if err := f.validStmt(s); err != nil { return err } } } if _, ok := stmt.(*ast.EmptyStmt); ok { return nil } if ret, ok := stmt.(*ast.ReturnStmt); ok { if ret.Results == nil || len(ret.Results) == 0 { return nil } if len(ret.Results) > 1 { return &Error{errors.New("Return statement doesn't allow multiple return values"), ret.Pos()} } return f.validRetExpr(ret.Results[0]) } if indec, ok := stmt.(*ast.IncDecStmt); ok { // TODO specialize if err := f.validExpr(indec.X); err != nil { return err } } return &Error{errors.New(fmt.Sprintf("Invalid stmt:%v", stmt)), stmt.Pos()} }
func generateCheckedAssign(stmt ast.Stmt, place ast.Expr) ast.Stmt { pos := stmt.Pos() return &ast.IfStmt{ If: pos, Init: stmt, Cond: &ast.BinaryExpr{ X: place, OpPos: pos, Op: token.NEQ, Y: ast.NewIdent("nil")}, Body: &ast.BlockStmt{ Lbrace: pos, List: []ast.Stmt{&ast.ReturnStmt{}}, Rbrace: pos}, } }
func (check *checker) multipleDefaults(list []ast.Stmt) { var first ast.Stmt for _, s := range list { var d ast.Stmt switch c := s.(type) { case *ast.CaseClause: if len(c.List) == 0 { d = s } case *ast.CommClause: if c.Comm == nil { d = s } default: check.invalidAST(s.Pos(), "case/communication clause expected") } if d != nil { if first != nil { check.errorf(d.Pos(), "multiple defaults (first at %s)", first.Pos()) } else { first = d } } } }
func (t *rewriteVisitor) rewriteRecvStmt(stmt ast.Stmt) []ast.Stmt { // Prohibit inner blocks from having channel operation nodes switch q := stmt.(type) { case *ast.AssignStmt: for _, expr := range q.Lhs { // TODO: Handle channel operations inside LHS of assignments RecurseProhibit(t, expr) } for _, expr := range q.Rhs { if expr == nil { continue } // TODO: Handle channel operations inside RHS of assignments if ue := filterRecvExpr(expr); ue != nil { RecurseProhibit(t, ue.X) } else { RecurseProhibit(t, expr) } } case *ast.ExprStmt: if q == nil { break } // TODO: Handle channel operations inside RHS of assignments if ue := filterRecvExpr(q.X); ue != nil { RecurseProhibit(t, ue.X) } else { RecurseProhibit(t, q) } default: panic("unreach") } // Rewrite receive statement itself return []ast.Stmt{ makeSimpleCallStmt("vtime", "Block", stmt.Pos()), stmt, makeSimpleCallStmt("vtime", "Unblock", stmt.Pos()), } }
// compiles a statement func (w *World) compileStmt(st ast.Stmt) Expr { switch st := st.(type) { default: panic(err(st.Pos(), "not allowed:", typ(st))) case *ast.EmptyStmt: return &emptyStmt{} case *ast.AssignStmt: return w.compileAssignStmt(st) case *ast.ExprStmt: return w.compileExpr(st.X) case *ast.IfStmt: return w.compileIfStmt(st) case *ast.ForStmt: return w.compileForStmt(st) case *ast.IncDecStmt: return w.compileIncDecStmt(st) case *ast.BlockStmt: w.EnterScope() defer w.ExitScope() return w.compileBlockStmt_noScope(st) } }
// statementBoundary finds the location in s that terminates the current basic // block in the source. func (f *File) statementBoundary(s ast.Stmt) token.Pos { // Control flow statements are easy. switch s := s.(type) { case *ast.BlockStmt: // Treat blocks like basic blocks to avoid overlapping counters. return s.Lbrace case *ast.IfStmt: return s.Body.Lbrace case *ast.ForStmt: return s.Body.Lbrace case *ast.LabeledStmt: return f.statementBoundary(s.Stmt) case *ast.RangeStmt: // Ranges might loop over things with function literals.: for _ = range []func(){ ... } {. // TODO: There are a few other such possibilities, but they're extremely unlikely. found, pos := hasFuncLiteral(s.X) if found { return pos } return s.Body.Lbrace case *ast.SwitchStmt: return s.Body.Lbrace case *ast.SelectStmt: return s.Body.Lbrace case *ast.TypeSwitchStmt: return s.Body.Lbrace } // If not a control flow statement, it is a declaration, expression, call, etc. and it may have a function literal. // If it does, that's tricky because we want to exclude the body of the function from this block. // Draw a line at the start of the body of the first function literal we find. // TODO: what if there's more than one? Probably doesn't matter much. found, pos := hasFuncLiteral(s) if found { return pos } return s.End() }
// stmt typechecks statement s. func (check *checker) stmt(s ast.Stmt) { switch s := s.(type) { case *ast.BadStmt, *ast.EmptyStmt: // ignore case *ast.DeclStmt: d, _ := s.Decl.(*ast.GenDecl) if d == nil || (d.Tok != token.CONST && d.Tok != token.TYPE && d.Tok != token.VAR) { check.invalidAST(token.NoPos, "const, type, or var declaration expected") return } if d.Tok == token.CONST { check.assocInitvals(d) } check.decl(d) case *ast.LabeledStmt: // TODO(gri) anything to do with label itself? check.stmt(s.Stmt) case *ast.ExprStmt: var x operand used := false switch e := unparen(s.X).(type) { case *ast.CallExpr: // function calls are permitted used = true // but some builtins are excluded // (Caution: This evaluates e.Fun twice, once here and once // below as part of s.X. This has consequences for // check.register. Perhaps this can be avoided.) check.expr(&x, e.Fun, nil, -1) if x.mode != invalid { if b, ok := x.typ.(*builtin); ok && !b.isStatement { used = false } } case *ast.UnaryExpr: // receive operations are permitted if e.Op == token.ARROW { used = true } } if !used { check.errorf(s.Pos(), "%s not used", s.X) // ok to continue } check.rawExpr(&x, s.X, nil, -1, false) if x.mode == typexpr { check.errorf(x.pos(), "%s is not an expression", &x) } case *ast.SendStmt: var ch, x operand check.expr(&ch, s.Chan, nil, -1) check.expr(&x, s.Value, nil, -1) if ch.mode == invalid || x.mode == invalid { return } if tch, ok := underlying(ch.typ).(*Chan); !ok || tch.Dir&ast.SEND == 0 || !check.assignment(&x, tch.Elt) { if x.mode != invalid { check.invalidOp(ch.pos(), "cannot send %s to channel %s", &x, &ch) } } case *ast.IncDecStmt: var op token.Token switch s.Tok { case token.INC: op = token.ADD case token.DEC: op = token.SUB default: check.invalidAST(s.TokPos, "unknown inc/dec operation %s", s.Tok) return } var x operand Y := &ast.BasicLit{ValuePos: s.X.Pos(), Kind: token.INT, Value: "1"} // use x's position check.binary(&x, s.X, Y, op, -1) if x.mode == invalid { return } check.assign1to1(s.X, nil, &x, false, -1) case *ast.AssignStmt: switch s.Tok { case token.ASSIGN, token.DEFINE: if len(s.Lhs) == 0 { check.invalidAST(s.Pos(), "missing lhs in assignment") return } check.assignNtoM(s.Lhs, s.Rhs, s.Tok == token.DEFINE, -1) default: // assignment operations if len(s.Lhs) != 1 || len(s.Rhs) != 1 { check.errorf(s.TokPos, "assignment operation %s requires single-valued expressions", s.Tok) return } // TODO(gri) make this conversion more efficient 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: check.invalidAST(s.TokPos, "unknown assignment operation %s", s.Tok) return } var x operand check.binary(&x, s.Lhs[0], s.Rhs[0], op, -1) if x.mode == invalid { return } check.assign1to1(s.Lhs[0], nil, &x, false, -1) } case *ast.GoStmt: check.call(s.Call) case *ast.DeferStmt: check.call(s.Call) case *ast.ReturnStmt: sig := check.funcsig if n := len(sig.Results); n > 0 { // TODO(gri) should not have to compute lhs, named every single time - clean this up lhs := make([]ast.Expr, n) named := false // if set, function has named results for i, res := range sig.Results { if len(res.Name) > 0 { // a blank (_) result parameter is a named result named = true } name := ast.NewIdent(res.Name) name.NamePos = s.Pos() check.register(name, &Var{Name: res.Name, Type: res.Type}) // Pkg == nil lhs[i] = name } if len(s.Results) > 0 || !named { // TODO(gri) assignNtoM should perhaps not require len(lhs) > 0 check.assignNtoM(lhs, s.Results, false, -1) } } else if len(s.Results) > 0 { check.errorf(s.Pos(), "no result values expected") } case *ast.BranchStmt: // TODO(gri) implement this case *ast.BlockStmt: check.stmtList(s.List) case *ast.IfStmt: check.optionalStmt(s.Init) var x operand check.expr(&x, s.Cond, nil, -1) if x.mode != invalid && !isBoolean(x.typ) { check.errorf(s.Cond.Pos(), "non-boolean condition in if statement") } check.stmt(s.Body) check.optionalStmt(s.Else) case *ast.SwitchStmt: check.optionalStmt(s.Init) var x operand tag := s.Tag if tag == nil { // use fake true tag value and position it at the opening { of the switch ident := &ast.Ident{NamePos: s.Body.Lbrace, Name: "true"} check.register(ident, Universe.Lookup("true")) tag = ident } check.expr(&x, tag, nil, -1) check.multipleDefaults(s.Body.List) // TODO(gri) check also correct use of fallthrough seen := make(map[interface{}]token.Pos) for _, s := range s.Body.List { clause, _ := s.(*ast.CaseClause) if clause == nil { continue // error reported before } if x.mode != invalid { for _, expr := range clause.List { x := x // copy of x (don't modify original) var y operand check.expr(&y, expr, nil, -1) if y.mode == invalid { continue // error reported before } // If we have a constant case value, it must appear only // once in the switch statement. Determine if there is a // duplicate entry, but only report an error if there are // no other errors. var dupl token.Pos var yy operand if y.mode == constant { // TODO(gri) This code doesn't work correctly for // large integer, floating point, or // complex values - the respective struct // comparisons are shallow. Need to use a // hash function to index the map. dupl = seen[y.val] seen[y.val] = y.pos() yy = y // remember y } // TODO(gri) The convertUntyped call pair below appears in other places. Factor! // Order matters: By comparing y against x, error positions are at the case values. check.convertUntyped(&y, x.typ) if y.mode == invalid { continue // error reported before } check.convertUntyped(&x, y.typ) if x.mode == invalid { continue // error reported before } check.comparison(&y, &x, token.EQL) if y.mode != invalid && dupl.IsValid() { check.errorf(yy.pos(), "%s is duplicate case (previous at %s)", &yy, check.fset.Position(dupl)) } } } check.stmtList(clause.Body) } case *ast.TypeSwitchStmt: check.optionalStmt(s.Init) // A type switch guard must be of the form: // // TypeSwitchGuard = [ identifier ":=" ] PrimaryExpr "." "(" "type" ")" . // // The parser is checking syntactic correctness; // remaining syntactic errors are considered AST errors here. // TODO(gri) better factoring of error handling (invalid ASTs) // var lhs *Var // lhs variable or nil var rhs ast.Expr switch guard := s.Assign.(type) { case *ast.ExprStmt: rhs = guard.X case *ast.AssignStmt: if len(guard.Lhs) != 1 || guard.Tok != token.DEFINE || len(guard.Rhs) != 1 { check.invalidAST(s.Pos(), "incorrect form of type switch guard") return } ident, _ := guard.Lhs[0].(*ast.Ident) if ident == nil { check.invalidAST(s.Pos(), "incorrect form of type switch guard") return } lhs = check.lookup(ident).(*Var) rhs = guard.Rhs[0] default: check.invalidAST(s.Pos(), "incorrect form of type switch guard") return } // rhs must be of the form: expr.(type) and expr must be an interface expr, _ := rhs.(*ast.TypeAssertExpr) if expr == nil || expr.Type != nil { check.invalidAST(s.Pos(), "incorrect form of type switch guard") return } var x operand check.expr(&x, expr.X, nil, -1) if x.mode == invalid { return } var T *Interface if T, _ = underlying(x.typ).(*Interface); T == nil { check.errorf(x.pos(), "%s is not an interface", &x) return } check.multipleDefaults(s.Body.List) for _, s := range s.Body.List { clause, _ := s.(*ast.CaseClause) if clause == nil { continue // error reported before } // Check each type in this type switch case. var typ Type for _, expr := range clause.List { typ = check.typOrNil(expr, false) if typ != nil && typ != Typ[Invalid] { if method, wrongType := missingMethod(typ, T); method != nil { var msg string if wrongType { msg = "%s cannot have dynamic type %s (wrong type for method %s)" } else { msg = "%s cannot have dynamic type %s (missing method %s)" } check.errorf(expr.Pos(), msg, &x, typ, method.Name) // ok to continue } } } // If lhs exists, set its type for each clause. if lhs != nil { // In clauses with a case listing exactly one type, the variable has that type; // otherwise, the variable has the type of the expression in the TypeSwitchGuard. if len(clause.List) != 1 || typ == nil { typ = x.typ } lhs.Type = typ } check.stmtList(clause.Body) } // There is only one object (lhs) associated with a lhs identifier, but that object // assumes different types for different clauses. Set it back to the type of the // TypeSwitchGuard expression so that that variable always has a valid type. if lhs != nil { lhs.Type = x.typ } case *ast.SelectStmt: check.multipleDefaults(s.Body.List) for _, s := range s.Body.List { clause, _ := s.(*ast.CommClause) if clause == nil { continue // error reported before } check.optionalStmt(clause.Comm) // TODO(gri) check correctness of c.Comm (must be Send/RecvStmt) check.stmtList(clause.Body) } case *ast.ForStmt: check.optionalStmt(s.Init) if s.Cond != nil { var x operand check.expr(&x, s.Cond, nil, -1) if x.mode != invalid && !isBoolean(x.typ) { check.errorf(s.Cond.Pos(), "non-boolean condition in for statement") } } check.optionalStmt(s.Post) check.stmt(s.Body) case *ast.RangeStmt: // check expression to iterate over decl := s.Tok == token.DEFINE var x operand check.expr(&x, s.X, nil, -1) if x.mode == invalid { // if we don't have a declaration, we can still check the loop's body if !decl { check.stmt(s.Body) } return } // determine key/value types var key, val Type switch typ := underlying(x.typ).(type) { case *Basic: if isString(typ) { key = Typ[UntypedInt] val = Typ[UntypedRune] } case *Array: key = Typ[UntypedInt] val = typ.Elt case *Slice: key = Typ[UntypedInt] val = typ.Elt case *Pointer: if typ, _ := underlying(typ.Base).(*Array); typ != nil { key = Typ[UntypedInt] val = typ.Elt } case *Map: key = typ.Key val = typ.Elt case *Chan: key = typ.Elt if typ.Dir&ast.RECV == 0 { check.errorf(x.pos(), "cannot range over send-only channel %s", &x) // ok to continue } if s.Value != nil { check.errorf(s.Value.Pos(), "iteration over %s permits only one iteration variable", &x) // ok to continue } } if key == nil { check.errorf(x.pos(), "cannot range over %s", &x) // if we don't have a declaration, we can still check the loop's body if !decl { check.stmt(s.Body) } return } // check assignment to/declaration of iteration variables // TODO(gri) The error messages/positions are not great here, // they refer to the expression in the range clause. // Should give better messages w/o too much code // duplication (assignment checking). x.mode = value if s.Key != nil { x.typ = key x.expr = s.Key check.assign1to1(s.Key, nil, &x, decl, -1) } else { check.invalidAST(s.Pos(), "range clause requires index iteration variable") // ok to continue } if s.Value != nil { x.typ = val x.expr = s.Value check.assign1to1(s.Value, nil, &x, decl, -1) } check.stmt(s.Body) default: check.errorf(s.Pos(), "invalid statement") } }
// stmt typechecks statement s. func (check *checker) stmt(ctxt stmtContext, s ast.Stmt) { // statements cannot use iota in general // (constant declarations set it explicitly) assert(check.iota == nil) // statements must end with the same top scope as they started with if debug { defer func(scope *Scope) { // don't check if code is panicking if p := recover(); p != nil { panic(p) } assert(scope == check.topScope) }(check.topScope) } inner := ctxt &^ fallthroughOk switch s := s.(type) { case *ast.BadStmt, *ast.EmptyStmt: // ignore case *ast.DeclStmt: check.declStmt(s.Decl) case *ast.LabeledStmt: check.hasLabel = true check.stmt(ctxt, s.Stmt) case *ast.ExprStmt: // spec: "With the exception of specific built-in functions, // function and method calls and receive operations can appear // in statement context. Such statements may be parenthesized." var x operand kind := check.rawExpr(&x, s.X, nil) var msg string switch x.mode { default: if kind == statement { return } msg = "is not used" case builtin: msg = "must be called" case typexpr: msg = "is not an expression" } check.errorf(x.pos(), "%s %s", &x, msg) case *ast.SendStmt: var ch, x operand check.expr(&ch, s.Chan) check.expr(&x, s.Value) if ch.mode == invalid || x.mode == invalid { return } if tch, ok := ch.typ.Underlying().(*Chan); !ok || tch.dir&ast.SEND == 0 || !check.assignment(&x, tch.elem) { if x.mode != invalid { check.invalidOp(ch.pos(), "cannot send %s to channel %s", &x, &ch) } } case *ast.IncDecStmt: var op token.Token switch s.Tok { case token.INC: op = token.ADD case token.DEC: op = token.SUB default: check.invalidAST(s.TokPos, "unknown inc/dec operation %s", s.Tok) return } var x operand Y := &ast.BasicLit{ValuePos: s.X.Pos(), Kind: token.INT, Value: "1"} // use x's position check.binary(&x, s.X, Y, op) if x.mode == invalid { return } check.assignVar(s.X, &x) case *ast.AssignStmt: switch s.Tok { case token.ASSIGN, token.DEFINE: if len(s.Lhs) == 0 { check.invalidAST(s.Pos(), "missing lhs in assignment") return } if s.Tok == token.DEFINE { check.shortVarDecl(s.TokPos, s.Lhs, s.Rhs) } else { // regular assignment check.assignVars(s.Lhs, s.Rhs) } default: // assignment operations if len(s.Lhs) != 1 || len(s.Rhs) != 1 { check.errorf(s.TokPos, "assignment operation %s requires single-valued expressions", s.Tok) return } op := assignOp(s.Tok) if op == token.ILLEGAL { check.invalidAST(s.TokPos, "unknown assignment operation %s", s.Tok) return } var x operand check.binary(&x, s.Lhs[0], s.Rhs[0], op) if x.mode == invalid { return } check.assignVar(s.Lhs[0], &x) } case *ast.GoStmt: check.suspendedCall("go", s.Call) case *ast.DeferStmt: check.suspendedCall("defer", s.Call) case *ast.ReturnStmt: sig := check.funcSig if n := sig.results.Len(); n > 0 { // determine if the function has named results named := false lhs := make([]*Var, n) for i, res := range sig.results.vars { if res.name != "" { // a blank (_) result parameter is a named result named = true } lhs[i] = res } if len(s.Results) > 0 || !named { check.initVars(lhs, s.Results, s.Return) return } } else if len(s.Results) > 0 { check.errorf(s.Pos(), "no result values expected") } case *ast.BranchStmt: if s.Label != nil { check.hasLabel = true return // checked in 2nd pass (check.labels) } switch s.Tok { case token.BREAK: if ctxt&inBreakable == 0 { check.errorf(s.Pos(), "break not in for, switch, or select statement") } case token.CONTINUE: if ctxt&inContinuable == 0 { check.errorf(s.Pos(), "continue not in for statement") } case token.FALLTHROUGH: if ctxt&fallthroughOk == 0 { check.errorf(s.Pos(), "fallthrough statement out of place") } default: check.invalidAST(s.Pos(), "branch statement: %s", s.Tok) } case *ast.BlockStmt: check.openScope(s) defer check.closeScope() check.stmtList(inner, s.List) case *ast.IfStmt: check.openScope(s) defer check.closeScope() check.initStmt(s.Init) var x operand check.expr(&x, s.Cond) if x.mode != invalid && !isBoolean(x.typ) { check.errorf(s.Cond.Pos(), "non-boolean condition in if statement") } check.stmt(inner, s.Body) if s.Else != nil { check.stmt(inner, s.Else) } case *ast.SwitchStmt: inner |= inBreakable check.openScope(s) defer check.closeScope() check.initStmt(s.Init) var x operand tag := s.Tag if tag == nil { // use fake true tag value and position it at the opening { of the switch ident := &ast.Ident{NamePos: s.Body.Lbrace, Name: "true"} check.recordObject(ident, Universe.Lookup("true")) tag = ident } check.expr(&x, tag) check.multipleDefaults(s.Body.List) for i, c := range s.Body.List { clause, _ := c.(*ast.CaseClause) if clause == nil { check.invalidAST(c.Pos(), "incorrect expression switch case") continue } if x.mode != invalid { check.caseValues(x, clause.List) } check.openScope(clause) inner := inner if i+1 < len(s.Body.List) { inner |= fallthroughOk } check.stmtList(inner, clause.Body) check.closeScope() } case *ast.TypeSwitchStmt: inner |= inBreakable check.openScope(s) defer check.closeScope() check.initStmt(s.Init) // A type switch guard must be of the form: // // TypeSwitchGuard = [ identifier ":=" ] PrimaryExpr "." "(" "type" ")" . // // The parser is checking syntactic correctness; // remaining syntactic errors are considered AST errors here. // TODO(gri) better factoring of error handling (invalid ASTs) // var lhs *ast.Ident // lhs identifier or nil var rhs ast.Expr switch guard := s.Assign.(type) { case *ast.ExprStmt: rhs = guard.X case *ast.AssignStmt: if len(guard.Lhs) != 1 || guard.Tok != token.DEFINE || len(guard.Rhs) != 1 { check.invalidAST(s.Pos(), "incorrect form of type switch guard") return } lhs, _ = guard.Lhs[0].(*ast.Ident) if lhs == nil { check.invalidAST(s.Pos(), "incorrect form of type switch guard") return } check.recordObject(lhs, nil) // lhs variable is implicitly declared in each cause clause rhs = guard.Rhs[0] default: check.invalidAST(s.Pos(), "incorrect form of type switch guard") return } // rhs must be of the form: expr.(type) and expr must be an interface expr, _ := rhs.(*ast.TypeAssertExpr) if expr == nil || expr.Type != nil { check.invalidAST(s.Pos(), "incorrect form of type switch guard") return } var x operand check.expr(&x, expr.X) if x.mode == invalid { return } xtyp, _ := x.typ.Underlying().(*Interface) if xtyp == nil { check.errorf(x.pos(), "%s is not an interface", &x) return } check.multipleDefaults(s.Body.List) var lhsVars []*Var // set of implicitly declared lhs variables seen := make(map[Type]token.Pos) // map of seen types to positions for _, s := range s.Body.List { clause, _ := s.(*ast.CaseClause) if clause == nil { check.invalidAST(s.Pos(), "incorrect type switch case") continue } // Check each type in this type switch case. T := check.caseTypes(&x, xtyp, clause.List, seen) check.openScope(clause) // If lhs exists, declare a corresponding variable in the case-local scope if necessary. if lhs != nil { // spec: "The TypeSwitchGuard may include a short variable declaration. // When that form is used, the variable is declared at the beginning of // the implicit block in each clause. In clauses with a case listing // exactly one type, the variable has that type; otherwise, the variable // has the type of the expression in the TypeSwitchGuard." if len(clause.List) != 1 || T == nil { T = x.typ } obj := NewVar(lhs.Pos(), check.pkg, lhs.Name, T) // For the "declared but not used" error, all lhs variables act as // one; i.e., if any one of them is 'used', all of them are 'used'. // Collect them for later analysis. lhsVars = append(lhsVars, obj) check.declare(check.topScope, nil, obj) check.recordImplicit(clause, obj) } check.stmtList(inner, clause.Body) check.closeScope() } // If a lhs variable was declared but there were no case clauses, make sure // we have at least one (dummy) 'unused' variable to force an error message. if len(lhsVars) == 0 && lhs != nil { lhsVars = []*Var{NewVar(lhs.Pos(), check.pkg, lhs.Name, x.typ)} } // Record lhs variables for this type switch, if any. if len(lhsVars) > 0 { check.lhsVarsList = append(check.lhsVarsList, lhsVars) } case *ast.SelectStmt: inner |= inBreakable check.multipleDefaults(s.Body.List) for _, s := range s.Body.List { clause, _ := s.(*ast.CommClause) if clause == nil { continue // error reported before } // clause.Comm must be a SendStmt, RecvStmt, or default case valid := false var rhs ast.Expr // rhs of RecvStmt, or nil switch s := clause.Comm.(type) { case nil, *ast.SendStmt: valid = true case *ast.AssignStmt: if len(s.Rhs) == 1 { rhs = s.Rhs[0] } case *ast.ExprStmt: rhs = s.X } // if present, rhs must be a receive operation if rhs != nil { if x, _ := unparen(rhs).(*ast.UnaryExpr); x != nil && x.Op == token.ARROW { valid = true } } if !valid { check.errorf(clause.Comm.Pos(), "select case must be send or receive (possibly with assignment)") continue } check.openScope(s) defer check.closeScope() if clause.Comm != nil { check.stmt(inner, clause.Comm) } check.stmtList(inner, clause.Body) } case *ast.ForStmt: inner |= inBreakable | inContinuable check.openScope(s) defer check.closeScope() check.initStmt(s.Init) if s.Cond != nil { var x operand check.expr(&x, s.Cond) if x.mode != invalid && !isBoolean(x.typ) { check.errorf(s.Cond.Pos(), "non-boolean condition in for statement") } } check.initStmt(s.Post) check.stmt(inner, s.Body) case *ast.RangeStmt: inner |= inBreakable | inContinuable check.openScope(s) defer check.closeScope() // check expression to iterate over decl := s.Tok == token.DEFINE var x operand check.expr(&x, s.X) if x.mode == invalid { // if we don't have a declaration, we can still check the loop's body // (otherwise we can't because we are missing the declared variables) if !decl { check.stmt(inner, s.Body) } return } // determine key/value types var key, val Type switch typ := x.typ.Underlying().(type) { case *Basic: if isString(typ) { key = Typ[Int] val = Typ[Rune] } case *Array: key = Typ[Int] val = typ.elem case *Slice: key = Typ[Int] val = typ.elem case *Pointer: if typ, _ := typ.base.Underlying().(*Array); typ != nil { key = Typ[Int] val = typ.elem } case *Map: key = typ.key val = typ.elem case *Chan: key = typ.elem val = Typ[Invalid] if typ.dir&ast.RECV == 0 { check.errorf(x.pos(), "cannot range over send-only channel %s", &x) // ok to continue } if s.Value != nil { check.errorf(s.Value.Pos(), "iteration over %s permits only one iteration variable", &x) // ok to continue } } if key == nil { check.errorf(x.pos(), "cannot range over %s", &x) // if we don't have a declaration, we can still check the loop's body if !decl { check.stmt(inner, s.Body) } return } // check assignment to/declaration of iteration variables // (irregular assignment, cannot easily map to existing assignment checks) if s.Key == nil { check.invalidAST(s.Pos(), "range clause requires index iteration variable") // ok to continue } // lhs expressions and initialization value (rhs) types lhs := [2]ast.Expr{s.Key, s.Value} rhs := [2]Type{key, val} if decl { // short variable declaration; variable scope starts after the range clause // (the for loop opens a new scope, so variables on the lhs never redeclare // previously declared variables) var vars []*Var for i, lhs := range lhs { if lhs == nil { continue } // determine lhs variable var obj *Var if ident, _ := lhs.(*ast.Ident); ident != nil { // declare new variable name := ident.Name obj = NewVar(ident.Pos(), check.pkg, name, nil) check.recordObject(ident, obj) // _ variables don't count as new variables if name != "_" { vars = append(vars, obj) } } else { check.errorf(lhs.Pos(), "cannot declare %s", lhs) obj = NewVar(lhs.Pos(), check.pkg, "_", nil) // dummy variable } // initialize lhs variable x.mode = value x.expr = lhs // we don't have a better rhs expression to use here x.typ = rhs[i] check.initVar(obj, &x) } // declare variables if len(vars) > 0 { for _, obj := range vars { check.declare(check.topScope, nil, obj) // recordObject already called } } else { check.errorf(s.TokPos, "no new variables on left side of :=") } } else { // ordinary assignment for i, lhs := range lhs { if lhs == nil { continue } x.mode = value x.expr = lhs // we don't have a better rhs expression to use here x.typ = rhs[i] check.assignVar(lhs, &x) } } check.stmt(inner, s.Body) default: check.errorf(s.Pos(), "invalid statement") } }
// stmt typechecks statement s. func (check *Checker) stmt(ctxt stmtContext, s ast.Stmt) { // statements cannot use iota in general // (constant declarations set it explicitly) assert(check.iota == nil) // statements must end with the same top scope as they started with if debug { defer func(scope *Scope) { // don't check if code is panicking if p := recover(); p != nil { panic(p) } assert(scope == check.scope) }(check.scope) } inner := ctxt &^ fallthroughOk switch s := s.(type) { case *ast.BadStmt, *ast.EmptyStmt: // ignore case *ast.DeclStmt: check.declStmt(s.Decl) case *ast.LabeledStmt: check.hasLabel = true check.stmt(ctxt, s.Stmt) case *ast.ExprStmt: // spec: "With the exception of specific built-in functions, // function and method calls and receive operations can appear // in statement context. Such statements may be parenthesized." var x operand kind := check.rawExpr(&x, s.X, nil) var msg string switch x.mode { default: if kind == statement { return } msg = "is not used" case builtin: msg = "must be called" case typexpr: msg = "is not an expression" } check.errorf(x.pos(), "%s %s", &x, msg) case *ast.SendStmt: var ch, x operand check.expr(&ch, s.Chan) check.expr(&x, s.Value) if ch.mode == invalid || x.mode == invalid { return } tch, ok := ch.typ.Underlying().(*Chan) if !ok { check.invalidOp(s.Arrow, "cannot send to non-chan type %s", ch.typ) return } if tch.dir == RecvOnly { check.invalidOp(s.Arrow, "cannot send to receive-only type %s", tch) return } check.assignment(&x, tch.elem, "send") case *ast.IncDecStmt: var op token.Token switch s.Tok { case token.INC: op = token.ADD case token.DEC: op = token.SUB default: check.invalidAST(s.TokPos, "unknown inc/dec operation %s", s.Tok) return } var x operand check.expr(&x, s.X) if x.mode == invalid { return } if !isNumeric(x.typ) { check.invalidOp(s.X.Pos(), "%s%s (non-numeric type %s)", s.X, s.Tok, x.typ) return } Y := &ast.BasicLit{ValuePos: s.X.Pos(), Kind: token.INT, Value: "1"} // use x's position check.binary(&x, nil, s.X, Y, op) if x.mode == invalid { return } check.assignVar(s.X, &x) case *ast.AssignStmt: switch s.Tok { case token.ASSIGN, token.DEFINE: if len(s.Lhs) == 0 { check.invalidAST(s.Pos(), "missing lhs in assignment") return } if s.Tok == token.DEFINE { check.shortVarDecl(s.TokPos, s.Lhs, s.Rhs) } else { // regular assignment check.assignVars(s.Lhs, s.Rhs) } default: // assignment operations if len(s.Lhs) != 1 || len(s.Rhs) != 1 { check.errorf(s.TokPos, "assignment operation %s requires single-valued expressions", s.Tok) return } op := assignOp(s.Tok) if op == token.ILLEGAL { check.invalidAST(s.TokPos, "unknown assignment operation %s", s.Tok) return } var x operand check.binary(&x, nil, s.Lhs[0], s.Rhs[0], op) if x.mode == invalid { return } check.assignVar(s.Lhs[0], &x) } case *ast.GoStmt: check.suspendedCall("go", s.Call) case *ast.DeferStmt: check.suspendedCall("defer", s.Call) case *ast.ReturnStmt: res := check.sig.results if res.Len() > 0 { // function returns results // (if one, say the first, result parameter is named, all of them are named) if len(s.Results) == 0 && res.vars[0].name != "" { // spec: "Implementation restriction: A compiler may disallow an empty expression // list in a "return" statement if a different entity (constant, type, or variable) // with the same name as a result parameter is in scope at the place of the return." for _, obj := range res.vars { if _, alt := check.scope.LookupParent(obj.name, check.pos); alt != nil && alt != obj { check.errorf(s.Pos(), "result parameter %s not in scope at return", obj.name) check.errorf(alt.Pos(), "\tinner declaration of %s", obj) // ok to continue } } } else { // return has results or result parameters are unnamed check.initVars(res.vars, s.Results, s.Return) } } else if len(s.Results) > 0 { check.error(s.Results[0].Pos(), "no result values expected") check.use(s.Results...) } case *ast.BranchStmt: if s.Label != nil { check.hasLabel = true return // checked in 2nd pass (check.labels) } switch s.Tok { case token.BREAK: if ctxt&breakOk == 0 { check.error(s.Pos(), "break not in for, switch, or select statement") } case token.CONTINUE: if ctxt&continueOk == 0 { check.error(s.Pos(), "continue not in for statement") } case token.FALLTHROUGH: if ctxt&fallthroughOk == 0 { check.error(s.Pos(), "fallthrough statement out of place") } default: check.invalidAST(s.Pos(), "branch statement: %s", s.Tok) } case *ast.BlockStmt: check.openScope(s, "block") defer check.closeScope() check.stmtList(inner, s.List) case *ast.IfStmt: check.openScope(s, "if") defer check.closeScope() check.simpleStmt(s.Init) var x operand check.expr(&x, s.Cond) if x.mode != invalid && !isBoolean(x.typ) { check.error(s.Cond.Pos(), "non-boolean condition in if statement") } check.stmt(inner, s.Body) // The parser produces a correct AST but if it was modified // elsewhere the else branch may be invalid. Check again. switch s.Else.(type) { case nil, *ast.BadStmt: // valid or error already reported case *ast.IfStmt, *ast.BlockStmt: check.stmt(inner, s.Else) default: check.error(s.Else.Pos(), "invalid else branch in if statement") } case *ast.SwitchStmt: inner |= breakOk check.openScope(s, "switch") defer check.closeScope() check.simpleStmt(s.Init) var x operand if s.Tag != nil { check.expr(&x, s.Tag) // By checking assignment of x to an invisible temporary // (as a compiler would), we get all the relevant checks. check.assignment(&x, nil, "switch expression") } else { // spec: "A missing switch expression is // equivalent to the boolean value true." x.mode = constant_ x.typ = Typ[Bool] x.val = constant.MakeBool(true) x.expr = &ast.Ident{NamePos: s.Body.Lbrace, Name: "true"} } check.multipleDefaults(s.Body.List) seen := make(valueMap) // map of seen case values to positions and types for i, c := range s.Body.List { clause, _ := c.(*ast.CaseClause) if clause == nil { check.invalidAST(c.Pos(), "incorrect expression switch case") continue } check.caseValues(&x, clause.List, seen) check.openScope(clause, "case") inner := inner if i+1 < len(s.Body.List) { inner |= fallthroughOk } check.stmtList(inner, clause.Body) check.closeScope() } case *ast.TypeSwitchStmt: inner |= breakOk check.openScope(s, "type switch") defer check.closeScope() check.simpleStmt(s.Init) // A type switch guard must be of the form: // // TypeSwitchGuard = [ identifier ":=" ] PrimaryExpr "." "(" "type" ")" . // // The parser is checking syntactic correctness; // remaining syntactic errors are considered AST errors here. // TODO(gri) better factoring of error handling (invalid ASTs) // var lhs *ast.Ident // lhs identifier or nil var rhs ast.Expr switch guard := s.Assign.(type) { case *ast.ExprStmt: rhs = guard.X case *ast.AssignStmt: if len(guard.Lhs) != 1 || guard.Tok != token.DEFINE || len(guard.Rhs) != 1 { check.invalidAST(s.Pos(), "incorrect form of type switch guard") return } lhs, _ = guard.Lhs[0].(*ast.Ident) if lhs == nil { check.invalidAST(s.Pos(), "incorrect form of type switch guard") return } if lhs.Name == "_" { // _ := x.(type) is an invalid short variable declaration check.softErrorf(lhs.Pos(), "no new variable on left side of :=") lhs = nil // avoid declared but not used error below } else { check.recordDef(lhs, nil) // lhs variable is implicitly declared in each cause clause } rhs = guard.Rhs[0] default: check.invalidAST(s.Pos(), "incorrect form of type switch guard") return } // rhs must be of the form: expr.(type) and expr must be an interface expr, _ := rhs.(*ast.TypeAssertExpr) if expr == nil || expr.Type != nil { check.invalidAST(s.Pos(), "incorrect form of type switch guard") return } var x operand check.expr(&x, expr.X) if x.mode == invalid { return } xtyp, _ := x.typ.Underlying().(*Interface) if xtyp == nil { check.errorf(x.pos(), "%s is not an interface", &x) return } check.multipleDefaults(s.Body.List) var lhsVars []*Var // list of implicitly declared lhs variables seen := make(map[Type]token.Pos) // map of seen types to positions for _, s := range s.Body.List { clause, _ := s.(*ast.CaseClause) if clause == nil { check.invalidAST(s.Pos(), "incorrect type switch case") continue } // Check each type in this type switch case. T := check.caseTypes(&x, xtyp, clause.List, seen) check.openScope(clause, "case") // If lhs exists, declare a corresponding variable in the case-local scope. if lhs != nil { // spec: "The TypeSwitchGuard may include a short variable declaration. // When that form is used, the variable is declared at the beginning of // the implicit block in each clause. In clauses with a case listing // exactly one type, the variable has that type; otherwise, the variable // has the type of the expression in the TypeSwitchGuard." if len(clause.List) != 1 || T == nil { T = x.typ } obj := NewVar(lhs.Pos(), check.pkg, lhs.Name, T) scopePos := clause.End() if len(clause.Body) > 0 { scopePos = clause.Body[0].Pos() } check.declare(check.scope, nil, obj, scopePos) check.recordImplicit(clause, obj) // For the "declared but not used" error, all lhs variables act as // one; i.e., if any one of them is 'used', all of them are 'used'. // Collect them for later analysis. lhsVars = append(lhsVars, obj) } check.stmtList(inner, clause.Body) check.closeScope() } // If lhs exists, we must have at least one lhs variable that was used. if lhs != nil { var used bool for _, v := range lhsVars { if v.used { used = true } v.used = true // avoid usage error when checking entire function } if !used { check.softErrorf(lhs.Pos(), "%s declared but not used", lhs.Name) } } case *ast.SelectStmt: inner |= breakOk check.multipleDefaults(s.Body.List) for _, s := range s.Body.List { clause, _ := s.(*ast.CommClause) if clause == nil { continue // error reported before } // clause.Comm must be a SendStmt, RecvStmt, or default case valid := false var rhs ast.Expr // rhs of RecvStmt, or nil switch s := clause.Comm.(type) { case nil, *ast.SendStmt: valid = true case *ast.AssignStmt: if len(s.Rhs) == 1 { rhs = s.Rhs[0] } case *ast.ExprStmt: rhs = s.X } // if present, rhs must be a receive operation if rhs != nil { if x, _ := unparen(rhs).(*ast.UnaryExpr); x != nil && x.Op == token.ARROW { valid = true } } if !valid { check.error(clause.Comm.Pos(), "select case must be send or receive (possibly with assignment)") continue } check.openScope(s, "case") if clause.Comm != nil { check.stmt(inner, clause.Comm) } check.stmtList(inner, clause.Body) check.closeScope() } case *ast.ForStmt: inner |= breakOk | continueOk check.openScope(s, "for") defer check.closeScope() check.simpleStmt(s.Init) if s.Cond != nil { var x operand check.expr(&x, s.Cond) if x.mode != invalid && !isBoolean(x.typ) { check.error(s.Cond.Pos(), "non-boolean condition in for statement") } } check.simpleStmt(s.Post) // spec: "The init statement may be a short variable // declaration, but the post statement must not." if s, _ := s.Post.(*ast.AssignStmt); s != nil && s.Tok == token.DEFINE { check.softErrorf(s.Pos(), "cannot declare in post statement") check.use(s.Lhs...) // avoid follow-up errors } check.stmt(inner, s.Body) case *ast.RangeStmt: inner |= breakOk | continueOk check.openScope(s, "for") defer check.closeScope() // check expression to iterate over var x operand check.expr(&x, s.X) // determine key/value types var key, val Type if x.mode != invalid { switch typ := x.typ.Underlying().(type) { case *Basic: if isString(typ) { key = Typ[Int] val = universeRune // use 'rune' name } case *Array: key = Typ[Int] val = typ.elem case *Slice: key = Typ[Int] val = typ.elem case *Pointer: if typ, _ := typ.base.Underlying().(*Array); typ != nil { key = Typ[Int] val = typ.elem } case *Map: key = typ.key val = typ.elem case *Chan: key = typ.elem val = Typ[Invalid] if typ.dir == SendOnly { check.errorf(x.pos(), "cannot range over send-only channel %s", &x) // ok to continue } if s.Value != nil { check.errorf(s.Value.Pos(), "iteration over %s permits only one iteration variable", &x) // ok to continue } } } if key == nil { check.errorf(x.pos(), "cannot range over %s", &x) // ok to continue } // check assignment to/declaration of iteration variables // (irregular assignment, cannot easily map to existing assignment checks) // lhs expressions and initialization value (rhs) types lhs := [2]ast.Expr{s.Key, s.Value} rhs := [2]Type{key, val} // key, val may be nil if s.Tok == token.DEFINE { // short variable declaration; variable scope starts after the range clause // (the for loop opens a new scope, so variables on the lhs never redeclare // previously declared variables) var vars []*Var for i, lhs := range lhs { if lhs == nil { continue } // determine lhs variable var obj *Var if ident, _ := lhs.(*ast.Ident); ident != nil { // declare new variable name := ident.Name obj = NewVar(ident.Pos(), check.pkg, name, nil) check.recordDef(ident, obj) // _ variables don't count as new variables if name != "_" { vars = append(vars, obj) } } else { check.errorf(lhs.Pos(), "cannot declare %s", lhs) obj = NewVar(lhs.Pos(), check.pkg, "_", nil) // dummy variable } // initialize lhs variable if typ := rhs[i]; typ != nil { x.mode = value x.expr = lhs // we don't have a better rhs expression to use here x.typ = typ check.initVar(obj, &x, "range clause") } else { obj.typ = Typ[Invalid] obj.used = true // don't complain about unused variable } } // declare variables if len(vars) > 0 { for _, obj := range vars { // spec: "The scope of a constant or variable identifier declared inside // a function begins at the end of the ConstSpec or VarSpec (ShortVarDecl // for short variable declarations) and ends at the end of the innermost // containing block." scopePos := s.End() check.declare(check.scope, nil /* recordDef already called */, obj, scopePos) } } else { check.error(s.TokPos, "no new variables on left side of :=") } } else { // ordinary assignment for i, lhs := range lhs { if lhs == nil { continue } if typ := rhs[i]; typ != nil { x.mode = value x.expr = lhs // we don't have a better rhs expression to use here x.typ = typ check.assignVar(lhs, &x) } } } check.stmt(inner, s.Body) default: check.error(s.Pos(), "invalid statement") } }
func (check *Checker) openScope(s ast.Stmt, comment string) { scope := NewScope(check.scope, s.Pos(), s.End(), comment) check.recordScope(s, scope) check.scope = scope }
// Returns true if a separating semicolon is optional. // Sets multiLine to true if the statements spans multiple lines. func (p *printer) stmt(stmt ast.Stmt, multiLine *bool) (optSemi bool) { p.print(stmt.Pos()) switch s := stmt.(type) { case *ast.BadStmt: p.print("BadStmt") case *ast.DeclStmt: p.decl(s.Decl, inStmtList, multiLine) optSemi = true // decl prints terminating semicolon if necessary case *ast.EmptyStmt: // nothing to do case *ast.LabeledStmt: // a "correcting" unindent immediately following a line break // is applied before the line break if there is no comment // between (see writeWhitespace) p.print(unindent) p.expr(s.Label, multiLine) p.print(token.COLON, vtab, indent) p.linebreak(s.Stmt.Pos().Line, 0, 1, ignore, true) optSemi = p.stmt(s.Stmt, multiLine) case *ast.ExprStmt: p.expr(s.X, multiLine) case *ast.IncDecStmt: p.expr(s.X, multiLine) p.print(s.Tok) case *ast.AssignStmt: p.exprList(s.Pos(), s.Lhs, commaSep, multiLine) p.print(blank, s.TokPos, s.Tok) p.exprList(s.TokPos, s.Rhs, blankStart|commaSep, multiLine) case *ast.GoStmt: p.print(token.GO, blank) p.expr(s.Call, multiLine) case *ast.DeferStmt: p.print(token.DEFER, blank) p.expr(s.Call, multiLine) case *ast.ReturnStmt: p.print(token.RETURN) if s.Results != nil { p.exprList(s.Pos(), s.Results, blankStart|commaSep, multiLine) } case *ast.BranchStmt: p.print(s.Tok) if s.Label != nil { p.print(blank) p.expr(s.Label, multiLine) } case *ast.BlockStmt: p.block(s, 1) *multiLine = true optSemi = true case *ast.IfStmt: p.print(token.IF) p.controlClause(false, s.Init, s.Cond, nil) p.block(s.Body, 1) *multiLine = true optSemi = true if s.Else != nil { p.print(blank, token.ELSE, blank) switch s.Else.(type) { case *ast.BlockStmt, *ast.IfStmt: optSemi = p.stmt(s.Else, ignoreMultiLine) default: p.print(token.LBRACE, indent, formfeed) p.stmt(s.Else, ignoreMultiLine) p.print(unindent, formfeed, token.RBRACE) } } case *ast.CaseClause: if s.Values != nil { p.print(token.CASE) p.exprList(s.Pos(), s.Values, blankStart|commaSep, multiLine) } else { p.print(token.DEFAULT) } p.print(s.Colon, token.COLON) p.stmtList(s.Body, 1) optSemi = true // "block" without {}'s case *ast.SwitchStmt: p.print(token.SWITCH) p.controlClause(false, s.Init, s.Tag, nil) p.block(s.Body, 0) *multiLine = true optSemi = true case *ast.TypeCaseClause: if s.Types != nil { p.print(token.CASE) p.exprList(s.Pos(), s.Types, blankStart|commaSep, multiLine) } else { p.print(token.DEFAULT) } p.print(s.Colon, token.COLON) p.stmtList(s.Body, 1) optSemi = true // "block" without {}'s case *ast.TypeSwitchStmt: p.print(token.SWITCH) if s.Init != nil { p.print(blank) p.stmt(s.Init, ignoreMultiLine) p.print(token.SEMICOLON) } p.print(blank) p.stmt(s.Assign, ignoreMultiLine) p.print(blank) p.block(s.Body, 0) *multiLine = true optSemi = true case *ast.CommClause: if s.Rhs != nil { p.print(token.CASE, blank) if s.Lhs != nil { p.expr(s.Lhs, multiLine) p.print(blank, s.Tok, blank) } p.expr(s.Rhs, multiLine) } else { p.print(token.DEFAULT) } p.print(s.Colon, token.COLON) p.stmtList(s.Body, 1) optSemi = true // "block" without {}'s case *ast.SelectStmt: p.print(token.SELECT, blank) p.block(s.Body, 0) *multiLine = true optSemi = true case *ast.ForStmt: p.print(token.FOR) p.controlClause(true, s.Init, s.Cond, s.Post) p.block(s.Body, 1) *multiLine = true optSemi = true case *ast.RangeStmt: p.print(token.FOR, blank) p.expr(s.Key, multiLine) if s.Value != nil { p.print(token.COMMA, blank) p.expr(s.Value, multiLine) } p.print(blank, s.TokPos, s.Tok, blank, token.RANGE, blank) p.expr(s.X, multiLine) p.print(blank) p.block(s.Body, 1) *multiLine = true optSemi = true default: panic("unreachable") } return }
// stmt typechecks statement s. func (check *checker) stmt(s ast.Stmt) { switch s := s.(type) { case *ast.BadStmt, *ast.EmptyStmt: // ignore case *ast.DeclStmt: check.decl(s.Decl) case *ast.LabeledStmt: // TODO(gri) anything to do with label itself? check.stmt(s.Stmt) case *ast.ExprStmt: var x operand used := false switch e := unparen(s.X).(type) { case *ast.CallExpr: // function calls are permitted used = true // but some builtins are excluded check.expr(&x, e.Fun, nil, -1) if x.mode != invalid { if b, ok := x.typ.(*builtin); ok && !b.isStatement { used = false } } case *ast.UnaryExpr: // receive operations are permitted if e.Op == token.ARROW { used = true } } if !used { check.errorf(s.Pos(), "%s not used", s.X) // ok to continue } check.rawExpr(&x, s.X, nil, -1, false) if x.mode == typexpr { check.errorf(x.pos(), "%s is not an expression", x) } case *ast.SendStmt: var ch, x operand check.expr(&ch, s.Chan, nil, -1) check.expr(&x, s.Value, nil, -1) if ch.mode == invalid || x.mode == invalid { return } if tch, ok := underlying(ch.typ).(*Chan); !ok || tch.Dir&ast.SEND == 0 || !x.isAssignable(tch.Elt) { check.invalidOp(ch.pos(), "cannot send %s to channel %s", &x, &ch) } case *ast.IncDecStmt: var op token.Token switch s.Tok { case token.INC: op = token.ADD case token.DEC: op = token.SUB default: check.invalidAST(s.TokPos, "unknown inc/dec operation %s", s.Tok) return } var x, y operand check.expr(&x, s.X, nil, -1) check.expr(&y, &ast.BasicLit{ValuePos: x.pos(), Kind: token.INT, Value: "1"}, nil, -1) // use x's position check.binary(&x, &y, op, nil) check.assign1to1(s.X, nil, &x, false, -1) case *ast.AssignStmt: switch s.Tok { case token.ASSIGN, token.DEFINE: if len(s.Lhs) == 0 { check.invalidAST(s.Pos(), "missing lhs in assignment") return } check.assignNtoM(s.Lhs, s.Rhs, s.Tok == token.DEFINE, -1) default: // assignment operations if len(s.Lhs) != 1 || len(s.Rhs) != 1 { check.errorf(s.TokPos, "assignment operation %s requires single-valued expressions", s.Tok) return } // TODO(gri) make this conversion more efficient 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: check.invalidAST(s.TokPos, "unknown assignment operation %s", s.Tok) return } var x, y operand check.expr(&x, s.Lhs[0], nil, -1) check.expr(&y, s.Rhs[0], nil, -1) check.binary(&x, &y, op, nil) check.assign1to1(s.Lhs[0], nil, &x, false, -1) } case *ast.GoStmt: unimplemented() case *ast.DeferStmt: unimplemented() case *ast.ReturnStmt: sig := check.functypes[len(check.functypes)-1] if n := len(sig.Results); n > 0 { // TODO(gri) should not have to compute lhs, named every single time - clean this up lhs := make([]ast.Expr, n) named := false // if set, function has named results for i, res := range sig.Results { if len(res.Name) > 0 { // a blank (_) result parameter is a named result parameter! named = true } name := ast.NewIdent(res.Name) name.NamePos = s.Pos() name.Obj = res lhs[i] = name } if len(s.Results) > 0 || !named { // TODO(gri) assignNtoM should perhaps not require len(lhs) > 0 check.assignNtoM(lhs, s.Results, false, -1) } } else if len(s.Results) > 0 { check.errorf(s.Pos(), "no result values expected") } case *ast.BranchStmt: unimplemented() case *ast.BlockStmt: check.stmtList(s.List) case *ast.IfStmt: check.optionalStmt(s.Init) var x operand check.expr(&x, s.Cond, nil, -1) if !isBoolean(x.typ) { check.errorf(s.Cond.Pos(), "non-boolean condition in if statement") } check.stmt(s.Body) check.optionalStmt(s.Else) case *ast.SwitchStmt: check.optionalStmt(s.Init) var x operand if s.Tag != nil { check.expr(&x, s.Tag, nil, -1) } else { // TODO(gri) should provide a position (see IncDec) for good error messages x.mode = constant x.typ = Typ[UntypedBool] x.val = true } for _, s := range s.Body.List { if clause, ok := s.(*ast.CaseClause); ok { for _, expr := range clause.List { var y operand check.expr(&y, expr, nil, -1) // TODO(gri) x and y must be comparable } check.stmtList(clause.Body) } else { check.errorf(s.Pos(), "invalid AST: case clause expected") } } case *ast.TypeSwitchStmt: unimplemented() case *ast.SelectStmt: for _, s := range s.Body.List { c, ok := s.(*ast.CommClause) if !ok { check.invalidAST(s.Pos(), "communication clause expected") continue } check.optionalStmt(c.Comm) // TODO(gri) check correctness of c.Comm (must be Send/RecvStmt) check.stmtList(c.Body) } case *ast.ForStmt: check.optionalStmt(s.Init) if s.Cond != nil { var x operand check.expr(&x, s.Cond, nil, -1) if !isBoolean(x.typ) { check.errorf(s.Cond.Pos(), "non-boolean condition in for statement") } } check.optionalStmt(s.Post) check.stmt(s.Body) case *ast.RangeStmt: unimplemented() default: check.errorf(s.Pos(), "invalid statement") } }
// stmt typechecks statement s. func (check *checker) stmt(s ast.Stmt) { switch s := s.(type) { case *ast.BadStmt, *ast.EmptyStmt: // ignore case *ast.DeclStmt: check.decl(s.Decl) case *ast.LabeledStmt: // TODO(gri) anything to do with label itself? check.stmt(s.Stmt) case *ast.ExprStmt: var x operand used := false switch e := unparen(s.X).(type) { case *ast.CallExpr: // function calls are permitted used = true // but some builtins are excluded check.expr(&x, e.Fun, nil, -1) if x.mode != invalid { if b, ok := x.typ.(*builtin); ok && !b.isStatement { used = false } } case *ast.UnaryExpr: // receive operations are permitted if e.Op == token.ARROW { used = true } } if !used { check.errorf(s.Pos(), "%s not used", s.X) // ok to continue } check.rawExpr(&x, s.X, nil, -1, false) if x.mode == typexpr { check.errorf(x.pos(), "%s is not an expression", &x) } case *ast.SendStmt: var ch, x operand check.expr(&ch, s.Chan, nil, -1) check.expr(&x, s.Value, nil, -1) if ch.mode == invalid || x.mode == invalid { return } if tch, ok := underlying(ch.typ).(*Chan); !ok || tch.Dir&ast.SEND == 0 || !x.isAssignable(tch.Elt) { check.invalidOp(ch.pos(), "cannot send %s to channel %s", &x, &ch) } case *ast.IncDecStmt: var op token.Token switch s.Tok { case token.INC: op = token.ADD case token.DEC: op = token.SUB default: check.invalidAST(s.TokPos, "unknown inc/dec operation %s", s.Tok) return } var x, y operand check.expr(&x, s.X, nil, -1) check.expr(&y, &ast.BasicLit{ValuePos: x.pos(), Kind: token.INT, Value: "1"}, nil, -1) // use x's position check.binary(&x, &y, op, nil) check.assign1to1(s.X, nil, &x, false, -1) case *ast.AssignStmt: switch s.Tok { case token.ASSIGN, token.DEFINE: if len(s.Lhs) == 0 { check.invalidAST(s.Pos(), "missing lhs in assignment") return } check.assignNtoM(s.Lhs, s.Rhs, s.Tok == token.DEFINE, -1) default: // assignment operations if len(s.Lhs) != 1 || len(s.Rhs) != 1 { check.errorf(s.TokPos, "assignment operation %s requires single-valued expressions", s.Tok) return } // TODO(gri) make this conversion more efficient 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: check.invalidAST(s.TokPos, "unknown assignment operation %s", s.Tok) return } var x, y operand check.expr(&x, s.Lhs[0], nil, -1) check.expr(&y, s.Rhs[0], nil, -1) check.binary(&x, &y, op, nil) check.assign1to1(s.Lhs[0], nil, &x, false, -1) } case *ast.GoStmt: check.call(s.Call) case *ast.DeferStmt: check.call(s.Call) case *ast.ReturnStmt: sig := check.functypes[len(check.functypes)-1] if n := len(sig.Results); n > 0 { // TODO(gri) should not have to compute lhs, named every single time - clean this up lhs := make([]ast.Expr, n) named := false // if set, function has named results for i, res := range sig.Results { if len(res.Name) > 0 { // a blank (_) result parameter is a named result parameter! named = true } name := ast.NewIdent(res.Name) name.NamePos = s.Pos() name.Obj = res lhs[i] = name } if len(s.Results) > 0 || !named { // TODO(gri) assignNtoM should perhaps not require len(lhs) > 0 check.assignNtoM(lhs, s.Results, false, -1) } } else if len(s.Results) > 0 { check.errorf(s.Pos(), "no result values expected") } case *ast.BranchStmt: unimplemented() case *ast.BlockStmt: check.stmtList(s.List) case *ast.IfStmt: check.optionalStmt(s.Init) var x operand check.expr(&x, s.Cond, nil, -1) if !isBoolean(x.typ) { check.errorf(s.Cond.Pos(), "non-boolean condition in if statement") } check.stmt(s.Body) check.optionalStmt(s.Else) case *ast.SwitchStmt: check.optionalStmt(s.Init) var x operand if s.Tag != nil { check.expr(&x, s.Tag, nil, -1) } else { // TODO(gri) should provide a position (see IncDec) for good error messages x.mode = constant x.typ = Typ[UntypedBool] x.val = true } check.multipleDefaults(s.Body.List) for _, s := range s.Body.List { clause, _ := s.(*ast.CaseClause) if clause == nil { continue // error reported before } for _, expr := range clause.List { var y operand check.expr(&y, expr, nil, -1) // TODO(gri) x and y must be comparable } check.stmtList(clause.Body) } case *ast.TypeSwitchStmt: check.optionalStmt(s.Init) // A type switch guard must be of the form: // // TypeSwitchGuard = [ identifier ":=" ] PrimaryExpr "." "(" "type" ")" . // // The parser is checking syntactic correctness; // remaining syntactic errors are considered AST errors here. // TODO(gri) better factoring of error handling (invalid ASTs) // var lhs *ast.Object // lhs identifier object or nil var rhs ast.Expr switch guard := s.Assign.(type) { case *ast.ExprStmt: rhs = guard.X case *ast.AssignStmt: if len(guard.Lhs) != 1 || guard.Tok != token.DEFINE || len(guard.Rhs) != 1 { check.invalidAST(s.Pos(), "incorrect form of type switch guard") return } ident, _ := guard.Lhs[0].(*ast.Ident) if ident == nil { check.invalidAST(s.Pos(), "incorrect form of type switch guard") return } lhs = ident.Obj rhs = guard.Rhs[0] default: check.invalidAST(s.Pos(), "incorrect form of type switch guard") return } // rhs must be of the form: expr.(type) and expr must be an interface expr, _ := rhs.(*ast.TypeAssertExpr) if expr == nil || expr.Type != nil { check.invalidAST(s.Pos(), "incorrect form of type switch guard") return } var x operand check.expr(&x, expr.X, nil, -1) if x.mode == invalid { return } var T *Interface if T, _ = underlying(x.typ).(*Interface); T == nil { check.errorf(x.pos(), "%s is not an interface", &x) return } check.multipleDefaults(s.Body.List) for _, s := range s.Body.List { clause, _ := s.(*ast.CaseClause) if clause == nil { continue // error reported before } // Check each type in this type switch case. var typ Type for _, expr := range clause.List { typ = check.typOrNil(expr, false) if typ != nil && typ != Typ[Invalid] { if method, wrongType := missingMethod(typ, T); method != nil { var msg string if wrongType { msg = "%s cannot have dynamic type %s (wrong type for method %s)" } else { msg = "%s cannot have dynamic type %s (missing method %s)" } check.errorf(expr.Pos(), msg, &x, typ, method.Name) // ok to continue } } } // If lhs exists, set its type for each clause. if lhs != nil { // In clauses with a case listing exactly one type, the variable has that type; // otherwise, the variable has the type of the expression in the TypeSwitchGuard. if len(clause.List) != 1 || typ == nil { typ = x.typ } lhs.Type = typ } check.stmtList(clause.Body) } // There is only one object (lhs) associated with a lhs identifier, but that object // assumes different types for different clauses. Set it to nil when we are done so // that the type cannot be used by mistake. if lhs != nil { lhs.Type = nil } case *ast.SelectStmt: check.multipleDefaults(s.Body.List) for _, s := range s.Body.List { clause, _ := s.(*ast.CommClause) if clause == nil { continue // error reported before } check.optionalStmt(clause.Comm) // TODO(gri) check correctness of c.Comm (must be Send/RecvStmt) check.stmtList(clause.Body) } case *ast.ForStmt: check.optionalStmt(s.Init) if s.Cond != nil { var x operand check.expr(&x, s.Cond, nil, -1) if !isBoolean(x.typ) { check.errorf(s.Cond.Pos(), "non-boolean condition in for statement") } } check.optionalStmt(s.Post) check.stmt(s.Body) case *ast.RangeStmt: // check expression to iterate over decl := s.Tok == token.DEFINE var x operand check.expr(&x, s.X, nil, -1) if x.mode == invalid { // if we don't have a declaration, we can still check the loop's body if !decl { check.stmt(s.Body) } return } // determine key/value types var key, val Type switch typ := underlying(x.typ).(type) { case *Basic: if isString(typ) { key = Typ[UntypedInt] val = Typ[UntypedRune] } case *Array: key = Typ[UntypedInt] val = typ.Elt case *Slice: key = Typ[UntypedInt] val = typ.Elt case *Pointer: if typ, _ := underlying(typ.Base).(*Array); typ != nil { key = Typ[UntypedInt] val = typ.Elt } case *Map: key = typ.Key val = typ.Elt case *Chan: key = typ.Elt if typ.Dir&ast.RECV == 0 { check.errorf(x.pos(), "cannot range over send-only channel %s", &x) // ok to continue } if s.Value != nil { check.errorf(s.Value.Pos(), "iteration over %s permits only one iteration variable", &x) // ok to continue } } if key == nil { check.errorf(x.pos(), "cannot range over %s", &x) // if we don't have a declaration, we can still check the loop's body if !decl { check.stmt(s.Body) } return } // check assignment to/declaration of iteration variables // TODO(gri) The error messages/positions are not great here, // they refer to the expression in the range clause. // Should give better messages w/o too much code // duplication (assignment checking). if s.Key != nil { x.typ = key check.assign1to1(s.Key, nil, &x, decl, -1) } else { check.invalidAST(s.Pos(), "range clause requires index iteration variable") // ok to continue } if s.Value != nil { x.typ = val check.assign1to1(s.Value, nil, &x, decl, -1) } check.stmt(s.Body) default: check.errorf(s.Pos(), "invalid statement") } }
func (v *stmtVisitor) VisitStmt(s ast.Stmt) { var statements *[]ast.Stmt switch s := s.(type) { case *ast.BlockStmt: statements = &s.List case *ast.CaseClause: statements = &s.Body case *ast.CommClause: statements = &s.Body case *ast.ForStmt: if s.Init != nil { v.VisitStmt(s.Init) } if s.Post != nil { v.VisitStmt(s.Post) } v.VisitStmt(s.Body) case *ast.IfStmt: if s.Init != nil { v.VisitStmt(s.Init) } v.VisitStmt(s.Body) if s.Else != nil { v.VisitStmt(s.Else) } case *ast.LabeledStmt: v.VisitStmt(s.Stmt) case *ast.RangeStmt: v.VisitStmt(s.Body) case *ast.SelectStmt: v.VisitStmt(s.Body) case *ast.SwitchStmt: if s.Init != nil { v.VisitStmt(s.Init) } v.VisitStmt(s.Body) case *ast.TypeSwitchStmt: if s.Init != nil { v.VisitStmt(s.Init) } v.VisitStmt(s.Assign) v.VisitStmt(s.Body) } if statements == nil { return } for i := 0; i < len(*statements); i++ { s := (*statements)[i] switch s.(type) { case *ast.CaseClause, *ast.CommClause, *ast.BlockStmt: break default: start, end := v.fset.Position(s.Pos()), v.fset.Position(s.End()) stmtObj := v.functions[len(v.functions)-1].RegisterStatement(start.Offset, end.Offset) expr := makeCall(fmt.Sprint(stmtObj, ".At")) stmt := &ast.ExprStmt{X: expr} item := []ast.Stmt{stmt} *statements = append((*statements)[:i], append(item, (*statements)[i:]...)...) i++ } v.VisitStmt(s) } }
func (c *funcContext) translateStmt(stmt ast.Stmt, label string) { c.Write([]byte{'\b'}) binary.Write(c, binary.BigEndian, uint32(stmt.Pos())) switch s := stmt.(type) { case *ast.BlockStmt: c.translateStmtList(s.List) case *ast.IfStmt: c.printLabel(label) if s.Init != nil { c.translateStmt(s.Init, "") } var caseClauses []ast.Stmt ifStmt := s for { caseClauses = append(caseClauses, &ast.CaseClause{List: []ast.Expr{ifStmt.Cond}, Body: ifStmt.Body.List}) switch elseStmt := ifStmt.Else.(type) { case *ast.IfStmt: if elseStmt.Init != nil { caseClauses = append(caseClauses, &ast.CaseClause{List: nil, Body: []ast.Stmt{elseStmt}}) break } ifStmt = elseStmt continue case *ast.BlockStmt: caseClauses = append(caseClauses, &ast.CaseClause{List: nil, Body: elseStmt.List}) case *ast.EmptyStmt, nil: // no else clause default: panic(fmt.Sprintf("Unhandled else: %T\n", elseStmt)) } break } c.translateBranchingStmt(caseClauses, false, c.translateExpr, nil, "", c.hasGoto[s]) case *ast.SwitchStmt: if s.Init != nil { c.translateStmt(s.Init, "") } translateCond := func(cond ast.Expr) *expression { return c.translateExpr(cond) } if s.Tag != nil { refVar := c.newVariable("_ref") c.Printf("%s = %s;", refVar, c.translateExpr(s.Tag)) translateCond = func(cond ast.Expr) *expression { refIdent := c.newIdent(refVar, c.p.info.Types[s.Tag].Type) return c.translateExpr(&ast.BinaryExpr{ X: refIdent, Op: token.EQL, Y: cond, }) } } c.translateBranchingStmt(s.Body.List, true, translateCond, nil, label, c.hasGoto[s]) case *ast.TypeSwitchStmt: if s.Init != nil { c.translateStmt(s.Init, "") } var expr ast.Expr var typeSwitchVar string switch a := s.Assign.(type) { case *ast.AssignStmt: expr = a.Rhs[0].(*ast.TypeAssertExpr).X typeSwitchVar = c.newVariable(a.Lhs[0].(*ast.Ident).Name) for _, caseClause := range s.Body.List { c.p.objectVars[c.p.info.Implicits[caseClause]] = typeSwitchVar } case *ast.ExprStmt: expr = a.X.(*ast.TypeAssertExpr).X } refVar := c.newVariable("_ref") typeVar := c.newVariable("_type") c.Printf("%s = %s;", refVar, c.translateExpr(expr)) c.Printf("%s = %s !== null ? %s.constructor : null;", typeVar, refVar, refVar) translateCond := func(cond ast.Expr) *expression { return c.formatExpr("%s", c.typeCheck(typeVar, c.p.info.Types[cond].Type)) } printCaseBodyPrefix := func(conds []ast.Expr) { if typeSwitchVar == "" { return } value := refVar if len(conds) == 1 { t := c.p.info.Types[conds[0]].Type if _, isInterface := t.Underlying().(*types.Interface); !isInterface && !types.Identical(t, types.Typ[types.UntypedNil]) { value += ".go$val" } } c.Printf("%s = %s;", typeSwitchVar, value) } c.translateBranchingStmt(s.Body.List, true, translateCond, printCaseBodyPrefix, label, c.hasGoto[s]) case *ast.ForStmt: if s.Init != nil { c.translateStmt(s.Init, "") } cond := "true" if s.Cond != nil { cond = c.translateExpr(s.Cond).String() } c.translateLoopingStmt(cond, s.Body, nil, func() { if s.Post != nil { c.translateStmt(s.Post, "") } }, label, c.hasGoto[s]) case *ast.RangeStmt: refVar := c.newVariable("_ref") c.Printf("%s = %s;", refVar, c.translateExpr(s.X)) iVar := c.newVariable("_i") c.Printf("%s = 0;", iVar) switch t := c.p.info.Types[s.X].Type.Underlying().(type) { case *types.Basic: runeVar := c.newVariable("_rune") c.translateLoopingStmt(iVar+" < "+refVar+".length", s.Body, func() { c.Printf("%s = go$decodeRune(%s, %s);", runeVar, refVar, iVar) if !isBlank(s.Value) { c.Printf("%s", c.translateAssign(s.Value, runeVar+"[0]")) } if !isBlank(s.Key) { c.Printf("%s", c.translateAssign(s.Key, iVar)) } }, func() { c.Printf("%s += %s[1];", iVar, runeVar) }, label, c.hasGoto[s]) case *types.Map: keysVar := c.newVariable("_keys") c.Printf("%s = go$keys(%s);", keysVar, refVar) c.translateLoopingStmt(iVar+" < "+keysVar+".length", s.Body, func() { entryVar := c.newVariable("_entry") c.Printf("%s = %s[%s[%s]];", entryVar, refVar, keysVar, iVar) if !isBlank(s.Value) { c.Printf("%s", c.translateAssign(s.Value, entryVar+".v")) } if !isBlank(s.Key) { c.Printf("%s", c.translateAssign(s.Key, entryVar+".k")) } }, func() { c.Printf("%s++;", iVar) }, label, c.hasGoto[s]) case *types.Array, *types.Pointer, *types.Slice: var length string switch t2 := t.(type) { case *types.Array: length = fmt.Sprintf("%d", t2.Len()) case *types.Pointer: length = fmt.Sprintf("%d", t2.Elem().Underlying().(*types.Array).Len()) case *types.Slice: length = refVar + ".length" } c.translateLoopingStmt(iVar+" < "+length, s.Body, func() { if !isBlank(s.Value) { indexExpr := &ast.IndexExpr{ X: c.newIdent(refVar, t), Index: c.newIdent(iVar, types.Typ[types.Int]), } et := elemType(t) c.p.info.Types[indexExpr] = types.TypeAndValue{Type: et} c.Printf("%s", c.translateAssign(s.Value, c.translateImplicitConversion(indexExpr, et).String())) } if !isBlank(s.Key) { c.Printf("%s", c.translateAssign(s.Key, iVar)) } }, func() { c.Printf("%s++;", iVar) }, label, c.hasGoto[s]) case *types.Chan: c.printLabel(label) // skip default: panic("") } case *ast.BranchStmt: c.printLabel(label) labelSuffix := "" data := c.flowDatas[""] if s.Label != nil { labelSuffix = " " + s.Label.Name data = c.flowDatas[s.Label.Name] } switch s.Tok { case token.BREAK: c.PrintCond(data.endCase == 0, fmt.Sprintf("break%s;", labelSuffix), fmt.Sprintf("go$s = %d; continue;", data.endCase)) case token.CONTINUE: data.postStmt() c.PrintCond(data.beginCase == 0, fmt.Sprintf("continue%s;", labelSuffix), fmt.Sprintf("go$s = %d; continue;", data.beginCase)) case token.GOTO: c.PrintCond(false, "goto "+s.Label.Name, fmt.Sprintf("go$s = %d; continue;", c.labelCases[s.Label.Name])) case token.FALLTHROUGH: // handled in CaseClause default: panic("Unhandled branch statment: " + s.Tok.String()) } case *ast.ReturnStmt: c.printLabel(label) results := s.Results if c.resultNames != nil { if len(s.Results) != 0 { c.translateStmt(&ast.AssignStmt{ Lhs: c.resultNames, Tok: token.ASSIGN, Rhs: s.Results, }, "") } results = c.resultNames } switch len(results) { case 0: c.Printf("return;") case 1: if c.sig.Results().Len() > 1 { c.Printf("return %s;", c.translateExpr(results[0])) break } v := c.translateImplicitConversion(results[0], c.sig.Results().At(0).Type()) c.delayedOutput = nil c.Printf("return %s;", v) default: values := make([]string, len(results)) for i, result := range results { values[i] = c.translateImplicitConversion(result, c.sig.Results().At(i).Type()).String() } c.delayedOutput = nil c.Printf("return [%s];", strings.Join(values, ", ")) } case *ast.DeferStmt: c.printLabel(label) if ident, isIdent := s.Call.Fun.(*ast.Ident); isIdent { if builtin, isBuiltin := c.p.info.Uses[ident].(*types.Builtin); isBuiltin { if builtin.Name() == "recover" { c.Printf("go$deferred.push({ fun: go$recover, args: [] });") return } args := make([]ast.Expr, len(s.Call.Args)) for i, arg := range s.Call.Args { args[i] = c.newIdent(c.newVariable("_arg"), c.p.info.Types[arg].Type) } call := c.translateExpr(&ast.CallExpr{ Fun: s.Call.Fun, Args: args, Ellipsis: s.Call.Ellipsis, }) c.Printf("go$deferred.push({ fun: function(%s) { %s; }, args: [%s] });", strings.Join(c.translateExprSlice(args, nil), ", "), call, strings.Join(c.translateExprSlice(s.Call.Args, nil), ", ")) return } } sig := c.p.info.Types[s.Call.Fun].Type.Underlying().(*types.Signature) args := strings.Join(c.translateArgs(sig, s.Call.Args, s.Call.Ellipsis.IsValid()), ", ") if sel, isSelector := s.Call.Fun.(*ast.SelectorExpr); isSelector { obj := c.p.info.Selections[sel].Obj() if !obj.Exported() { c.p.dependencies[obj] = true } c.Printf(`go$deferred.push({ recv: %s, method: "%s", args: [%s] });`, c.translateExpr(sel.X), sel.Sel.Name, args) return } c.Printf("go$deferred.push({ fun: %s, args: [%s] });", c.translateExpr(s.Call.Fun), args) case *ast.AssignStmt: c.printLabel(label) if 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 parts []string lhs := s.Lhs[0] switch l := lhs.(type) { case *ast.IndexExpr: lhsVar := c.newVariable("_lhs") indexVar := c.newVariable("_index") parts = append(parts, lhsVar+" = "+c.translateExpr(l.X).String()+";") parts = append(parts, indexVar+" = "+c.translateExpr(l.Index).String()+";") lhs = &ast.IndexExpr{ X: c.newIdent(lhsVar, c.p.info.Types[l.X].Type), Index: c.newIdent(indexVar, c.p.info.Types[l.Index].Type), } c.p.info.Types[lhs] = c.p.info.Types[l] case *ast.StarExpr: lhsVar := c.newVariable("_lhs") parts = append(parts, lhsVar+" = "+c.translateExpr(l.X).String()+";") lhs = &ast.StarExpr{ X: c.newIdent(lhsVar, c.p.info.Types[l.X].Type), } c.p.info.Types[lhs] = c.p.info.Types[l] case *ast.SelectorExpr: v := hasCallVisitor{c.p.info, false} ast.Walk(&v, l.X) if v.hasCall { lhsVar := c.newVariable("_lhs") parts = append(parts, lhsVar+" = "+c.translateExpr(l.X).String()+";") lhs = &ast.SelectorExpr{ X: c.newIdent(lhsVar, c.p.info.Types[l.X].Type), Sel: l.Sel, } c.p.info.Types[lhs] = c.p.info.Types[l] c.p.info.Selections[lhs.(*ast.SelectorExpr)] = c.p.info.Selections[l] } } parenExpr := &ast.ParenExpr{X: s.Rhs[0]} c.p.info.Types[parenExpr] = c.p.info.Types[s.Rhs[0]] binaryExpr := &ast.BinaryExpr{ X: lhs, Op: op, Y: parenExpr, } c.p.info.Types[binaryExpr] = c.p.info.Types[s.Lhs[0]] parts = append(parts, c.translateAssign(lhs, c.translateExpr(binaryExpr).String())) c.Printf("%s", strings.Join(parts, " ")) return } if s.Tok == token.DEFINE { for _, lhs := range s.Lhs { if !isBlank(lhs) { c.p.info.Types[lhs] = types.TypeAndValue{Type: c.p.info.Defs[lhs.(*ast.Ident)].Type()} } } } removeParens := func(e ast.Expr) ast.Expr { for { if p, isParen := e.(*ast.ParenExpr); isParen { e = p.X continue } break } return e } switch { case len(s.Lhs) == 1 && len(s.Rhs) == 1: lhs := removeParens(s.Lhs[0]) if isBlank(lhs) { v := hasCallVisitor{c.p.info, false} ast.Walk(&v, s.Rhs[0]) if v.hasCall { c.Printf("%s;", c.translateExpr(s.Rhs[0]).String()) } return } c.Printf("%s", c.translateAssign(lhs, c.translateImplicitConversion(s.Rhs[0], c.p.info.Types[s.Lhs[0]].Type).String())) case len(s.Lhs) > 1 && len(s.Rhs) == 1: tupleVar := c.newVariable("_tuple") out := tupleVar + " = " + c.translateExpr(s.Rhs[0]).String() + ";" tuple := c.p.info.Types[s.Rhs[0]].Type.(*types.Tuple) for i, lhs := range s.Lhs { lhs = removeParens(lhs) if !isBlank(lhs) { out += " " + c.translateAssign(lhs, c.translateImplicitConversion(c.newIdent(fmt.Sprintf("%s[%d]", tupleVar, i), tuple.At(i).Type()), c.p.info.Types[s.Lhs[i]].Type).String()) } } c.Printf("%s", out) case len(s.Lhs) == len(s.Rhs): parts := make([]string, len(s.Rhs)) for i, rhs := range s.Rhs { parts[i] = c.translateImplicitConversion(rhs, c.p.info.Types[s.Lhs[i]].Type).String() } tupleVar := c.newVariable("_tuple") out := tupleVar + " = [" + strings.Join(parts, ", ") + "];" for i, lhs := range s.Lhs { lhs = removeParens(lhs) if !isBlank(lhs) { out += " " + c.translateAssign(lhs, fmt.Sprintf("%s[%d]", tupleVar, i)) } } c.Printf("%s", out) default: panic("Invalid arity of AssignStmt.") } case *ast.IncDecStmt: t := c.p.info.Types[s.X].Type if iExpr, isIExpr := s.X.(*ast.IndexExpr); isIExpr { switch u := c.p.info.Types[iExpr.X].Type.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, Value: "1", } c.p.info.Types[one] = types.TypeAndValue{Type: t, Value: exact.MakeInt64(1)} c.translateStmt(&ast.AssignStmt{ Lhs: []ast.Expr{s.X}, Tok: tok, Rhs: []ast.Expr{one}, }, label) case *ast.ExprStmt: c.printLabel(label) c.Printf("%s;", c.translateExpr(s.X).String()) case *ast.DeclStmt: c.printLabel(label) decl := s.Decl.(*ast.GenDecl) switch decl.Tok { case token.VAR: for _, spec := range s.Decl.(*ast.GenDecl).Specs { valueSpec := spec.(*ast.ValueSpec) lhs := make([]ast.Expr, len(valueSpec.Names)) for i, name := range valueSpec.Names { lhs[i] = name } rhs := valueSpec.Values isTuple := false if len(rhs) == 1 { _, isTuple = c.p.info.Types[rhs[0]].Type.(*types.Tuple) } for len(rhs) < len(lhs) && !isTuple { rhs = append(rhs, nil) } c.translateStmt(&ast.AssignStmt{ Lhs: lhs, Tok: token.DEFINE, Rhs: rhs, }, "") } case token.TYPE: for _, spec := range decl.Specs { o := c.p.info.Defs[spec.(*ast.TypeSpec).Name].(*types.TypeName) c.translateType(o, false) c.initType(o) } case token.CONST: // skip, constants are inlined } case *ast.LabeledStmt: c.printLabel(label) c.translateStmt(s.Stmt, s.Label.Name) case *ast.SelectStmt: c.printLabel(label) c.Printf(`go$notSupported("select");`) case *ast.GoStmt: c.printLabel(label) c.Printf(`go$notSupported("go");`) case *ast.SendStmt: c.printLabel(label) c.Printf(`go$notSupported("send");`) case *ast.EmptyStmt: // skip default: panic(fmt.Sprintf("Unhandled statement: %T\n", s)) } }
func (a *blockCompiler) compileStmt(s ast.Stmt) { sc := &stmtCompiler{a, s.Pos(), nil} sc.compile(s) }
// Sets multiLine to true if the statements spans multiple lines. func (p *printer) stmt(stmt ast.Stmt, nextIsRBrace bool, multiLine *bool) { p.print(stmt.Pos()) switch s := stmt.(type) { case *ast.BadStmt: p.print("BadStmt") case *ast.DeclStmt: p.decl(s.Decl, multiLine) case *ast.EmptyStmt: // nothing to do case *ast.LabeledStmt: // a "correcting" unindent immediately following a line break // is applied before the line break if there is no comment // between (see writeWhitespace) p.print(unindent) p.expr(s.Label, multiLine) p.print(token.COLON, indent) if e, isEmpty := s.Stmt.(*ast.EmptyStmt); isEmpty { if !nextIsRBrace { p.print(newline, e.Pos(), token.SEMICOLON) break } } else { p.linebreak(s.Stmt.Pos().Line, 1, ignore, true) } p.stmt(s.Stmt, nextIsRBrace, multiLine) case *ast.ExprStmt: const depth = 1 p.expr0(s.X, depth, multiLine) case *ast.IncDecStmt: const depth = 1 p.expr0(s.X, depth+1, multiLine) p.print(s.Tok) case *ast.AssignStmt: var depth = 1 if len(s.Lhs) > 1 && len(s.Rhs) > 1 { depth++ } p.exprList(s.Pos(), s.Lhs, depth, commaSep, multiLine, s.TokPos) p.print(blank, s.TokPos, s.Tok) p.exprList(s.TokPos, s.Rhs, depth, blankStart|commaSep, multiLine, noPos) case *ast.GoStmt: p.print(token.GO, blank) p.expr(s.Call, multiLine) case *ast.DeferStmt: p.print(token.DEFER, blank) p.expr(s.Call, multiLine) case *ast.ReturnStmt: p.print(token.RETURN) if s.Results != nil { p.exprList(s.Pos(), s.Results, 1, blankStart|commaSep, multiLine, noPos) } case *ast.BranchStmt: p.print(s.Tok) if s.Label != nil { p.print(blank) p.expr(s.Label, multiLine) } case *ast.BlockStmt: p.block(s, 1) *multiLine = true case *ast.IfStmt: p.print(token.IF) p.controlClause(false, s.Init, s.Cond, nil) p.block(s.Body, 1) *multiLine = true if s.Else != nil { p.print(blank, token.ELSE, blank) switch s.Else.(type) { case *ast.BlockStmt, *ast.IfStmt: p.stmt(s.Else, nextIsRBrace, ignoreMultiLine) default: p.print(token.LBRACE, indent, formfeed) p.stmt(s.Else, true, ignoreMultiLine) p.print(unindent, formfeed, token.RBRACE) } } case *ast.CaseClause: if s.Values != nil { p.print(token.CASE) p.exprList(s.Pos(), s.Values, 1, blankStart|commaSep, multiLine, s.Colon) } else { p.print(token.DEFAULT) } p.print(s.Colon, token.COLON) p.stmtList(s.Body, 1, nextIsRBrace) case *ast.SwitchStmt: p.print(token.SWITCH) p.controlClause(false, s.Init, s.Tag, nil) p.block(s.Body, 0) *multiLine = true case *ast.TypeCaseClause: if s.Types != nil { p.print(token.CASE) p.exprList(s.Pos(), s.Types, 1, blankStart|commaSep, multiLine, s.Colon) } else { p.print(token.DEFAULT) } p.print(s.Colon, token.COLON) p.stmtList(s.Body, 1, nextIsRBrace) case *ast.TypeSwitchStmt: p.print(token.SWITCH) if s.Init != nil { p.print(blank) p.stmt(s.Init, false, ignoreMultiLine) p.print(token.SEMICOLON) } p.print(blank) p.stmt(s.Assign, false, ignoreMultiLine) p.print(blank) p.block(s.Body, 0) *multiLine = true case *ast.CommClause: if s.Rhs != nil { p.print(token.CASE, blank) if s.Lhs != nil { p.expr(s.Lhs, multiLine) p.print(blank, s.Tok, blank) } p.expr(s.Rhs, multiLine) } else { p.print(token.DEFAULT) } p.print(s.Colon, token.COLON) p.stmtList(s.Body, 1, nextIsRBrace) case *ast.SelectStmt: p.print(token.SELECT, blank) p.block(s.Body, 0) *multiLine = true case *ast.ForStmt: p.print(token.FOR) p.controlClause(true, s.Init, s.Cond, s.Post) p.block(s.Body, 1) *multiLine = true case *ast.RangeStmt: p.print(token.FOR, blank) p.expr(s.Key, multiLine) if s.Value != nil { p.print(token.COMMA, blank) p.expr(s.Value, multiLine) } p.print(blank, s.TokPos, s.Tok, blank, token.RANGE, blank) p.expr(stripParens(s.X), multiLine) p.print(blank) p.block(s.Body, 1) *multiLine = true default: panic("unreachable") } return }
func (p *printer) stmt(stmt ast.Stmt, nextIsRBrace bool) { p.print(stmt.Pos()) switch s := stmt.(type) { case *ast.BadStmt: p.print("BadStmt") case *ast.DeclStmt: p.decl(s.Decl) case *ast.EmptyStmt: // nothing to do case *ast.LabeledStmt: // a "correcting" unindent immediately following a line break // is applied before the line break if there is no comment // between (see writeWhitespace) p.print(unindent) p.expr(s.Label) p.print(s.Colon, token.COLON, indent) if e, isEmpty := s.Stmt.(*ast.EmptyStmt); isEmpty { if !nextIsRBrace { p.print(newline, e.Pos(), token.SEMICOLON) break } } else { p.linebreak(p.lineFor(s.Stmt.Pos()), 1, ignore, true) } p.stmt(s.Stmt, nextIsRBrace) case *ast.ExprStmt: const depth = 1 p.expr0(s.X, depth) case *ast.SendStmt: const depth = 1 p.expr0(s.Chan, depth) p.print(blank, s.Arrow, token.ARROW, blank) p.expr0(s.Value, depth) case *ast.IncDecStmt: const depth = 1 p.expr0(s.X, depth+1) p.print(s.TokPos, s.Tok) case *ast.AssignStmt: var depth = 1 if len(s.Lhs) > 1 && len(s.Rhs) > 1 { depth++ } p.exprList(s.Pos(), s.Lhs, depth, 0, s.TokPos) p.print(blank, s.TokPos, s.Tok, blank) p.exprList(s.TokPos, s.Rhs, depth, 0, token.NoPos) case *ast.GoStmt: p.print(token.GO, blank) p.expr(s.Call) case *ast.DeferStmt: p.print(token.DEFER, blank) p.expr(s.Call) case *ast.ReturnStmt: p.print(token.RETURN) if s.Results != nil { p.print(blank) p.exprList(s.Pos(), s.Results, 1, 0, token.NoPos) } case *ast.BranchStmt: p.print(s.Tok) if s.Label != nil { p.print(blank) p.expr(s.Label) } case *ast.BlockStmt: p.block(s, 1) case *ast.IfStmt: p.print(token.IF) p.controlClause(false, s.Init, s.Cond, nil) p.block(s.Body, 1) if s.Else != nil { p.print(blank, token.ELSE, blank) switch s.Else.(type) { case *ast.BlockStmt, *ast.IfStmt: p.stmt(s.Else, nextIsRBrace) default: p.print(token.LBRACE, indent, formfeed) p.stmt(s.Else, true) p.print(unindent, formfeed, token.RBRACE) } } case *ast.CaseClause: if s.List != nil { p.print(token.CASE, blank) p.exprList(s.Pos(), s.List, 1, 0, s.Colon) } else { p.print(token.DEFAULT) } p.print(s.Colon, token.COLON) p.stmtList(s.Body, 1, nextIsRBrace) case *ast.SwitchStmt: p.print(token.SWITCH) p.controlClause(false, s.Init, s.Tag, nil) p.block(s.Body, 0) case *ast.TypeSwitchStmt: p.print(token.SWITCH) if s.Init != nil { p.print(blank) p.stmt(s.Init, false) p.print(token.SEMICOLON) } p.print(blank) p.stmt(s.Assign, false) p.print(blank) p.block(s.Body, 0) case *ast.CommClause: if s.Comm != nil { p.print(token.CASE, blank) p.stmt(s.Comm, false) } else { p.print(token.DEFAULT) } p.print(s.Colon, token.COLON) p.stmtList(s.Body, 1, nextIsRBrace) case *ast.SelectStmt: p.print(token.SELECT, blank) body := s.Body if len(body.List) == 0 && !p.commentBefore(p.posFor(body.Rbrace)) { // print empty select statement w/o comments on one line p.print(body.Lbrace, token.LBRACE, body.Rbrace, token.RBRACE) } else { p.block(body, 0) } case *ast.ForStmt: p.print(token.FOR) p.controlClause(true, s.Init, s.Cond, s.Post) p.block(s.Body, 1) case *ast.RangeStmt: p.print(token.FOR, blank) p.expr(s.Key) if s.Value != nil { // use position of value following the comma as // comma position for correct comment placement p.print(s.Value.Pos(), token.COMMA, blank) p.expr(s.Value) } p.print(blank, s.TokPos, s.Tok, blank, token.RANGE, blank) p.expr(stripParens(s.X)) p.print(blank) p.block(s.Body, 1) default: panic("unreachable") } return }
func (f *File) statementBoundary(s ast.Stmt) token.Pos { // Control flow statements are easy. switch s := s.(type) { case *ast.BlockStmt: // Treat blocks like basic blocks to avoid overlapping counters. return s.Lbrace case *ast.IfStmt: found, pos := hasFuncLiteral(s.Init) if found { return pos } found, pos = hasFuncLiteral(s.Cond) if found { return pos } return s.Body.Lbrace case *ast.ForStmt: found, pos := hasFuncLiteral(s.Init) if found { return pos } found, pos = hasFuncLiteral(s.Cond) if found { return pos } found, pos = hasFuncLiteral(s.Post) if found { return pos } return s.Body.Lbrace case *ast.LabeledStmt: return f.statementBoundary(s.Stmt) case *ast.RangeStmt: found, pos := hasFuncLiteral(s.X) if found { return pos } return s.Body.Lbrace case *ast.SwitchStmt: found, pos := hasFuncLiteral(s.Init) if found { return pos } found, pos = hasFuncLiteral(s.Tag) if found { return pos } return s.Body.Lbrace case *ast.SelectStmt: return s.Body.Lbrace case *ast.TypeSwitchStmt: found, pos := hasFuncLiteral(s.Init) if found { return pos } return s.Body.Lbrace } found, pos := hasFuncLiteral(s) if found { return pos } return s.End() }
func (c *funcContext) translateStmt(stmt ast.Stmt, label *types.Label) { c.SetPos(stmt.Pos()) stmt = filter.IncDecStmt(stmt, c.p.Info) stmt = filter.Assign(stmt, c.p.Info) switch s := stmt.(type) { case *ast.BlockStmt: c.translateStmtList(s.List) case *ast.IfStmt: if s.Init != nil { c.translateStmt(s.Init, nil) } var caseClauses []ast.Stmt ifStmt := s for { caseClauses = append(caseClauses, &ast.CaseClause{List: []ast.Expr{ifStmt.Cond}, Body: ifStmt.Body.List}) switch elseStmt := ifStmt.Else.(type) { case *ast.IfStmt: if elseStmt.Init != nil { caseClauses = append(caseClauses, &ast.CaseClause{List: nil, Body: []ast.Stmt{elseStmt}}) break } ifStmt = elseStmt continue case *ast.BlockStmt: caseClauses = append(caseClauses, &ast.CaseClause{List: nil, Body: elseStmt.List}) case *ast.EmptyStmt, nil: // no else clause default: panic(fmt.Sprintf("Unhandled else: %T\n", elseStmt)) } break } c.translateBranchingStmt(caseClauses, false, nil, nil, nil, c.Flattened[s]) case *ast.SwitchStmt: if s.Init != nil { c.translateStmt(s.Init, nil) } tag := s.Tag if tag == nil { tag = ast.NewIdent("true") c.p.Types[tag] = types.TypeAndValue{Type: types.Typ[types.Bool], Value: exact.MakeBool(true)} } if c.p.Types[tag].Value == nil { refVar := c.newVariable("_ref") c.Printf("%s = %s;", refVar, c.translateExpr(tag)) tag = c.newIdent(refVar, c.p.Types[tag].Type) } translateCond := func(cond ast.Expr) *expression { return c.translateExpr(&ast.BinaryExpr{ X: tag, Op: token.EQL, Y: cond, }) } c.translateBranchingStmt(s.Body.List, true, translateCond, nil, label, c.Flattened[s]) case *ast.TypeSwitchStmt: if s.Init != nil { c.translateStmt(s.Init, nil) } refVar := c.newVariable("_ref") var expr ast.Expr var printCaseBodyPrefix func(index int) switch a := s.Assign.(type) { case *ast.AssignStmt: expr = a.Rhs[0].(*ast.TypeAssertExpr).X printCaseBodyPrefix = func(index int) { value := refVar caseClause := s.Body.List[index].(*ast.CaseClause) if len(caseClause.List) == 1 { t := c.p.Types[caseClause.List[0]].Type if _, isInterface := t.Underlying().(*types.Interface); !isInterface && !types.Identical(t, types.Typ[types.UntypedNil]) { value += ".$val" } } c.Printf("%s = %s;", c.objectName(c.p.Implicits[caseClause]), value) } case *ast.ExprStmt: expr = a.X.(*ast.TypeAssertExpr).X } c.Printf("%s = %s;", refVar, c.translateExpr(expr)) translateCond := func(cond ast.Expr) *expression { if types.Identical(c.p.Types[cond].Type, types.Typ[types.UntypedNil]) { return c.formatExpr("%s === $ifaceNil", refVar) } return c.formatExpr("$assertType(%s, %s, true)[1]", refVar, c.typeName(c.p.Types[cond].Type)) } c.translateBranchingStmt(s.Body.List, true, translateCond, printCaseBodyPrefix, label, c.Flattened[s]) case *ast.ForStmt: if s.Init != nil { c.translateStmt(s.Init, nil) } cond := func() string { if s.Cond == nil { return "true" } return c.translateExpr(s.Cond).String() } c.translateLoopingStmt(cond, s.Body, nil, func() { if s.Post != nil { c.translateStmt(s.Post, nil) } }, label, c.Flattened[s]) case *ast.RangeStmt: refVar := c.newVariable("_ref") c.Printf("%s = %s;", refVar, c.translateExpr(s.X)) switch t := c.p.Types[s.X].Type.Underlying().(type) { case *types.Basic: iVar := c.newVariable("_i") c.Printf("%s = 0;", iVar) runeVar := c.newVariable("_rune") c.translateLoopingStmt(func() string { return iVar + " < " + refVar + ".length" }, s.Body, func() { c.Printf("%s = $decodeRune(%s, %s);", runeVar, refVar, iVar) if !isBlank(s.Key) { c.Printf("%s", c.translateAssign(s.Key, iVar, types.Typ[types.Int], s.Tok == token.DEFINE)) } if !isBlank(s.Value) { c.Printf("%s", c.translateAssign(s.Value, runeVar+"[0]", types.Typ[types.Rune], s.Tok == token.DEFINE)) } }, func() { c.Printf("%s += %s[1];", iVar, runeVar) }, label, c.Flattened[s]) case *types.Map: iVar := c.newVariable("_i") c.Printf("%s = 0;", iVar) keysVar := c.newVariable("_keys") c.Printf("%s = $keys(%s);", keysVar, refVar) c.translateLoopingStmt(func() string { return iVar + " < " + keysVar + ".length" }, s.Body, func() { entryVar := c.newVariable("_entry") c.Printf("%s = %s[%s[%s]];", entryVar, refVar, keysVar, iVar) c.translateStmt(&ast.IfStmt{ Cond: c.newIdent(entryVar+" === undefined", types.Typ[types.Bool]), Body: &ast.BlockStmt{List: []ast.Stmt{&ast.BranchStmt{Tok: token.CONTINUE}}}, }, nil) if !isBlank(s.Key) { c.Printf("%s", c.translateAssign(s.Key, entryVar+".k", t.Key(), s.Tok == token.DEFINE)) } if !isBlank(s.Value) { c.Printf("%s", c.translateAssign(s.Value, entryVar+".v", t.Elem(), s.Tok == token.DEFINE)) } }, func() { c.Printf("%s++;", iVar) }, label, c.Flattened[s]) case *types.Array, *types.Pointer, *types.Slice: var length string var elemType types.Type switch t2 := t.(type) { case *types.Array: length = fmt.Sprintf("%d", t2.Len()) elemType = t2.Elem() case *types.Pointer: length = fmt.Sprintf("%d", t2.Elem().Underlying().(*types.Array).Len()) elemType = t2.Elem().Underlying().(*types.Array).Elem() case *types.Slice: length = refVar + ".$length" elemType = t2.Elem() } iVar := c.newVariable("_i") c.Printf("%s = 0;", iVar) c.translateLoopingStmt(func() string { return iVar + " < " + length }, s.Body, func() { if !isBlank(s.Key) { c.Printf("%s", c.translateAssign(s.Key, iVar, types.Typ[types.Int], s.Tok == token.DEFINE)) } if !isBlank(s.Value) { c.Printf("%s", c.translateAssign(s.Value, c.translateImplicitConversion(c.setType(&ast.IndexExpr{ X: c.newIdent(refVar, t), Index: c.newIdent(iVar, types.Typ[types.Int]), }, elemType), elemType).String(), elemType, s.Tok == token.DEFINE)) } }, func() { c.Printf("%s++;", iVar) }, label, c.Flattened[s]) case *types.Chan: okVar := c.newIdent(c.newVariable("_ok"), types.Typ[types.Bool]) key := s.Key tok := s.Tok if key == nil { key = ast.NewIdent("_") tok = token.ASSIGN } forStmt := &ast.ForStmt{ Body: &ast.BlockStmt{ List: []ast.Stmt{ &ast.AssignStmt{ Lhs: []ast.Expr{ key, okVar, }, Rhs: []ast.Expr{ c.setType(&ast.UnaryExpr{X: c.newIdent(refVar, t), Op: token.ARROW}, types.NewTuple(types.NewVar(0, nil, "", t.Elem()), types.NewVar(0, nil, "", types.Typ[types.Bool]))), }, Tok: tok, }, &ast.IfStmt{ Cond: &ast.UnaryExpr{X: okVar, Op: token.NOT}, Body: &ast.BlockStmt{List: []ast.Stmt{&ast.BranchStmt{Tok: token.BREAK}}}, }, s.Body, }, }, } c.Flattened[forStmt] = true c.translateStmt(forStmt, label) default: panic("") } case *ast.BranchStmt: normalLabel := "" blockingLabel := "" data := c.flowDatas[nil] if s.Label != nil { normalLabel = " " + s.Label.Name blockingLabel = " s" // use explicit label "s", because surrounding loop may not be flattened data = c.flowDatas[c.p.Uses[s.Label].(*types.Label)] } switch s.Tok { case token.BREAK: c.PrintCond(data.endCase == 0, fmt.Sprintf("break%s;", normalLabel), fmt.Sprintf("$s = %d; continue%s;", data.endCase, blockingLabel)) case token.CONTINUE: data.postStmt() c.PrintCond(data.beginCase == 0, fmt.Sprintf("continue%s;", normalLabel), fmt.Sprintf("$s = %d; continue%s;", data.beginCase, blockingLabel)) case token.GOTO: c.PrintCond(false, "goto "+s.Label.Name, fmt.Sprintf("$s = %d; continue;", c.labelCase(c.p.Uses[s.Label].(*types.Label)))) case token.FALLTHROUGH: // handled in CaseClause default: panic("Unhandled branch statment: " + s.Tok.String()) } case *ast.ReturnStmt: results := s.Results if c.resultNames != nil { if len(s.Results) != 0 { c.translateStmt(&ast.AssignStmt{ Lhs: c.resultNames, Tok: token.ASSIGN, Rhs: s.Results, }, nil) } results = c.resultNames } c.Printf("return%s;", c.translateResults(results)) case *ast.DeferStmt: isBuiltin := false isJs := false switch fun := s.Call.Fun.(type) { case *ast.Ident: var builtin *types.Builtin builtin, isBuiltin = c.p.Uses[fun].(*types.Builtin) if isBuiltin && builtin.Name() == "recover" { c.Printf("$deferred.push([$recover, []]);") return } case *ast.SelectorExpr: isJs = typesutil.IsJsPackage(c.p.Uses[fun.Sel].Pkg()) } sig := c.p.Types[s.Call.Fun].Type.Underlying().(*types.Signature) args := c.translateArgs(sig, s.Call.Args, s.Call.Ellipsis.IsValid(), true) if isBuiltin || isJs { vars := make([]string, len(s.Call.Args)) callArgs := make([]ast.Expr, len(s.Call.Args)) for i, arg := range s.Call.Args { v := c.newVariable("_arg") vars[i] = v callArgs[i] = c.newIdent(v, c.p.Types[arg].Type) } call := c.translateExpr(&ast.CallExpr{ Fun: s.Call.Fun, Args: callArgs, Ellipsis: s.Call.Ellipsis, }) c.Printf("$deferred.push([function(%s) { %s; }, [%s]]);", strings.Join(vars, ", "), call, strings.Join(args, ", ")) return } c.Printf("$deferred.push([%s, [%s]]);", c.translateExpr(s.Call.Fun), strings.Join(args, ", ")) case *ast.AssignStmt: if s.Tok != token.ASSIGN && s.Tok != token.DEFINE { panic(s.Tok) } if s.Tok == token.DEFINE { for _, lhs := range s.Lhs { if !isBlank(lhs) { obj := c.p.Defs[lhs.(*ast.Ident)] if obj == nil { obj = c.p.Uses[lhs.(*ast.Ident)] } c.setType(lhs, obj.Type()) } } } switch { case len(s.Lhs) == 1 && len(s.Rhs) == 1: lhs := astutil.RemoveParens(s.Lhs[0]) if isBlank(lhs) { if analysis.HasSideEffect(s.Rhs[0], c.p.Info.Info) { c.Printf("%s;", c.translateExpr(s.Rhs[0]).String()) } return } lhsType := c.p.Types[s.Lhs[0]].Type c.Printf("%s", c.translateAssignOfExpr(lhs, s.Rhs[0], lhsType, s.Tok == token.DEFINE)) case len(s.Lhs) > 1 && len(s.Rhs) == 1: tupleVar := c.newVariable("_tuple") out := tupleVar + " = " + c.translateExpr(s.Rhs[0]).String() + ";" tuple := c.p.Types[s.Rhs[0]].Type.(*types.Tuple) for i, lhs := range s.Lhs { lhs = astutil.RemoveParens(lhs) if !isBlank(lhs) { lhsType := c.p.Types[s.Lhs[i]].Type out += " " + c.translateAssignOfExpr(lhs, c.newIdent(fmt.Sprintf("%s[%d]", tupleVar, i), tuple.At(i).Type()), lhsType, s.Tok == token.DEFINE) } } c.Printf("%s", out) case len(s.Lhs) == len(s.Rhs): tmpVars := make([]string, len(s.Rhs)) var parts []string for i, rhs := range s.Rhs { tmpVars[i] = c.newVariable("_tmp") if isBlank(astutil.RemoveParens(s.Lhs[i])) { if analysis.HasSideEffect(rhs, c.p.Info.Info) { c.Printf("%s;", c.translateExpr(rhs).String()) } continue } lhsType := c.p.Types[s.Lhs[i]].Type parts = append(parts, c.translateAssignOfExpr(c.newIdent(tmpVars[i], c.p.Types[s.Lhs[i]].Type), rhs, lhsType, true)) } for i, lhs := range s.Lhs { lhs = astutil.RemoveParens(lhs) if !isBlank(lhs) { t := c.p.Types[lhs].Type parts = append(parts, c.translateAssignOfExpr(lhs, c.newIdent(tmpVars[i], t), t, s.Tok == token.DEFINE)) } } c.Printf("%s", strings.Join(parts, " ")) default: panic("Invalid arity of AssignStmt.") } case *ast.DeclStmt: decl := s.Decl.(*ast.GenDecl) switch decl.Tok { case token.VAR: for _, spec := range s.Decl.(*ast.GenDecl).Specs { valueSpec := spec.(*ast.ValueSpec) lhs := make([]ast.Expr, len(valueSpec.Names)) for i, name := range valueSpec.Names { lhs[i] = name } rhs := valueSpec.Values isTuple := false if len(rhs) == 1 { _, isTuple = c.p.Types[rhs[0]].Type.(*types.Tuple) } for len(rhs) < len(lhs) && !isTuple { rhs = append(rhs, nil) } c.translateStmt(&ast.AssignStmt{ Lhs: lhs, Tok: token.DEFINE, Rhs: rhs, }, nil) } case token.TYPE: for _, spec := range decl.Specs { o := c.p.Defs[spec.(*ast.TypeSpec).Name].(*types.TypeName) c.p.typeNames = append(c.p.typeNames, o) c.p.objectNames[o] = c.newVariableWithLevel(o.Name(), true) c.p.dependencies[o] = true } case token.CONST: // skip, constants are inlined } case *ast.ExprStmt: expr := c.translateExpr(s.X) if expr != nil && expr.String() != "" { c.Printf("%s;", expr) } case *ast.LabeledStmt: label := c.p.Defs[s.Label].(*types.Label) if c.GotoLabel[label] { c.PrintCond(false, s.Label.Name+":", fmt.Sprintf("case %d:", c.labelCase(label))) } c.translateStmt(s.Stmt, label) case *ast.GoStmt: c.Printf("$go(%s, [%s]);", c.translateExpr(s.Call.Fun), strings.Join(c.translateArgs(c.p.Types[s.Call.Fun].Type.Underlying().(*types.Signature), s.Call.Args, s.Call.Ellipsis.IsValid(), false), ", ")) case *ast.SendStmt: chanType := c.p.Types[s.Chan].Type.Underlying().(*types.Chan) call := &ast.CallExpr{ Fun: c.newIdent("$send", types.NewSignature(nil, types.NewTuple(types.NewVar(0, nil, "", chanType), types.NewVar(0, nil, "", chanType.Elem())), nil, false)), Args: []ast.Expr{s.Chan, s.Value}, } c.Blocking[call] = true c.translateStmt(&ast.ExprStmt{X: call}, label) case *ast.SelectStmt: var channels []string var caseClauses []ast.Stmt flattened := false hasDefault := false for i, s := range s.Body.List { clause := s.(*ast.CommClause) switch comm := clause.Comm.(type) { case nil: channels = append(channels, "[]") hasDefault = true case *ast.ExprStmt: channels = append(channels, c.formatExpr("[%e]", astutil.RemoveParens(comm.X).(*ast.UnaryExpr).X).String()) case *ast.AssignStmt: channels = append(channels, c.formatExpr("[%e]", astutil.RemoveParens(comm.Rhs[0]).(*ast.UnaryExpr).X).String()) case *ast.SendStmt: channels = append(channels, c.formatExpr("[%e, %e]", comm.Chan, comm.Value).String()) default: panic(fmt.Sprintf("unhandled: %T", comm)) } indexLit := &ast.BasicLit{Kind: token.INT} c.p.Types[indexLit] = types.TypeAndValue{Type: types.Typ[types.Int], Value: exact.MakeInt64(int64(i))} caseClauses = append(caseClauses, &ast.CaseClause{ List: []ast.Expr{indexLit}, Body: clause.Body, }) flattened = flattened || c.Flattened[clause] } selectCall := c.setType(&ast.CallExpr{ Fun: c.newIdent("$select", types.NewSignature(nil, types.NewTuple(types.NewVar(0, nil, "", types.NewInterface(nil, nil))), types.NewTuple(types.NewVar(0, nil, "", types.Typ[types.Int])), false)), Args: []ast.Expr{c.newIdent(fmt.Sprintf("[%s]", strings.Join(channels, ", ")), types.NewInterface(nil, nil))}, }, types.Typ[types.Int]) c.Blocking[selectCall] = !hasDefault selectionVar := c.newVariable("_selection") c.Printf("%s = %s;", selectionVar, c.translateExpr(selectCall)) translateCond := func(cond ast.Expr) *expression { return c.formatExpr("%s[0] === %e", selectionVar, cond) } printCaseBodyPrefix := func(index int) { if assign, ok := s.Body.List[index].(*ast.CommClause).Comm.(*ast.AssignStmt); ok { switch rhsType := c.p.Types[assign.Rhs[0]].Type.(type) { case *types.Tuple: c.translateStmt(&ast.AssignStmt{Lhs: assign.Lhs, Rhs: []ast.Expr{c.newIdent(selectionVar+"[1]", rhsType)}, Tok: assign.Tok}, nil) default: c.translateStmt(&ast.AssignStmt{Lhs: assign.Lhs, Rhs: []ast.Expr{c.newIdent(selectionVar+"[1][0]", rhsType)}, Tok: assign.Tok}, nil) } } } c.translateBranchingStmt(caseClauses, true, translateCond, printCaseBodyPrefix, label, flattened) case *ast.EmptyStmt: // skip default: panic(fmt.Sprintf("Unhandled statement: %T\n", s)) } }
// checkStmt type checks a statement. func (c *checker) checkStmt(s ast.Stmt) { if debug { fmt.Printf("Check statement: %T @ %s\n", s, c.fset.Position(s.Pos())) } switch s := s.(type) { case *ast.AssignStmt: // TODO Each left-hand side operand must be addressable, // a map index expression, or the blank identifier. Operands // may be parenthesized. if len(s.Rhs) < len(s.Lhs) { idents := make([]*ast.Ident, len(s.Lhs)) for i, e := range s.Lhs { if ident, ok := e.(*ast.Ident); ok { if ident.Obj != nil { idents[i] = ident } } else { c.checkExpr(e, nil) } } c.checkExpr(s.Rhs[0], idents) for _, ident := range idents { if ident != nil { maybeConvertUntyped(ident.Obj) } } } else { idents := make([]*ast.Ident, 1) for i, e := range s.Rhs { lhs := s.Lhs[i] if ident, ok := lhs.(*ast.Ident); ok && ident.Obj != nil { idents[0] = ident rhstyp := c.checkExpr(e, idents) if !maybeConvertUntyped(ident.Obj) { lhstyp := ident.Obj.Type.(Type) if _, untyped := rhstyp.(*Basic); untyped { c.types[e] = lhstyp } } } else { lhstyp := c.checkExpr(lhs, nil) rhstyp := c.checkExpr(e, nil) if _, untyped := rhstyp.(*Basic); untyped { c.types[e] = lhstyp } } } } case *ast.BlockStmt: for _, s := range s.List { c.checkStmt(s) } case *ast.ExprStmt: c.checkExpr(s.X, nil) case *ast.BranchStmt: // no-op case *ast.DeclStmt: // Only a GenDecl/ValueSpec is permissible in a statement. decl := s.Decl.(*ast.GenDecl) if decl.Tok == token.CONST { c.decomposeRepeatConsts(decl) } for _, spec := range decl.Specs { spec := spec.(*ast.ValueSpec) for _, name := range spec.Names { c.checkObj(name.Obj, true) } } case *ast.EmptyStmt: // no-op case *ast.ForStmt: if s.Init != nil { c.checkStmt(s.Init) } // TODO make sure cond expr is some sort of bool. if s.Cond != nil { c.checkExpr(s.Cond, nil) } if s.Post != nil { c.checkStmt(s.Post) } c.checkStmt(s.Body) case *ast.GoStmt: c.checkExpr(s.Call, nil) case *ast.IfStmt: if s.Init != nil { c.checkStmt(s.Init) } // TODO make sure cond expr is some sort of bool. c.checkExpr(s.Cond, nil) c.checkStmt(s.Body) if s.Else != nil { c.checkStmt(s.Else) } case *ast.IncDecStmt: // TODO check operand is addressable. // TODO check operand type is suitable for inc/dec. c.checkExpr(s.X, nil) case *ast.LabeledStmt: c.checkStmt(s.Stmt) case *ast.RangeStmt: var k, v Type switch x := Underlying(c.checkExpr(s.X, nil)).(type) { case *Pointer: if x, ok := Underlying(x.Base).(*Array); ok { k, v = Int, x.Elt } else { c.errorf(s.Pos(), "invalid type for range") return } case *Array: k, v = Int, x.Elt case *Slice: k, v = Int, x.Elt case *Map: k, v = x.Key, x.Elt case *Chan: k = x.Elt if s.Value != nil { c.errorf(s.Pos(), "too many variables in range") return } case *Name: if x != String { c.errorf(s.Pos(), "invalid type for range") return } k, v = Int, Rune case *Basic: if x.Kind != StringKind { c.errorf(s.Pos(), "invalid type for range") return } k, v = Int, Rune default: c.errorf(s.Pos(), "invalid type for range") return } // TODO check key, value are addressable and assignable from range // values. if s.Key != nil { if ident, ok := s.Key.(*ast.Ident); ok && ident.Obj != nil && ident.Obj.Type == nil { ident.Obj.Type = k } else { c.checkExpr(s.Key, nil) } } if s.Value != nil { if ident, ok := s.Value.(*ast.Ident); ok && ident.Obj != nil && ident.Obj.Type == nil { ident.Obj.Type = v } else { c.checkExpr(s.Value, nil) } } c.checkStmt(s.Body) case *ast.ReturnStmt: // TODO we need to check the result type(s) against the // current function's declared return type(s). We need // context for that. if c.function != nil { // Propagate the return type of the current function // so we implicitly convert constants. if len(s.Results) == len(c.function.Results) { for i, res := range c.function.Results { c.types[s.Results[i]] = res.Type.(Type) } } } for _, e := range s.Results { c.checkExpr(e, nil) } case *ast.SelectStmt: if s.Body != nil { for _, s_ := range s.Body.List { cc := s_.(*ast.CommClause) if cc.Comm != nil { c.checkStmt(cc.Comm) } for _, s := range cc.Body { c.checkStmt(s) } } } case *ast.SendStmt: c.checkExpr(s.Chan, nil) c.checkExpr(s.Value, nil) case *ast.SwitchStmt: if s.Init != nil { c.checkStmt(s.Init) } tag := Bool.Underlying // omitted tag == "true" if s.Tag != nil { tag = c.checkExpr(s.Tag, nil) } for _, s_ := range s.Body.List { cc := s_.(*ast.CaseClause) for _, e := range cc.List { // TODO check type is comparable to tag. t := c.checkExpr(e, nil) _, _ = t, tag } for _, s := range cc.Body { c.checkStmt(s) } } case *ast.TypeSwitchStmt: if s.Init != nil { c.checkStmt(s.Init) } var assignident *ast.Ident var assigntyp Type c.checkStmt(s.Assign) if assign, ok := s.Assign.(*ast.AssignStmt); ok { assignident = assign.Lhs[0].(*ast.Ident) assigntyp = assignident.Obj.Type.(Type) } for _, s_ := range s.Body.List { cc := s_.(*ast.CaseClause) for _, e := range cc.List { // TODO check expression is a type that could // satisfy the interface. if id, ok := e.(*ast.Ident); ok && id.Obj == Nil { // ... } else { typ := c.makeType(e, true) c.types[e] = typ } } // In clauses with a case listing exactly one type, the variable // has that type; otherwise, the variable has the type of the // expression in the TypeSwitchGuard. if assignident != nil { if len(cc.List) == 1 { assignident.Obj.Type = c.types[cc.List[0]] } else { assignident.Obj.Type = assigntyp } } for _, s := range cc.Body { c.checkStmt(s) } } case *ast.DeferStmt: c.checkExpr(s.Call, nil) default: panic(fmt.Sprintf("unimplemented %T", s)) } }
func (c *funcContext) translateStmt(stmt ast.Stmt, label string) { c.WritePos(stmt.Pos()) switch s := stmt.(type) { case *ast.BlockStmt: c.printLabel(label) c.translateStmtList(s.List) case *ast.IfStmt: c.printLabel(label) if s.Init != nil { c.translateStmt(s.Init, "") } var caseClauses []ast.Stmt ifStmt := s for { caseClauses = append(caseClauses, &ast.CaseClause{List: []ast.Expr{ifStmt.Cond}, Body: ifStmt.Body.List}) switch elseStmt := ifStmt.Else.(type) { case *ast.IfStmt: if elseStmt.Init != nil { caseClauses = append(caseClauses, &ast.CaseClause{List: nil, Body: []ast.Stmt{elseStmt}}) break } ifStmt = elseStmt continue case *ast.BlockStmt: caseClauses = append(caseClauses, &ast.CaseClause{List: nil, Body: elseStmt.List}) case *ast.EmptyStmt, nil: // no else clause default: panic(fmt.Sprintf("Unhandled else: %T\n", elseStmt)) } break } c.translateBranchingStmt(caseClauses, false, c.translateExpr, nil, "", c.flattened[s]) case *ast.SwitchStmt: if s.Init != nil { c.translateStmt(s.Init, "") } translateCond := func(cond ast.Expr) *expression { return c.translateExpr(cond) } if s.Tag != nil { refVar := c.newVariable("_ref") c.Printf("%s = %s;", refVar, c.translateExpr(s.Tag)) translateCond = func(cond ast.Expr) *expression { return c.translateExpr(&ast.BinaryExpr{ X: c.newIdent(refVar, c.p.info.Types[s.Tag].Type), Op: token.EQL, Y: cond, }) } } c.translateBranchingStmt(s.Body.List, true, translateCond, nil, label, c.flattened[s]) case *ast.TypeSwitchStmt: if s.Init != nil { c.translateStmt(s.Init, "") } var expr ast.Expr var typeSwitchVar string switch a := s.Assign.(type) { case *ast.AssignStmt: expr = a.Rhs[0].(*ast.TypeAssertExpr).X typeSwitchVar = c.newVariable(a.Lhs[0].(*ast.Ident).Name) for _, caseClause := range s.Body.List { c.p.objectVars[c.p.info.Implicits[caseClause]] = typeSwitchVar } case *ast.ExprStmt: expr = a.X.(*ast.TypeAssertExpr).X } refVar := c.newVariable("_ref") c.Printf("%s = %s;", refVar, c.translateExpr(expr)) translateCond := func(cond ast.Expr) *expression { if types.Identical(c.p.info.Types[cond].Type, types.Typ[types.UntypedNil]) { return c.formatExpr("%s === $ifaceNil", refVar) } return c.formatExpr("$assertType(%s, %s, true)[1]", refVar, c.typeName(c.p.info.Types[cond].Type)) } printCaseBodyPrefix := func(index int) { if typeSwitchVar == "" { return } value := refVar if conds := s.Body.List[index].(*ast.CaseClause).List; len(conds) == 1 { t := c.p.info.Types[conds[0]].Type if _, isInterface := t.Underlying().(*types.Interface); !isInterface && !types.Identical(t, types.Typ[types.UntypedNil]) { value += ".$val" } } c.Printf("%s = %s;", typeSwitchVar, value) } c.translateBranchingStmt(s.Body.List, true, translateCond, printCaseBodyPrefix, label, c.flattened[s]) case *ast.ForStmt: if s.Init != nil { c.translateStmt(s.Init, "") } cond := "true" if s.Cond != nil { cond = c.translateExpr(s.Cond).String() } c.translateLoopingStmt(cond, s.Body, nil, func() { if s.Post != nil { c.translateStmt(s.Post, "") } }, label, c.flattened[s]) case *ast.RangeStmt: refVar := c.newVariable("_ref") c.Printf("%s = %s;", refVar, c.translateExpr(s.X)) switch t := c.p.info.Types[s.X].Type.Underlying().(type) { case *types.Basic: iVar := c.newVariable("_i") c.Printf("%s = 0;", iVar) runeVar := c.newVariable("_rune") c.translateLoopingStmt(iVar+" < "+refVar+".length", s.Body, func() { c.Printf("%s = $decodeRune(%s, %s);", runeVar, refVar, iVar) if !isBlank(s.Key) { c.Printf("%s", c.translateAssign(s.Key, iVar, types.Typ[types.Int], s.Tok == token.DEFINE)) } if !isBlank(s.Value) { c.Printf("%s", c.translateAssign(s.Value, runeVar+"[0]", types.Typ[types.Rune], s.Tok == token.DEFINE)) } }, func() { c.Printf("%s += %s[1];", iVar, runeVar) }, label, c.flattened[s]) case *types.Map: iVar := c.newVariable("_i") c.Printf("%s = 0;", iVar) keysVar := c.newVariable("_keys") c.Printf("%s = $keys(%s);", keysVar, refVar) c.translateLoopingStmt(iVar+" < "+keysVar+".length", s.Body, func() { entryVar := c.newVariable("_entry") c.Printf("%s = %s[%s[%s]];", entryVar, refVar, keysVar, iVar) if !isBlank(s.Key) { c.Printf("%s", c.translateAssign(s.Key, entryVar+".k", t.Key(), s.Tok == token.DEFINE)) } if !isBlank(s.Value) { c.Printf("%s", c.translateAssign(s.Value, entryVar+".v", t.Elem(), s.Tok == token.DEFINE)) } }, func() { c.Printf("%s++;", iVar) }, label, c.flattened[s]) case *types.Array, *types.Pointer, *types.Slice: var length string var elemType types.Type switch t2 := t.(type) { case *types.Array: length = fmt.Sprintf("%d", t2.Len()) elemType = t2.Elem() case *types.Pointer: length = fmt.Sprintf("%d", t2.Elem().Underlying().(*types.Array).Len()) elemType = t2.Elem().Underlying().(*types.Array).Elem() case *types.Slice: length = refVar + ".$length" elemType = t2.Elem() } iVar := c.newVariable("_i") c.Printf("%s = 0;", iVar) c.translateLoopingStmt(iVar+" < "+length, s.Body, func() { if !isBlank(s.Key) { c.Printf("%s", c.translateAssign(s.Key, iVar, types.Typ[types.Int], s.Tok == token.DEFINE)) } if !isBlank(s.Value) { c.Printf("%s", c.translateAssign(s.Value, c.translateImplicitConversion(c.setType(&ast.IndexExpr{ X: c.newIdent(refVar, t), Index: c.newIdent(iVar, types.Typ[types.Int]), }, elemType), elemType).String(), elemType, s.Tok == token.DEFINE)) } }, func() { c.Printf("%s++;", iVar) }, label, c.flattened[s]) case *types.Chan: okVar := c.newIdent(c.newVariable("_ok"), types.Typ[types.Bool]) forStmt := &ast.ForStmt{ Body: &ast.BlockStmt{ List: []ast.Stmt{ &ast.AssignStmt{ Lhs: []ast.Expr{ s.Key, okVar, }, Rhs: []ast.Expr{ c.setType(&ast.UnaryExpr{X: c.newIdent(refVar, t), Op: token.ARROW}, types.NewTuple(types.NewVar(0, nil, "", t.Elem()), types.NewVar(0, nil, "", types.Typ[types.Bool]))), }, Tok: s.Tok, }, &ast.IfStmt{ Cond: &ast.UnaryExpr{X: okVar, Op: token.NOT}, Body: &ast.BlockStmt{List: []ast.Stmt{&ast.BranchStmt{Tok: token.BREAK}}}, }, s.Body, }, }, } c.flattened[forStmt] = true c.translateStmt(forStmt, label) default: panic("") } case *ast.BranchStmt: c.printLabel(label) labelSuffix := "" data := c.flowDatas[""] if s.Label != nil { labelSuffix = " " + s.Label.Name data = c.flowDatas[s.Label.Name] } switch s.Tok { case token.BREAK: c.PrintCond(data.endCase == 0, fmt.Sprintf("break%s;", labelSuffix), fmt.Sprintf("$s = %d; continue;", data.endCase)) case token.CONTINUE: data.postStmt() c.PrintCond(data.beginCase == 0, fmt.Sprintf("continue%s;", labelSuffix), fmt.Sprintf("$s = %d; continue;", data.beginCase)) case token.GOTO: c.PrintCond(false, "goto "+s.Label.Name, fmt.Sprintf("$s = %d; continue;", c.labelCases[s.Label.Name])) case token.FALLTHROUGH: // handled in CaseClause default: panic("Unhandled branch statment: " + s.Tok.String()) } case *ast.ReturnStmt: c.printLabel(label) results := s.Results if c.resultNames != nil { if len(s.Results) != 0 { c.translateStmt(&ast.AssignStmt{ Lhs: c.resultNames, Tok: token.ASSIGN, Rhs: s.Results, }, "") } results = c.resultNames } switch len(results) { case 0: c.Printf("return;") case 1: if c.sig.Results().Len() > 1 { c.Printf("return %s;", c.translateExpr(results[0])) return } v := c.translateImplicitConversion(results[0], c.sig.Results().At(0).Type()) c.delayedOutput = nil c.Printf("return %s;", v) default: values := make([]string, len(results)) for i, result := range results { values[i] = c.translateImplicitConversion(result, c.sig.Results().At(i).Type()).String() } c.delayedOutput = nil c.Printf("return [%s];", strings.Join(values, ", ")) } case *ast.DeferStmt: c.printLabel(label) isBuiltin := false isJs := false switch fun := s.Call.Fun.(type) { case *ast.Ident: var builtin *types.Builtin builtin, isBuiltin = c.p.info.Uses[fun].(*types.Builtin) if isBuiltin && builtin.Name() == "recover" { c.Printf("$deferred.push([$recover, []]);") return } case *ast.SelectorExpr: isJs = isJsPackage(c.p.info.Uses[fun.Sel].Pkg()) } if isBuiltin || isJs { args := make([]ast.Expr, len(s.Call.Args)) for i, arg := range s.Call.Args { args[i] = c.newIdent(c.newVariable("_arg"), c.p.info.Types[arg].Type) } call := c.translateExpr(&ast.CallExpr{ Fun: s.Call.Fun, Args: args, Ellipsis: s.Call.Ellipsis, }) c.Printf("$deferred.push([function(%s) { %s; }, [%s]]);", strings.Join(c.translateExprSlice(args, nil), ", "), call, strings.Join(c.translateExprSlice(s.Call.Args, nil), ", ")) return } sig := c.p.info.Types[s.Call.Fun].Type.Underlying().(*types.Signature) args := c.translateArgs(sig, s.Call.Args, s.Call.Ellipsis.IsValid()) if len(c.blocking) != 0 { args = append(args, "true") } c.Printf("$deferred.push([%s, [%s]]);", c.translateExpr(s.Call.Fun), strings.Join(args, ", ")) case *ast.AssignStmt: c.printLabel(label) if 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 parts []string lhs := s.Lhs[0] switch l := lhs.(type) { case *ast.IndexExpr: lhsVar := c.newVariable("_lhs") indexVar := c.newVariable("_index") parts = append(parts, lhsVar+" = "+c.translateExpr(l.X).String()+";") parts = append(parts, indexVar+" = "+c.translateExpr(l.Index).String()+";") lhs = c.setType(&ast.IndexExpr{ X: c.newIdent(lhsVar, c.p.info.Types[l.X].Type), Index: c.newIdent(indexVar, c.p.info.Types[l.Index].Type), }, c.p.info.Types[l].Type) case *ast.StarExpr: lhsVar := c.newVariable("_lhs") parts = append(parts, lhsVar+" = "+c.translateExpr(l.X).String()+";") lhs = c.setType(&ast.StarExpr{ X: c.newIdent(lhsVar, c.p.info.Types[l.X].Type), }, c.p.info.Types[l].Type) case *ast.SelectorExpr: v := hasCallVisitor{c.p.info, false} ast.Walk(&v, l.X) if v.hasCall { lhsVar := c.newVariable("_lhs") parts = append(parts, lhsVar+" = "+c.translateExpr(l.X).String()+";") lhs = c.setType(&ast.SelectorExpr{ X: c.newIdent(lhsVar, c.p.info.Types[l.X].Type), Sel: l.Sel, }, c.p.info.Types[l].Type) c.p.info.Selections[lhs.(*ast.SelectorExpr)] = c.p.info.Selections[l] } } lhsType := c.p.info.Types[s.Lhs[0]].Type parts = append(parts, c.translateAssign(lhs, c.translateExpr(c.setType(&ast.BinaryExpr{ X: lhs, Op: op, Y: c.setType(&ast.ParenExpr{X: s.Rhs[0]}, c.p.info.Types[s.Rhs[0]].Type), }, lhsType)).String(), lhsType, s.Tok == token.DEFINE)) c.Printf("%s", strings.Join(parts, " ")) return } if s.Tok == token.DEFINE { for _, lhs := range s.Lhs { if !isBlank(lhs) { obj := c.p.info.Defs[lhs.(*ast.Ident)] if obj == nil { obj = c.p.info.Uses[lhs.(*ast.Ident)] } c.setType(lhs, obj.Type()) } } } switch { case len(s.Lhs) == 1 && len(s.Rhs) == 1: lhs := removeParens(s.Lhs[0]) if isBlank(lhs) { v := hasCallVisitor{c.p.info, false} ast.Walk(&v, s.Rhs[0]) if v.hasCall { c.Printf("%s;", c.translateExpr(s.Rhs[0]).String()) } return } lhsType := c.p.info.Types[s.Lhs[0]].Type c.Printf("%s", c.translateAssignOfExpr(lhs, s.Rhs[0], lhsType, s.Tok == token.DEFINE)) case len(s.Lhs) > 1 && len(s.Rhs) == 1: tupleVar := c.newVariable("_tuple") out := tupleVar + " = " + c.translateExpr(s.Rhs[0]).String() + ";" tuple := c.p.info.Types[s.Rhs[0]].Type.(*types.Tuple) for i, lhs := range s.Lhs { lhs = removeParens(lhs) if !isBlank(lhs) { lhsType := c.p.info.Types[s.Lhs[i]].Type out += " " + c.translateAssignOfExpr(lhs, c.newIdent(fmt.Sprintf("%s[%d]", tupleVar, i), tuple.At(i).Type()), lhsType, s.Tok == token.DEFINE) } } c.Printf("%s", out) case len(s.Lhs) == len(s.Rhs): tmpVars := make([]string, len(s.Rhs)) var parts []string for i, rhs := range s.Rhs { tmpVars[i] = c.newVariable("_tmp") if isBlank(removeParens(s.Lhs[i])) { v := hasCallVisitor{c.p.info, false} ast.Walk(&v, rhs) if v.hasCall { c.Printf("%s;", c.translateExpr(rhs).String()) } continue } lhsType := c.p.info.Types[s.Lhs[i]].Type parts = append(parts, c.translateAssignOfExpr(c.newIdent(tmpVars[i], c.p.info.Types[s.Lhs[i]].Type), rhs, lhsType, true)) } for i, lhs := range s.Lhs { lhs = removeParens(lhs) if !isBlank(lhs) { parts = append(parts, c.translateAssign(lhs, tmpVars[i], c.p.info.Types[lhs].Type, s.Tok == token.DEFINE)) } } c.Printf("%s", strings.Join(parts, " ")) default: panic("Invalid arity of AssignStmt.") } case *ast.IncDecStmt: t := c.p.info.Types[s.X].Type if iExpr, isIExpr := s.X.(*ast.IndexExpr); isIExpr { switch u := c.p.info.Types[iExpr.X].Type.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 } c.translateStmt(&ast.AssignStmt{ Lhs: []ast.Expr{s.X}, Tok: tok, Rhs: []ast.Expr{c.newInt(1, t)}, }, label) case *ast.DeclStmt: c.printLabel(label) decl := s.Decl.(*ast.GenDecl) switch decl.Tok { case token.VAR: for _, spec := range s.Decl.(*ast.GenDecl).Specs { valueSpec := spec.(*ast.ValueSpec) lhs := make([]ast.Expr, len(valueSpec.Names)) for i, name := range valueSpec.Names { lhs[i] = name } rhs := valueSpec.Values isTuple := false if len(rhs) == 1 { _, isTuple = c.p.info.Types[rhs[0]].Type.(*types.Tuple) } for len(rhs) < len(lhs) && !isTuple { rhs = append(rhs, nil) } c.translateStmt(&ast.AssignStmt{ Lhs: lhs, Tok: token.DEFINE, Rhs: rhs, }, "") } case token.TYPE: for _, spec := range decl.Specs { o := c.p.info.Defs[spec.(*ast.TypeSpec).Name].(*types.TypeName) c.translateType(o, false) c.initType(o) } case token.CONST: // skip, constants are inlined } case *ast.ExprStmt: c.printLabel(label) expr := c.translateExpr(s.X) if expr != nil { c.Printf("%s;", expr) } case *ast.LabeledStmt: c.printLabel(label) c.translateStmt(s.Stmt, s.Label.Name) case *ast.GoStmt: c.printLabel(label) c.Printf("$go(%s, [%s]);", c.translateExpr(s.Call.Fun), strings.Join(c.translateArgs(c.p.info.Types[s.Call.Fun].Type.Underlying().(*types.Signature), s.Call.Args, s.Call.Ellipsis.IsValid()), ", ")) case *ast.SendStmt: chanType := c.p.info.Types[s.Chan].Type.Underlying().(*types.Chan) call := &ast.CallExpr{ Fun: c.newIdent("$send", types.NewSignature(nil, nil, types.NewTuple(types.NewVar(0, nil, "", chanType), types.NewVar(0, nil, "", chanType.Elem())), nil, false)), Args: []ast.Expr{s.Chan, s.Value}, } c.blocking[call] = true c.translateStmt(&ast.ExprStmt{call}, label) case *ast.SelectStmt: var channels []string var caseClauses []ast.Stmt flattened := false hasDefault := false for i, s := range s.Body.List { clause := s.(*ast.CommClause) switch comm := clause.Comm.(type) { case nil: channels = append(channels, "[]") hasDefault = true case *ast.ExprStmt: channels = append(channels, c.formatExpr("[%e]", removeParens(comm.X).(*ast.UnaryExpr).X).String()) case *ast.AssignStmt: channels = append(channels, c.formatExpr("[%e]", removeParens(comm.Rhs[0]).(*ast.UnaryExpr).X).String()) case *ast.SendStmt: channels = append(channels, c.formatExpr("[%e, %e]", comm.Chan, comm.Value).String()) default: panic(fmt.Sprintf("unhandled: %T", comm)) } caseClauses = append(caseClauses, &ast.CaseClause{ List: []ast.Expr{c.newInt(i, types.Typ[types.Int])}, Body: clause.Body, }) flattened = flattened || c.flattened[clause] } selectCall := c.setType(&ast.CallExpr{ Fun: c.newIdent("$select", types.NewSignature(nil, nil, types.NewTuple(types.NewVar(0, nil, "", types.NewInterface(nil, nil))), types.NewTuple(types.NewVar(0, nil, "", types.Typ[types.Int])), false)), Args: []ast.Expr{c.newIdent(fmt.Sprintf("[%s]", strings.Join(channels, ", ")), types.NewInterface(nil, nil))}, }, types.Typ[types.Int]) c.blocking[selectCall] = !hasDefault selectionVar := c.newVariable("_selection") c.Printf("%s = %s;", selectionVar, c.translateExpr(selectCall)) translateCond := func(cond ast.Expr) *expression { return c.formatExpr("%s[0] === %e", selectionVar, cond) } printCaseBodyPrefix := func(index int) { if assign, ok := s.Body.List[index].(*ast.CommClause).Comm.(*ast.AssignStmt); ok { switch rhsType := c.p.info.Types[assign.Rhs[0]].Type.(type) { case *types.Tuple: c.translateStmt(&ast.AssignStmt{Lhs: assign.Lhs, Rhs: []ast.Expr{c.newIdent(selectionVar+"[1]", rhsType)}, Tok: assign.Tok}, "") default: c.translateStmt(&ast.AssignStmt{Lhs: assign.Lhs, Rhs: []ast.Expr{c.newIdent(selectionVar+"[1][0]", rhsType)}, Tok: assign.Tok}, "") } } } c.translateBranchingStmt(caseClauses, true, translateCond, printCaseBodyPrefix, label, flattened) case *ast.EmptyStmt: // skip default: panic(fmt.Sprintf("Unhandled statement: %T\n", s)) } }
// findDead walks the statement looking for dead code. // If d.reachable is false on entry, stmt itself is dead. // When findDead returns, d.reachable tells whether the // statement following stmt is reachable. func (d *deadState) findDead(stmt ast.Stmt) { // Is this a labeled goto target? // If so, assume it is reachable due to the goto. // This is slightly conservative, in that we don't // check that the goto is reachable, so // L: goto L // will not provoke a warning. // But it's good enough. if x, isLabel := stmt.(*ast.LabeledStmt); isLabel && d.hasGoto[x.Label.Name] { d.reachable = true } if !d.reachable { switch stmt.(type) { case *ast.EmptyStmt: // do not warn about unreachable empty statements default: d.f.Bad(stmt.Pos(), "unreachable code") d.reachable = true // silence error about next statement } } switch x := stmt.(type) { default: d.f.Warnf(x.Pos(), "internal error in findDead: unexpected statement %T", x) case *ast.AssignStmt, *ast.BadStmt, *ast.DeclStmt, *ast.DeferStmt, *ast.EmptyStmt, *ast.GoStmt, *ast.IncDecStmt, *ast.SendStmt: // no control flow case *ast.BlockStmt: for _, stmt := range x.List { d.findDead(stmt) } case *ast.BranchStmt: switch x.Tok { case token.BREAK, token.GOTO, token.FALLTHROUGH: d.reachable = false case token.CONTINUE: // NOTE: We accept "continue" statements as terminating. // They are not necessary in the spec definition of terminating, // because a continue statement cannot be the final statement // before a return. But for the more general problem of syntactically // identifying dead code, continue redirects control flow just // like the other terminating statements. d.reachable = false } case *ast.ExprStmt: // Call to panic? call, ok := x.X.(*ast.CallExpr) if ok { name, ok := call.Fun.(*ast.Ident) if ok && name.Name == "panic" && name.Obj == nil { d.reachable = false } } case *ast.ForStmt: d.findDead(x.Body) d.reachable = x.Cond != nil || d.hasBreak[x] case *ast.IfStmt: d.findDead(x.Body) if x.Else != nil { r := d.reachable d.reachable = true d.findDead(x.Else) d.reachable = d.reachable || r } else { // might not have executed if statement d.reachable = true } case *ast.LabeledStmt: d.findDead(x.Stmt) case *ast.RangeStmt: d.findDead(x.Body) d.reachable = true case *ast.ReturnStmt: d.reachable = false case *ast.SelectStmt: // NOTE: Unlike switch and type switch below, we don't care // whether a select has a default, because a select without a // default blocks until one of the cases can run. That's different // from a switch without a default, which behaves like it has // a default with an empty body. anyReachable := false for _, comm := range x.Body.List { d.reachable = true for _, stmt := range comm.(*ast.CommClause).Body { d.findDead(stmt) } anyReachable = anyReachable || d.reachable } d.reachable = anyReachable || d.hasBreak[x] case *ast.SwitchStmt: anyReachable := false hasDefault := false for _, cas := range x.Body.List { cc := cas.(*ast.CaseClause) if cc.List == nil { hasDefault = true } d.reachable = true for _, stmt := range cc.Body { d.findDead(stmt) } anyReachable = anyReachable || d.reachable } d.reachable = anyReachable || d.hasBreak[x] || !hasDefault case *ast.TypeSwitchStmt: anyReachable := false hasDefault := false for _, cas := range x.Body.List { cc := cas.(*ast.CaseClause) if cc.List == nil { hasDefault = true } d.reachable = true for _, stmt := range cc.Body { d.findDead(stmt) } anyReachable = anyReachable || d.reachable } d.reachable = anyReachable || d.hasBreak[x] || !hasDefault } }