in libgo/go/go/types/stmt.go [311:873]
func (check *Checker) stmt(ctxt stmtContext, s ast.Stmt) {
// 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)
}
// process collected function literals before scope changes
defer check.processDelayed(len(check.delayed))
inner := ctxt &^ (fallthroughOk | finalSwitchCase)
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
var code errorCode
switch x.mode {
default:
if kind == statement {
return
}
msg = "is not used"
code = _UnusedExpr
case builtin:
msg = "must be called"
code = _UncalledBuiltin
case typexpr:
msg = "is not an expression"
code = _NotAnExpr
}
check.errorf(&x, code, "%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 := asChan(ch.typ)
if tch == nil {
check.invalidOp(inNode(s, s.Arrow), _InvalidSend, "cannot send to non-chan type %s", ch.typ)
return
}
if tch.dir == RecvOnly {
check.invalidOp(inNode(s, s.Arrow), _InvalidSend, "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(inNode(s, 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, _NonNumericIncDec, "%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, s.TokPos)
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, "missing lhs in assignment")
return
}
if s.Tok == token.DEFINE {
check.shortVarDecl(inNode(s, 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(inNode(s, s.TokPos), _MultiValAssignOp, "assignment operation %s requires single-valued expressions", s.Tok)
return
}
op := assignOp(s.Tok)
if op == token.ILLEGAL {
check.invalidAST(atPos(s.TokPos), "unknown assignment operation %s", s.Tok)
return
}
var x operand
check.binary(&x, nil, s.Lhs[0], s.Rhs[0], op, s.TokPos)
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.lookup(obj.name); alt != nil && alt != obj {
check.errorf(s, _OutOfScopeResult, "result parameter %s not in scope at return", obj.name)
check.errorf(alt, _OutOfScopeResult, "\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], _WrongResultCount, "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, _MisplacedBreak, "break not in for, switch, or select statement")
}
case token.CONTINUE:
if ctxt&continueOk == 0 {
check.error(s, _MisplacedContinue, "continue not in for statement")
}
case token.FALLTHROUGH:
if ctxt&fallthroughOk == 0 {
msg := "fallthrough statement out of place"
code := _MisplacedFallthrough
if ctxt&finalSwitchCase != 0 {
msg = "cannot fallthrough final case in switch"
}
check.error(s, code, msg)
}
default:
check.invalidAST(s, "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, _InvalidCond, "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.invalidAST(s.Else, "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")
if x.mode != invalid && !Comparable(x.typ) && !hasNil(x.typ) {
check.errorf(&x, _InvalidExprSwitch, "cannot switch on %s (%s is not comparable)", &x, x.typ)
x.mode = invalid
}
} 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, "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
} else {
inner |= finalSwitchCase
}
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, "incorrect form of type switch guard")
return
}
lhs, _ = guard.Lhs[0].(*ast.Ident)
if lhs == nil {
check.invalidAST(s, "incorrect form of type switch guard")
return
}
if lhs.Name == "_" {
// _ := x.(type) is an invalid short variable declaration
check.softErrorf(lhs, _NoNewVar, "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, "incorrect form of type switch guard")
return
}
// rhs must be of the form: expr.(type) and expr must be an ordinary interface
expr, _ := rhs.(*ast.TypeAssertExpr)
if expr == nil || expr.Type != nil {
check.invalidAST(s, "incorrect form of type switch guard")
return
}
var x operand
check.expr(&x, expr.X)
if x.mode == invalid {
return
}
xtyp, _ := under(x.typ).(*Interface)
if xtyp == nil {
check.errorf(&x, _InvalidTypeSwitch, "%s is not an interface", &x)
return
}
check.ordinaryType(&x, xtyp)
check.multipleDefaults(s.Body.List)
var lhsVars []*Var // list of implicitly declared lhs variables
seen := make(map[Type]ast.Expr) // map of seen types to positions
for _, s := range s.Body.List {
clause, _ := s.(*ast.CaseClause)
if clause == nil {
check.invalidAST(s, "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.Pos() + token.Pos(len("default")) // for default clause (len(List) == 0)
if n := len(clause.List); n > 0 {
scopePos = clause.List[n-1].End()
}
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, _UnusedVar, "%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, _InvalidSelectCase, "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, _InvalidCond, "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, _InvalidPostDecl, "cannot declare in post statement")
// Don't call useLHS here because we want to use the lhs in
// this erroneous statement so that we don't get errors about
// these lhs variables being declared but not used.
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 {
typ := optype(x.typ)
if _, ok := typ.(*Chan); ok && s.Value != nil {
// TODO(gri) this also needs to happen for channels in generic variables
check.softErrorf(atPos(s.Value.Pos()), _InvalidIterVar, "range over %s permits only one iteration variable", &x)
// ok to continue
}
var msg string
key, val, msg = rangeKeyVal(typ, isVarName(s.Key), isVarName(s.Value))
if key == nil || msg != "" {
if msg != "" {
// TODO(rFindley) should this be parenthesized, to be consistent with other qualifiers?
msg = ": " + msg
}
check.softErrorf(&x, _InvalidRangeExpr, "cannot range over %s%s", &x, msg)
// 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.invalidAST(lhs, "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 {
scopePos := s.X.End()
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."
check.declare(check.scope, nil /* recordDef already called */, obj, scopePos)
}
} else {
check.error(inNode(s, s.TokPos), _NoNewVar, "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.invalidAST(s, "invalid statement")
}
}