mirror of
https://github.com/golang/go
synced 2024-11-18 21:05:02 -07:00
go.tools/go/types: assignment checking cleanup (round 2)
- consolited remainign assignment check routines - removed more dead code - fixed incorrect scope hierarchy in case of errors for some statements - fixed scope of key iteration variable for range clauses R=adonovan CC=golang-dev https://golang.org/cl/10694044
This commit is contained in:
parent
06a43b8a0c
commit
b8f13c4c9b
@ -68,7 +68,9 @@ type Context struct {
|
|||||||
// is nil if the identifier was not declared. Ident may be called
|
// is nil if the identifier was not declared. Ident may be called
|
||||||
// multiple times for the same identifier (e.g., for typed variable
|
// multiple times for the same identifier (e.g., for typed variable
|
||||||
// declarations with multiple initialization statements); but Ident
|
// declarations with multiple initialization statements); but Ident
|
||||||
// will report the same obj for a given id in this case.
|
// will report the same obj for a given id in this case. The object
|
||||||
|
// may not be fully set up at the time of the callback (e.g., its type
|
||||||
|
// may be nil and only filled in later).
|
||||||
Ident func(id *ast.Ident, obj Object)
|
Ident func(id *ast.Ident, obj Object)
|
||||||
// TODO(gri) Can we make this stronger, so that Ident is called
|
// TODO(gri) Can we make this stronger, so that Ident is called
|
||||||
// always exactly once (w/o resorting to a map, internally)?
|
// always exactly once (w/o resorting to a map, internally)?
|
||||||
|
@ -2,6 +2,8 @@
|
|||||||
// Use of this source code is governed by a BSD-style
|
// Use of this source code is governed by a BSD-style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// This file implements initialization and assignment checks.
|
||||||
|
|
||||||
package types
|
package types
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@ -10,6 +12,29 @@ import (
|
|||||||
"code.google.com/p/go.tools/go/exact"
|
"code.google.com/p/go.tools/go/exact"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// assignment 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.(*Tuple); ok {
|
||||||
|
// TODO(gri) elsewhere we use "assignment count mismatch" (consolidate)
|
||||||
|
check.errorf(x.pos(), "%d-valued expression %s used as single value", t.Len(), x)
|
||||||
|
x.mode = invalid
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
check.convertUntyped(x, to)
|
||||||
|
|
||||||
|
return x.mode != invalid && x.isAssignable(check.ctxt, to)
|
||||||
|
}
|
||||||
|
|
||||||
func (check *checker) initConst(lhs *Const, x *operand) {
|
func (check *checker) initConst(lhs *Const, x *operand) {
|
||||||
lhs.val = exact.MakeUnknown()
|
lhs.val = exact.MakeUnknown()
|
||||||
|
|
||||||
@ -48,9 +73,7 @@ func (check *checker) initConst(lhs *Const, x *operand) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (check *checker) initVar(lhs *Var, x *operand) {
|
func (check *checker) initVar(lhs *Var, x *operand) {
|
||||||
typ := x.typ
|
if x.mode == invalid || x.typ == Typ[Invalid] || lhs.typ == Typ[Invalid] {
|
||||||
|
|
||||||
if x.mode == invalid || typ == Typ[Invalid] || lhs.typ == Typ[Invalid] {
|
|
||||||
if lhs.typ == nil {
|
if lhs.typ == nil {
|
||||||
lhs.typ = Typ[Invalid]
|
lhs.typ = Typ[Invalid]
|
||||||
}
|
}
|
||||||
@ -59,6 +82,7 @@ func (check *checker) initVar(lhs *Var, x *operand) {
|
|||||||
|
|
||||||
// If the lhs doesn't have a type yet, use the type of x.
|
// If the lhs doesn't have a type yet, use the type of x.
|
||||||
if lhs.typ == nil {
|
if lhs.typ == nil {
|
||||||
|
typ := x.typ
|
||||||
if isUntyped(typ) {
|
if isUntyped(typ) {
|
||||||
// convert untyped types to default types
|
// convert untyped types to default types
|
||||||
if typ == Typ[UntypedNil] {
|
if typ == Typ[UntypedNil] {
|
||||||
@ -78,10 +102,35 @@ func (check *checker) initVar(lhs *Var, x *operand) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func invalidateVars(list []*Var) {
|
func (check *checker) assignVar(lhs ast.Expr, x *operand) {
|
||||||
for _, obj := range list {
|
if x.mode == invalid || x.typ == Typ[Invalid] {
|
||||||
if obj.typ == nil {
|
return
|
||||||
obj.typ = Typ[Invalid]
|
}
|
||||||
|
|
||||||
|
// Don't evaluate lhs if it is the blank identifier.
|
||||||
|
if ident, _ := lhs.(*ast.Ident); ident != nil && ident.Name == "_" {
|
||||||
|
check.callIdent(ident, nil)
|
||||||
|
check.updateExprType(x.expr, x.typ, true) // rhs has its final type
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var z operand
|
||||||
|
check.expr(&z, lhs)
|
||||||
|
if z.mode == invalid || z.typ == Typ[Invalid] {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if z.mode == constant || z.mode == value {
|
||||||
|
check.errorf(z.pos(), "cannot assign to non-variable %s", &z)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO(gri) z.mode can also be valueok which in some cases is ok (maps)
|
||||||
|
// but in others isn't (channels). Complete the checks here.
|
||||||
|
|
||||||
|
if !check.assignment(x, z.typ) {
|
||||||
|
if x.mode != invalid {
|
||||||
|
check.errorf(x.pos(), "cannot assign %s to %s", x, &z)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -143,6 +192,57 @@ func (check *checker) initVars(lhs []*Var, rhs []ast.Expr, allowCommaOk bool) {
|
|||||||
invalidateVars(lhs)
|
invalidateVars(lhs)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (check *checker) assignVars(lhs, rhs []ast.Expr) {
|
||||||
|
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) {
|
||||||
|
var x operand
|
||||||
|
for i, e := range rhs {
|
||||||
|
check.expr(&x, e)
|
||||||
|
check.assignVar(lhs[i], &x)
|
||||||
|
}
|
||||||
|
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
|
||||||
|
var x operand
|
||||||
|
check.expr(&x, rhs[0])
|
||||||
|
if x.mode == invalid {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if t, ok := x.typ.(*Tuple); ok && len(lhs) == t.Len() {
|
||||||
|
// function result
|
||||||
|
for i, lhs := range lhs {
|
||||||
|
x.mode = value
|
||||||
|
x.expr = rhs[0]
|
||||||
|
x.typ = t.At(i).typ
|
||||||
|
check.assignVar(lhs, &x)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if x.mode == valueok && len(lhs) == 2 {
|
||||||
|
// comma-ok expression
|
||||||
|
x.mode = value
|
||||||
|
check.assignVar(lhs[0], &x)
|
||||||
|
|
||||||
|
x.mode = value
|
||||||
|
x.typ = Typ[UntypedBool]
|
||||||
|
check.assignVar(lhs[1], &x)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
check.errorf(rhs[0].Pos(), "assignment count mismatch: %d = %d", len(lhs), len(rhs))
|
||||||
|
}
|
||||||
|
|
||||||
func (check *checker) shortVarDecl(lhs, rhs []ast.Expr) {
|
func (check *checker) shortVarDecl(lhs, rhs []ast.Expr) {
|
||||||
scope := check.topScope
|
scope := check.topScope
|
||||||
|
|
||||||
@ -192,3 +292,11 @@ func (check *checker) shortVarDecl(lhs, rhs []ast.Expr) {
|
|||||||
check.errorf(vars[0].Pos(), "no new variables on left side of :=")
|
check.errorf(vars[0].Pos(), "no new variables on left side of :=")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func invalidateVars(list []*Var) {
|
||||||
|
for _, obj := range list {
|
||||||
|
if obj.typ == nil {
|
||||||
|
obj.typ = Typ[Invalid]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
251
go/types/stmt.go
251
go/types/stmt.go
@ -11,186 +11,19 @@ import (
|
|||||||
"go/token"
|
"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.(*Tuple); ok {
|
|
||||||
// TODO(gri) elsewhere we use "assignment count mismatch" (consolidate)
|
|
||||||
check.errorf(x.pos(), "%d-valued expression %s used as single value", t.Len(), x)
|
|
||||||
x.mode = invalid
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
check.convertUntyped(x, to)
|
|
||||||
|
|
||||||
return x.mode != invalid && x.isAssignable(check.ctxt, to)
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO(gri) assign1to1 is only used in one place now with decl = true.
|
|
||||||
// Remove and consolidate with other assignment functions.
|
|
||||||
|
|
||||||
// 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) {
|
|
||||||
// Start with rhs so we have an expression type
|
|
||||||
// for declarations with implicit type.
|
|
||||||
if x == nil {
|
|
||||||
x = new(operand)
|
|
||||||
check.expr(x, rhs)
|
|
||||||
// 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 == "_" {
|
|
||||||
check.callIdent(ident, nil)
|
|
||||||
// the rhs has its final type
|
|
||||||
check.updateExprType(rhs, x.typ, true)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var z operand
|
|
||||||
check.expr(&z, lhs)
|
|
||||||
if z.mode == invalid || z.typ == Typ[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 the type of lhs from the type of x;
|
|
||||||
// if x is invalid, set the object type to Typ[Invalid].
|
|
||||||
obj := NewVar(ident.Pos(), check.pkg, ident.Name, nil)
|
|
||||||
defer check.declare(check.topScope, ident, obj)
|
|
||||||
|
|
||||||
var typ Type = 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.typ = 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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO(gri) assignNtoM is only used in one place now.
|
|
||||||
// Remove and consolidate with other assignment functions.
|
|
||||||
|
|
||||||
// assignNtoM typechecks a regular assignment.
|
|
||||||
// Precondition: len(lhs) > 0 .
|
|
||||||
//
|
|
||||||
func (check *checker) assignNtoM(lhs, rhs []ast.Expr) {
|
|
||||||
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, false)
|
|
||||||
}
|
|
||||||
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])
|
|
||||||
if x.mode == invalid {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if t, ok := x.typ.(*Tuple); ok && len(lhs) == t.Len() {
|
|
||||||
// function result
|
|
||||||
x.mode = value
|
|
||||||
for i := 0; i < len(lhs); i++ {
|
|
||||||
obj := t.At(i)
|
|
||||||
x.expr = nil // TODO(gri) should do better here
|
|
||||||
x.typ = obj.typ
|
|
||||||
check.assign1to1(lhs[i], nil, &x, false)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if x.mode == valueok && len(lhs) == 2 {
|
|
||||||
// comma-ok expression
|
|
||||||
x.mode = value
|
|
||||||
check.assign1to1(lhs[0], nil, &x, false)
|
|
||||||
|
|
||||||
x.typ = Typ[UntypedBool]
|
|
||||||
check.assign1to1(lhs[1], nil, &x, false)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
check.errorf(lhs[0].Pos(), "assignment count mismatch: %d = %d", len(lhs), len(rhs))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (check *checker) optionalStmt(s ast.Stmt) {
|
func (check *checker) optionalStmt(s ast.Stmt) {
|
||||||
if s != nil {
|
if s != nil {
|
||||||
|
scope := check.topScope
|
||||||
check.stmt(s)
|
check.stmt(s)
|
||||||
|
assert(check.topScope == scope)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (check *checker) stmtList(list []ast.Stmt) {
|
func (check *checker) stmtList(list []ast.Stmt) {
|
||||||
for _, s := range list {
|
for _, s := range list {
|
||||||
|
scope := check.topScope
|
||||||
check.stmt(s)
|
check.stmt(s)
|
||||||
|
assert(check.topScope == scope)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -317,7 +150,7 @@ func (check *checker) stmt(s ast.Stmt) {
|
|||||||
if x.mode == invalid {
|
if x.mode == invalid {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
check.assign1to1(s.X, nil, &x, false)
|
check.assignVar(s.X, &x)
|
||||||
|
|
||||||
case *ast.AssignStmt:
|
case *ast.AssignStmt:
|
||||||
switch s.Tok {
|
switch s.Tok {
|
||||||
@ -330,7 +163,7 @@ func (check *checker) stmt(s ast.Stmt) {
|
|||||||
check.shortVarDecl(s.Lhs, s.Rhs)
|
check.shortVarDecl(s.Lhs, s.Rhs)
|
||||||
} else {
|
} else {
|
||||||
// regular assignment
|
// regular assignment
|
||||||
check.assignNtoM(s.Lhs, s.Rhs)
|
check.assignVars(s.Lhs, s.Rhs)
|
||||||
}
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
@ -373,7 +206,7 @@ func (check *checker) stmt(s ast.Stmt) {
|
|||||||
if x.mode == invalid {
|
if x.mode == invalid {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
check.assign1to1(s.Lhs[0], nil, &x, false)
|
check.assignVar(s.Lhs[0], &x)
|
||||||
}
|
}
|
||||||
|
|
||||||
case *ast.GoStmt:
|
case *ast.GoStmt:
|
||||||
@ -497,6 +330,7 @@ func (check *checker) stmt(s ast.Stmt) {
|
|||||||
|
|
||||||
case *ast.TypeSwitchStmt:
|
case *ast.TypeSwitchStmt:
|
||||||
check.openScope(s)
|
check.openScope(s)
|
||||||
|
defer check.closeScope()
|
||||||
check.optionalStmt(s.Init)
|
check.optionalStmt(s.Init)
|
||||||
|
|
||||||
// A type switch guard must be of the form:
|
// A type switch guard must be of the form:
|
||||||
@ -590,7 +424,6 @@ func (check *checker) stmt(s ast.Stmt) {
|
|||||||
check.stmtList(clause.Body)
|
check.stmtList(clause.Body)
|
||||||
check.closeScope()
|
check.closeScope()
|
||||||
}
|
}
|
||||||
check.closeScope()
|
|
||||||
|
|
||||||
case *ast.SelectStmt:
|
case *ast.SelectStmt:
|
||||||
check.multipleDefaults(s.Body.List)
|
check.multipleDefaults(s.Body.List)
|
||||||
@ -621,6 +454,8 @@ func (check *checker) stmt(s ast.Stmt) {
|
|||||||
|
|
||||||
case *ast.RangeStmt:
|
case *ast.RangeStmt:
|
||||||
check.openScope(s)
|
check.openScope(s)
|
||||||
|
defer check.closeScope()
|
||||||
|
|
||||||
// check expression to iterate over
|
// check expression to iterate over
|
||||||
decl := s.Tok == token.DEFINE
|
decl := s.Tok == token.DEFINE
|
||||||
var x operand
|
var x operand
|
||||||
@ -678,27 +513,63 @@ func (check *checker) stmt(s ast.Stmt) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// check assignment to/declaration of iteration variables
|
// check assignment to/declaration of iteration variables
|
||||||
// TODO(gri) The error messages/positions are not great here,
|
// (irregular assignment, cannot easily map to existing assignment checks)
|
||||||
// they refer to the expression in the range clause.
|
if s.Key == nil {
|
||||||
// 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)
|
|
||||||
} else {
|
|
||||||
check.invalidAST(s.Pos(), "range clause requires index iteration variable")
|
check.invalidAST(s.Pos(), "range clause requires index iteration variable")
|
||||||
// ok to continue
|
// ok to continue
|
||||||
}
|
}
|
||||||
if s.Value != nil {
|
|
||||||
x.typ = val
|
// lhs expressions and initialization value (rhs) types
|
||||||
x.expr = s.Value
|
lhs := [2]ast.Expr{s.Key, s.Value}
|
||||||
check.assign1to1(s.Value, nil, &x, decl)
|
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.declare(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)
|
check.stmt(s.Body)
|
||||||
check.closeScope()
|
|
||||||
|
|
||||||
default:
|
default:
|
||||||
check.errorf(s.Pos(), "invalid statement")
|
check.errorf(s.Pos(), "invalid statement")
|
||||||
|
Loading…
Reference in New Issue
Block a user