| // Copyright 2013 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 initialization and assignment checks. |
| |
| package types |
| |
| import ( |
| "go/ast" |
| "go/token" |
| ) |
| |
| // assignment reports whether x can be assigned to a variable of type T, |
| // if necessary by attempting to convert untyped values to the appropriate |
| // type. context describes the context in which the assignment takes place. |
| // Use T == nil to indicate assignment to an untyped blank identifier. |
| // x.mode is set to invalid if the assignment failed. |
| func (check *Checker) assignment(x *operand, T Type, context string) { |
| check.singleValue(x) |
| |
| switch x.mode { |
| case invalid: |
| return // error reported before |
| case constant_, variable, mapindex, value, commaok, commaerr: |
| // ok |
| default: |
| // we may get here because of other problems (issue #39634, crash 12) |
| check.errorf(x, 0, "cannot assign %s to %s in %s", x, T, context) |
| return |
| } |
| |
| if isUntyped(x.typ) { |
| target := T |
| // spec: "If an untyped constant is assigned to a variable of interface |
| // type or the blank identifier, the constant is first converted to type |
| // bool, rune, int, float64, complex128 or string respectively, depending |
| // on whether the value is a boolean, rune, integer, floating-point, |
| // complex, or string constant." |
| if T == nil || IsInterface(T) { |
| if T == nil && x.typ == Typ[UntypedNil] { |
| check.errorf(x, _UntypedNil, "use of untyped nil in %s", context) |
| x.mode = invalid |
| return |
| } |
| target = Default(x.typ) |
| } |
| newType, val, code := check.implicitTypeAndValue(x, target) |
| if code != 0 { |
| msg := check.sprintf("cannot use %s as %s value in %s", x, target, context) |
| switch code { |
| case _TruncatedFloat: |
| msg += " (truncated)" |
| case _NumericOverflow: |
| msg += " (overflows)" |
| default: |
| code = _IncompatibleAssign |
| } |
| check.error(x, code, msg) |
| x.mode = invalid |
| return |
| } |
| if val != nil { |
| x.val = val |
| check.updateExprVal(x.expr, val) |
| } |
| if newType != x.typ { |
| x.typ = newType |
| check.updateExprType(x.expr, newType, false) |
| } |
| } |
| |
| // A generic (non-instantiated) function value cannot be assigned to a variable. |
| if sig := asSignature(x.typ); sig != nil && len(sig.tparams) > 0 { |
| check.errorf(x, _Todo, "cannot use generic function %s without instantiation in %s", x, context) |
| } |
| |
| // spec: "If a left-hand side is the blank identifier, any typed or |
| // non-constant value except for the predeclared identifier nil may |
| // be assigned to it." |
| if T == nil { |
| return |
| } |
| |
| reason := "" |
| if ok, code := x.assignableTo(check, T, &reason); !ok { |
| if reason != "" { |
| check.errorf(x, code, "cannot use %s as %s value in %s: %s", x, T, context, reason) |
| } else { |
| check.errorf(x, code, "cannot use %s as %s value in %s", x, T, context) |
| } |
| x.mode = invalid |
| } |
| } |
| |
| func (check *Checker) initConst(lhs *Const, x *operand) { |
| if x.mode == invalid || x.typ == Typ[Invalid] || lhs.typ == Typ[Invalid] { |
| if lhs.typ == nil { |
| lhs.typ = Typ[Invalid] |
| } |
| return |
| } |
| |
| // rhs must be a constant |
| if x.mode != constant_ { |
| check.errorf(x, _InvalidConstInit, "%s is not constant", x) |
| if lhs.typ == nil { |
| lhs.typ = Typ[Invalid] |
| } |
| return |
| } |
| assert(isConstType(x.typ)) |
| |
| // If the lhs doesn't have a type yet, use the type of x. |
| if lhs.typ == nil { |
| lhs.typ = x.typ |
| } |
| |
| check.assignment(x, lhs.typ, "constant declaration") |
| if x.mode == invalid { |
| return |
| } |
| |
| lhs.val = x.val |
| } |
| |
| func (check *Checker) initVar(lhs *Var, x *operand, context string) Type { |
| if x.mode == invalid || x.typ == Typ[Invalid] || lhs.typ == Typ[Invalid] { |
| if lhs.typ == nil { |
| lhs.typ = Typ[Invalid] |
| } |
| return nil |
| } |
| |
| // If the lhs doesn't have a type yet, use the type of x. |
| if lhs.typ == nil { |
| typ := x.typ |
| if isUntyped(typ) { |
| // convert untyped types to default types |
| if typ == Typ[UntypedNil] { |
| check.errorf(x, _UntypedNil, "use of untyped nil in %s", context) |
| lhs.typ = Typ[Invalid] |
| return nil |
| } |
| typ = Default(typ) |
| } |
| lhs.typ = typ |
| } |
| |
| check.assignment(x, lhs.typ, context) |
| if x.mode == invalid { |
| return nil |
| } |
| |
| return x.typ |
| } |
| |
| func (check *Checker) assignVar(lhs ast.Expr, x *operand) Type { |
| if x.mode == invalid || x.typ == Typ[Invalid] { |
| check.useLHS(lhs) |
| return nil |
| } |
| |
| // Determine if the lhs is a (possibly parenthesized) identifier. |
| ident, _ := unparen(lhs).(*ast.Ident) |
| |
| // Don't evaluate lhs if it is the blank identifier. |
| if ident != nil && ident.Name == "_" { |
| check.recordDef(ident, nil) |
| check.assignment(x, nil, "assignment to _ identifier") |
| if x.mode == invalid { |
| return nil |
| } |
| return x.typ |
| } |
| |
| // If the lhs is an identifier denoting a variable v, this assignment |
| // is not a 'use' of v. Remember current value of v.used and restore |
| // after evaluating the lhs via check.expr. |
| var v *Var |
| var v_used bool |
| if ident != nil { |
| if obj := check.lookup(ident.Name); obj != nil { |
| // It's ok to mark non-local variables, but ignore variables |
| // from other packages to avoid potential race conditions with |
| // dot-imported variables. |
| if w, _ := obj.(*Var); w != nil && w.pkg == check.pkg { |
| v = w |
| v_used = v.used |
| } |
| } |
| } |
| |
| var z operand |
| check.expr(&z, lhs) |
| if v != nil { |
| v.used = v_used // restore v.used |
| } |
| |
| if z.mode == invalid || z.typ == Typ[Invalid] { |
| return nil |
| } |
| |
| // spec: "Each left-hand side operand must be addressable, a map index |
| // expression, or the blank identifier. Operands may be parenthesized." |
| switch z.mode { |
| case invalid: |
| return nil |
| case variable, mapindex: |
| // ok |
| default: |
| if sel, ok := z.expr.(*ast.SelectorExpr); ok { |
| var op operand |
| check.expr(&op, sel.X) |
| if op.mode == mapindex { |
| check.errorf(&z, _UnaddressableFieldAssign, "cannot assign to struct field %s in map", ExprString(z.expr)) |
| return nil |
| } |
| } |
| check.errorf(&z, _UnassignableOperand, "cannot assign to %s", &z) |
| return nil |
| } |
| |
| check.assignment(x, z.typ, "assignment") |
| if x.mode == invalid { |
| return nil |
| } |
| |
| return x.typ |
| } |
| |
| // If returnPos is valid, initVars is called to type-check the assignment of |
| // return expressions, and returnPos is the position of the return statement. |
| func (check *Checker) initVars(lhs []*Var, origRHS []ast.Expr, returnPos token.Pos) { |
| rhs, commaOk := check.exprList(origRHS, len(lhs) == 2 && !returnPos.IsValid()) |
| |
| if len(lhs) != len(rhs) { |
| // invalidate lhs |
| for _, obj := range lhs { |
| if obj.typ == nil { |
| obj.typ = Typ[Invalid] |
| } |
| } |
| // don't report an error if we already reported one |
| for _, x := range rhs { |
| if x.mode == invalid { |
| return |
| } |
| } |
| if returnPos.IsValid() { |
| check.errorf(atPos(returnPos), _WrongResultCount, "wrong number of return values (want %d, got %d)", len(lhs), len(rhs)) |
| return |
| } |
| check.errorf(rhs[0], _WrongAssignCount, "cannot initialize %d variables with %d values", len(lhs), len(rhs)) |
| return |
| } |
| |
| context := "assignment" |
| if returnPos.IsValid() { |
| context = "return statement" |
| } |
| |
| if commaOk { |
| var a [2]Type |
| for i := range a { |
| a[i] = check.initVar(lhs[i], rhs[i], context) |
| } |
| check.recordCommaOkTypes(origRHS[0], a) |
| return |
| } |
| |
| for i, lhs := range lhs { |
| check.initVar(lhs, rhs[i], context) |
| } |
| } |
| |
| func (check *Checker) assignVars(lhs, origRHS []ast.Expr) { |
| rhs, commaOk := check.exprList(origRHS, len(lhs) == 2) |
| |
| if len(lhs) != len(rhs) { |
| check.useLHS(lhs...) |
| // don't report an error if we already reported one |
| for _, x := range rhs { |
| if x.mode == invalid { |
| return |
| } |
| } |
| check.errorf(rhs[0], _WrongAssignCount, "cannot assign %d values to %d variables", len(rhs), len(lhs)) |
| return |
| } |
| |
| if commaOk { |
| var a [2]Type |
| for i := range a { |
| a[i] = check.assignVar(lhs[i], rhs[i]) |
| } |
| check.recordCommaOkTypes(origRHS[0], a) |
| return |
| } |
| |
| for i, lhs := range lhs { |
| check.assignVar(lhs, rhs[i]) |
| } |
| } |
| |
| func (check *Checker) shortVarDecl(pos positioner, lhs, rhs []ast.Expr) { |
| top := len(check.delayed) |
| scope := check.scope |
| |
| // collect lhs variables |
| seen := make(map[string]bool, len(lhs)) |
| lhsVars := make([]*Var, len(lhs)) |
| newVars := make([]*Var, 0, len(lhs)) |
| hasErr := false |
| for i, lhs := range lhs { |
| ident, _ := lhs.(*ast.Ident) |
| if ident == nil { |
| check.useLHS(lhs) |
| // TODO(rFindley) this is redundant with a parser error. Consider omitting? |
| check.errorf(lhs, _BadDecl, "non-name %s on left side of :=", lhs) |
| hasErr = true |
| continue |
| } |
| |
| name := ident.Name |
| if name != "_" { |
| if seen[name] { |
| check.errorf(lhs, _RepeatedDecl, "%s repeated on left side of :=", lhs) |
| hasErr = true |
| continue |
| } |
| seen[name] = true |
| } |
| |
| // Use the correct obj if the ident is redeclared. The |
| // variable's scope starts after the declaration; so we |
| // must use Scope.Lookup here and call Scope.Insert |
| // (via check.declare) later. |
| if alt := scope.Lookup(name); alt != nil { |
| check.recordUse(ident, alt) |
| // redeclared object must be a variable |
| if obj, _ := alt.(*Var); obj != nil { |
| lhsVars[i] = obj |
| } else { |
| check.errorf(lhs, _UnassignableOperand, "cannot assign to %s", lhs) |
| hasErr = true |
| } |
| continue |
| } |
| |
| // declare new variable |
| obj := NewVar(ident.Pos(), check.pkg, name, nil) |
| lhsVars[i] = obj |
| if name != "_" { |
| newVars = append(newVars, obj) |
| } |
| check.recordDef(ident, obj) |
| } |
| |
| // create dummy variables where the lhs is invalid |
| for i, obj := range lhsVars { |
| if obj == nil { |
| lhsVars[i] = NewVar(lhs[i].Pos(), check.pkg, "_", nil) |
| } |
| } |
| |
| check.initVars(lhsVars, rhs, token.NoPos) |
| |
| // process function literals in rhs expressions before scope changes |
| check.processDelayed(top) |
| |
| if len(newVars) == 0 && !hasErr { |
| check.softErrorf(pos, _NoNewVar, "no new variables on left side of :=") |
| return |
| } |
| |
| // declare new variables |
| // 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 := rhs[len(rhs)-1].End() |
| for _, obj := range newVars { |
| check.declare(scope, nil, obj, scopePos) // id = nil: recordDef already called |
| } |
| } |