| // 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" |
| ) |
| |
| // assigment reports whether x can be assigned to a variable of type 'to', |
| // if necessary by attempting to convert untyped values to the appropriate |
| // type. If x.mode == invalid upon return, then assignment has already |
| // issued an error message and the caller doesn't have to report another. |
| // TODO(gri) This latter behavior is for historic reasons and complicates |
| // callers. Needs to be cleaned up. |
| func (check *checker) assignment(x *operand, to Type) bool { |
| if x.mode == invalid { |
| return false |
| } |
| |
| if t, ok := x.typ.(*Result); ok { |
| // TODO(gri) elsewhere we use "assignment count mismatch" (consolidate) |
| check.errorf(x.pos(), "%d-valued expression %s used as single value", len(t.Values), x) |
| x.mode = invalid |
| return false |
| } |
| |
| check.convertUntyped(x, to) |
| |
| return x.mode != invalid && x.isAssignable(check.ctxt, to) |
| } |
| |
| // assign1to1 typechecks a single assignment of the form lhs = rhs (if rhs != nil), or |
| // lhs = x (if rhs == nil). If decl is set, the lhs expression must be an identifier; |
| // if its type is not set, it is deduced from the type of x or set to Typ[Invalid] in |
| // case of an error. |
| // |
| func (check *checker) assign1to1(lhs, rhs ast.Expr, x *operand, decl bool, iota int) { |
| // Start with rhs so we have an expression type |
| // for declarations with implicit type. |
| if x == nil { |
| x = new(operand) |
| check.expr(x, rhs, nil, iota) |
| // don't exit for declarations - we need the lhs first |
| if x.mode == invalid && !decl { |
| return |
| } |
| } |
| // x.mode == valid || decl |
| |
| // lhs may be an identifier |
| ident, _ := lhs.(*ast.Ident) |
| |
| // regular assignment; we know x is valid |
| if !decl { |
| // anything can be assigned to the blank identifier |
| if ident != nil && ident.Name == "_" { |
| // the rhs has its final type |
| check.updateExprType(rhs, x.typ, true) |
| return |
| } |
| |
| var z operand |
| check.expr(&z, lhs, nil, -1) |
| if z.mode == invalid { |
| return |
| } |
| |
| // TODO(gri) verify that all other z.mode values |
| // that may appear here are legal |
| if z.mode == constant || !check.assignment(x, z.typ) { |
| if x.mode != invalid { |
| check.errorf(x.pos(), "cannot assign %s to %s", x, &z) |
| } |
| } |
| return |
| } |
| |
| // declaration with initialization; lhs must be an identifier |
| if ident == nil { |
| check.errorf(lhs.Pos(), "cannot declare %s", lhs) |
| return |
| } |
| |
| // Determine typ of lhs: If the object doesn't have a type |
| // yet, determine it from the type of x; if x is invalid, |
| // set the object type to Typ[Invalid]. |
| var typ Type |
| obj := check.lookup(ident) |
| switch obj := obj.(type) { |
| default: |
| unreachable() |
| |
| case nil: |
| // TODO(gri) is this really unreachable? |
| unreachable() |
| |
| case *Const: |
| typ = obj.Type // may already be Typ[Invalid] |
| if typ == nil { |
| typ = Typ[Invalid] |
| if x.mode != invalid { |
| typ = x.typ |
| } |
| obj.Type = typ |
| } |
| |
| case *Var: |
| typ = obj.Type // may already be Typ[Invalid] |
| if typ == nil { |
| typ = Typ[Invalid] |
| if x.mode != invalid { |
| typ = x.typ |
| if isUntyped(typ) { |
| // convert untyped types to default types |
| if typ == Typ[UntypedNil] { |
| check.errorf(x.pos(), "use of untyped nil") |
| typ = Typ[Invalid] |
| } else { |
| typ = defaultType(typ) |
| } |
| } |
| } |
| obj.Type = typ |
| } |
| } |
| |
| // nothing else to check if we don't have a valid lhs or rhs |
| if typ == Typ[Invalid] || x.mode == invalid { |
| return |
| } |
| |
| if !check.assignment(x, typ) { |
| if x.mode != invalid { |
| if x.typ != Typ[Invalid] && typ != Typ[Invalid] { |
| check.errorf(x.pos(), "cannot initialize %s (type %s) with %s", ident.Name, typ, x) |
| } |
| } |
| return |
| } |
| |
| // for constants, set their value |
| if obj, _ := obj.(*Const); obj != nil { |
| obj.Val = nil // failure case: we don't know the constant value |
| if x.mode == constant { |
| if isConstType(x.typ) { |
| obj.Val = x.val |
| } else if x.typ != Typ[Invalid] { |
| check.errorf(x.pos(), "%s has invalid constant type", x) |
| } |
| } else if x.mode != invalid { |
| check.errorf(x.pos(), "%s is not constant", x) |
| } |
| } |
| } |
| |
| // assignNtoM typechecks a general assignment. If decl is set, the lhs expressions |
| // must be identifiers; if their types are not set, they are deduced from the types |
| // of the corresponding rhs expressions, or set to Typ[Invalid] in case of an error. |
| // Precondition: len(lhs) > 0 . |
| // |
| func (check *checker) assignNtoM(lhs, rhs []ast.Expr, decl bool, iota int) { |
| assert(len(lhs) > 0) |
| |
| // If the lhs and rhs have corresponding expressions, treat each |
| // matching pair as an individual pair. |
| if len(lhs) == len(rhs) { |
| for i, e := range rhs { |
| check.assign1to1(lhs[i], e, nil, decl, iota) |
| } |
| return |
| } |
| |
| // Otherwise, the rhs must be a single expression (possibly |
| // a function call returning multiple values, or a comma-ok |
| // expression). |
| if len(rhs) == 1 { |
| // len(lhs) > 1 |
| // Start with rhs so we have expression types |
| // for declarations with implicit types. |
| var x operand |
| check.expr(&x, rhs[0], nil, iota) |
| if x.mode == invalid { |
| goto Error |
| } |
| |
| if t, _ := x.typ.(*Result); t != nil && len(lhs) == len(t.Values) { |
| // function result |
| x.mode = value |
| for i, obj := range t.Values { |
| x.expr = nil // TODO(gri) should do better here |
| x.typ = obj.Type |
| check.assign1to1(lhs[i], nil, &x, decl, iota) |
| } |
| return |
| } |
| |
| if x.mode == valueok && len(lhs) == 2 { |
| // comma-ok expression |
| x.mode = value |
| check.assign1to1(lhs[0], nil, &x, decl, iota) |
| |
| x.typ = Typ[UntypedBool] |
| check.assign1to1(lhs[1], nil, &x, decl, iota) |
| return |
| } |
| } |
| |
| check.errorf(lhs[0].Pos(), "assignment count mismatch: %d = %d", len(lhs), len(rhs)) |
| |
| Error: |
| // In case of a declaration, set all lhs types to Typ[Invalid]. |
| if decl { |
| for _, e := range lhs { |
| ident, _ := e.(*ast.Ident) |
| if ident == nil { |
| check.errorf(e.Pos(), "cannot declare %s", e) |
| continue |
| } |
| switch obj := check.lookup(ident).(type) { |
| case *Const: |
| obj.Type = Typ[Invalid] |
| case *Var: |
| obj.Type = Typ[Invalid] |
| default: |
| unreachable() |
| } |
| } |
| } |
| } |
| |
| func (check *checker) optionalStmt(s ast.Stmt) { |
| if s != nil { |
| check.stmt(s) |
| } |
| } |
| |
| func (check *checker) stmtList(list []ast.Stmt) { |
| for _, s := range list { |
| check.stmt(s) |
| } |
| } |
| |
| func (check *checker) call(call *ast.CallExpr) { |
| var x operand |
| check.rawExpr(&x, call, nil, -1, false) // don't check if value is used |
| // TODO(gri) If a builtin is called, the builtin must be valid in statement context. |
| } |
| |
| 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 |
| } |
| } |
| } |
| } |
| |
| // 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") |
| } |
| } |