| // Copyright 2012 The Go Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| // This file implements typechecking of statements. |
| |
| package types |
| |
| import ( |
| "go/ast" |
| "go/token" |
| ) |
| |
| func (check *checker) optionalStmt(s ast.Stmt) { |
| if s != nil { |
| scope := check.topScope |
| check.stmt(s, false) |
| assert(check.topScope == scope) |
| } |
| } |
| |
| func (check *checker) stmtList(list []ast.Stmt, fallthroughOk bool) { |
| scope := check.topScope |
| for i, s := range list { |
| check.stmt(s, fallthroughOk && i+1 == len(list)) |
| } |
| assert(check.topScope == scope) |
| } |
| |
| 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 (check *checker) openScope(node ast.Node) { |
| s := NewScope(check.topScope) |
| check.recordScope(node, s) |
| check.topScope = s |
| } |
| |
| func (check *checker) closeScope() { |
| check.topScope = check.topScope.Parent() |
| } |
| |
| // stmt typechecks statement s. |
| func (check *checker) stmt(s ast.Stmt, fallthroughOk bool) { |
| // statements cannot use iota in general |
| // (constant declarations set it explicitly) |
| assert(check.iota == nil) |
| |
| switch s := s.(type) { |
| case *ast.BadStmt, *ast.EmptyStmt: |
| // ignore |
| |
| case *ast.DeclStmt: |
| check.declStmt(s.Decl) |
| |
| case *ast.LabeledStmt: |
| scope := check.funcSig.labels |
| if scope == nil { |
| scope = NewScope(nil) // no label scope chain |
| check.funcSig.labels = scope |
| } |
| label := s.Label |
| check.declareObj(scope, label, NewLabel(label.Pos(), label.Name)) |
| check.stmt(s.Stmt, fallthroughOk) |
| |
| 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. Perhaps this can be avoided.) |
| check.expr(&x, e.Fun) |
| 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) |
| 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) |
| 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.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) |
| 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.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 |
| } |
| // 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) |
| if x.mode == invalid { |
| return |
| } |
| check.assignVar(s.Lhs[0], &x) |
| } |
| |
| case *ast.GoStmt: |
| var x operand |
| check.call(&x, s.Call) |
| // TODO(gri) If a builtin is called, the builtin must be valid this context. |
| |
| case *ast.DeferStmt: |
| var x operand |
| check.call(&x, s.Call) |
| // TODO(gri) If a builtin is called, the builtin must be valid this context. |
| |
| 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, false) |
| return |
| } |
| } else if len(s.Results) > 0 { |
| check.errorf(s.Pos(), "no result values expected") |
| } |
| |
| case *ast.BranchStmt: |
| switch s.Tok { |
| case token.BREAK: |
| // TODO(gri) implement checks |
| case token.CONTINUE: |
| // TODO(gri) implement checks |
| case token.GOTO: |
| // TODO(gri) implement checks |
| case token.FALLTHROUGH: |
| if s.Label != nil { |
| check.invalidAST(s.Label.Pos(), "fallthrough statement cannot have label") |
| // ok to continue |
| } |
| if !fallthroughOk { |
| check.errorf(s.Pos(), "fallthrough statement out of place") |
| } |
| default: |
| check.invalidAST(s.Pos(), "unknown branch statement (%s)", s.Tok) |
| } |
| |
| case *ast.BlockStmt: |
| check.openScope(s) |
| check.stmtList(s.List, false) |
| check.closeScope() |
| |
| case *ast.IfStmt: |
| check.openScope(s) |
| check.optionalStmt(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(s.Body, false) |
| check.optionalStmt(s.Else) |
| check.closeScope() |
| |
| case *ast.SwitchStmt: |
| check.openScope(s) |
| 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.recordObject(ident, Universe.Lookup("true")) |
| tag = ident |
| } |
| check.expr(&x, tag) |
| |
| check.multipleDefaults(s.Body.List) |
| // TODO(gri) check also correct use of fallthrough |
| seen := make(map[interface{}]token.Pos) |
| for i, c := range s.Body.List { |
| clause, _ := c.(*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) |
| 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.openScope(clause) |
| check.stmtList(clause.Body, i+1 < len(s.Body.List)) |
| check.closeScope() |
| } |
| check.closeScope() |
| |
| case *ast.TypeSwitchStmt: |
| check.openScope(s) |
| defer check.closeScope() |
| 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.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 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) |
| 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 T Type |
| for _, expr := range clause.List { |
| T = check.typOrNil(expr) |
| if T != nil && T != Typ[Invalid] { |
| check.typeAssertion(expr.Pos(), &x, xtyp, T) |
| } |
| } |
| 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) |
| check.declareObj(check.topScope, nil, obj) |
| check.recordImplicit(clause, obj) |
| } |
| check.stmtList(clause.Body, false) |
| check.closeScope() |
| } |
| |
| 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.openScope(clause) |
| check.optionalStmt(clause.Comm) // TODO(gri) check correctness of c.Comm (must be Send/RecvStmt) |
| check.stmtList(clause.Body, false) |
| check.closeScope() |
| } |
| |
| case *ast.ForStmt: |
| check.openScope(s) |
| check.optionalStmt(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.optionalStmt(s.Post) |
| check.stmt(s.Body, false) |
| check.closeScope() |
| |
| case *ast.RangeStmt: |
| 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(s.Body, false) |
| } |
| return |
| } |
| |
| // determine key/value types |
| var key, val Type |
| switch typ := x.typ.Underlying().(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, _ := typ.base.Underlying().(*Array); typ != nil { |
| key = Typ[UntypedInt] |
| val = typ.elt |
| } |
| case *Map: |
| key = typ.key |
| val = typ.elt |
| case *Chan: |
| key = typ.elt |
| 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(s.Body, false) |
| } |
| 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 { |
| // declaration; variable scope starts after the range clause |
| var idents []*ast.Ident |
| var vars []*Var |
| for i, lhs := range lhs { |
| if lhs == nil { |
| continue |
| } |
| |
| // determine lhs variable |
| name := "_" // dummy, in case lhs is not an identifier |
| ident, _ := lhs.(*ast.Ident) |
| if ident != nil { |
| name = ident.Name |
| } else { |
| check.errorf(lhs.Pos(), "cannot declare %s", lhs) |
| } |
| idents = append(idents, ident) |
| |
| obj := NewVar(lhs.Pos(), check.pkg, name, nil) |
| vars = append(vars, obj) |
| |
| // 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 |
| for i, ident := range idents { |
| check.declareObj(check.topScope, ident, vars[i]) |
| } |
| } 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(s.Body, false) |
| |
| default: |
| check.errorf(s.Pos(), "invalid statement") |
| } |
| } |