go.tools/go/types: return typed bool for comma-ok expressions

Also: determine default types for assignments of the form: _ = x .

R=adonovan
CC=golang-dev
https://golang.org/cl/12186043
This commit is contained in:
Robert Griesemer 2013-07-31 12:49:34 -07:00
parent 323b856f4d
commit ae07aa6055
4 changed files with 55 additions and 33 deletions

View File

@ -34,19 +34,23 @@ func TestCommaOkTypes(t *testing.T) {
var tests = []struct {
src string
expr string // comma-ok expression string
typ Type // type of first comma-ok value
typ string // typestring of comma-ok value
}{
{`package p; var x interface{}; var _, _ = x.(int)`,
`x.(int)`,
Typ[Int],
`(int, bool)`,
},
{`package p; var m map[string]complex128; var _, _ = m["foo"]`,
{`package p; var x interface{}; func _() { _, _ = x.(int) }`,
`x.(int)`,
`(int, bool)`,
},
{`package p; type mybool bool; var m map[string]complex128; var b mybool; func _() { _, b = m["foo"] }`,
`m["foo"]`,
Typ[Complex128],
`(complex128, p.mybool)`,
},
{`package p; var c chan string; var _, _ = <-c`,
`<-c`,
Typ[String],
`(string, bool)`,
},
}
@ -75,13 +79,8 @@ func TestCommaOkTypes(t *testing.T) {
}
// check that type is correct
got, _ := typ.(*Tuple)
want := NewTuple(
NewVar(token.NoPos, nil, "", test.typ),
NewVar(token.NoPos, nil, "", Typ[UntypedBool]),
)
if got == nil || !identicalTuples(got, want) {
t.Errorf("%s: got %s; want %s", path, got, want)
if got := typ.String(); got != test.typ {
t.Errorf("%s: got %s; want %s", path, got, test.typ)
}
}
}

View File

@ -72,12 +72,12 @@ func (check *checker) initConst(lhs *Const, x *operand) {
lhs.val = x.val
}
func (check *checker) initVar(lhs *Var, x *operand) {
func (check *checker) initVar(lhs *Var, x *operand) Type {
if x.mode == invalid || x.typ == Typ[Invalid] || lhs.typ == Typ[Invalid] {
if lhs.typ == nil {
lhs.typ = Typ[Invalid]
}
return // nothing else to check
return nil // nothing else to check
}
// If the lhs doesn't have a type yet, use the type of x.
@ -88,7 +88,7 @@ func (check *checker) initVar(lhs *Var, x *operand) {
if typ == Typ[UntypedNil] {
check.errorf(x.pos(), "use of untyped nil")
lhs.typ = Typ[Invalid]
return // nothing else to check
return nil // nothing else to check
}
typ = defaultType(typ)
}
@ -99,30 +99,45 @@ func (check *checker) initVar(lhs *Var, x *operand) {
if x.mode != invalid {
check.errorf(x.pos(), "cannot initialize variable %s (type %s) with %s", lhs.Name(), lhs.typ, x)
}
}
return nil
}
func (check *checker) assignVar(lhs ast.Expr, x *operand) {
return lhs.typ
}
func (check *checker) assignVar(lhs ast.Expr, x *operand) Type {
if x.mode == invalid || x.typ == Typ[Invalid] {
return
return nil
}
// Don't evaluate lhs if it is the blank identifier.
if ident, _ := lhs.(*ast.Ident); ident != nil && ident.Name == "_" {
check.recordObject(ident, nil)
check.updateExprType(x.expr, x.typ, true) // rhs has its final type
return
// If the lhs is untyped, determine the default type.
// The spec is unclear about this, but gc appears to
// do this.
typ := x.typ
if isUntyped(typ) {
// convert untyped types to default types
if typ == Typ[UntypedNil] {
check.errorf(x.pos(), "use of untyped nil")
return nil // nothing else to check
}
typ = defaultType(typ)
}
check.updateExprType(x.expr, typ, true) // rhs has its final type
return typ
}
var z operand
check.expr(&z, lhs)
if z.mode == invalid || z.typ == Typ[Invalid] {
return
return nil
}
if z.mode == constant || z.mode == value {
check.errorf(z.pos(), "cannot assign to non-variable %s", &z)
return
return nil
}
// TODO(gri) z.mode can also be valueok which in some cases is ok (maps)
@ -132,7 +147,10 @@ func (check *checker) assignVar(lhs ast.Expr, x *operand) {
if x.mode != invalid {
check.errorf(x.pos(), "cannot assign %s to %s", x, &z)
}
return nil
}
return z.typ
}
func (check *checker) initVars(lhs []*Var, rhs []ast.Expr, allowCommaOk bool) {
@ -182,15 +200,17 @@ func (check *checker) initVars(lhs []*Var, rhs []ast.Expr, allowCommaOk bool) {
if allowCommaOk && x.mode == valueok && l == 2 {
// comma-ok expression
check.recordCommaOkType(rhs, x.typ)
x.mode = value
check.initVar(lhs[0], &x)
t1 := check.initVar(lhs[0], &x)
x.mode = value
x.expr = rhs
x.typ = Typ[UntypedBool]
check.initVar(lhs[1], &x)
t2 := check.initVar(lhs[1], &x)
if t1 != nil && t2 != nil {
check.recordCommaOkTypes(rhs, t1, t2)
}
return
}
}
@ -245,15 +265,17 @@ func (check *checker) assignVars(lhs, rhs []ast.Expr) {
if x.mode == valueok && l == 2 {
// comma-ok expression
check.recordCommaOkType(rhs, x.typ)
x.mode = value
check.assignVar(lhs[0], &x)
t1 := check.assignVar(lhs[0], &x)
x.mode = value
x.expr = rhs
x.typ = Typ[UntypedBool]
check.assignVar(lhs[1], &x)
t2 := check.assignVar(lhs[1], &x)
if t1 != nil && t2 != nil {
check.recordCommaOkTypes(rhs, t1, t2)
}
return
}
}

View File

@ -82,14 +82,14 @@ func (check *checker) recordTypeAndValue(x ast.Expr, typ Type, val exact.Value)
}
}
func (check *checker) recordCommaOkType(x ast.Expr, typ Type) {
assert(x != nil && typ != nil)
func (check *checker) recordCommaOkTypes(x ast.Expr, t1, t2 Type) {
assert(x != nil && !isUntyped(t1) && !isUntyped(t2) && isBoolean(t2))
if m := check.Types; m != nil {
assert(m[x] != nil) // should have been recorded already
pos := x.Pos()
m[x] = NewTuple(
NewVar(pos, check.pkg, "", typ),
NewVar(pos, check.pkg, "", Typ[UntypedBool]),
NewVar(pos, check.pkg, "", t1),
NewVar(pos, check.pkg, "", t2),
)
}
}

View File

@ -509,6 +509,7 @@ func (check *checker) stmt(s ast.Stmt, fallthroughOk bool) {
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