Commit b8db56ad authored by Robert Griesemer's avatar Robert Griesemer

go/types: cleanup of assignment checks

Also:
- cleaner handling of constants w/ unknown value
- removed several TODOs

R=adonovan
CC=golang-dev
https://golang.org/cl/7473043
parent 0a71a5b0
...@@ -202,20 +202,22 @@ func (check *checker) object(obj Object, cycleOk bool) { ...@@ -202,20 +202,22 @@ func (check *checker) object(obj Object, cycleOk bool) {
return // already checked return // already checked
} }
// The obj.Val field for constants is initialized to its respective // The obj.Val field for constants is initialized to its respective
// iota value by the parser. // iota value (type int) by the parser.
// The object's fields can be in one of the following states: // If the object's type is Typ[Invalid], the object value is ignored.
// Type != nil => the constant value is Val // If the object's type is valid, the object value must be a legal
// Type == nil => the constant is not typechecked yet, and Val can be: // constant value; it may be nil to indicate that we don't know the
// Val is int => Val is the value of iota for this declaration // value of the constant (e.g., in: "const x = float32("foo")" we
// Val == nil => the object's expression is being evaluated // know that x is a constant and has type float32, but we don't
if obj.Val == nil { // have a value due to the error in the conversion).
check.errorf(obj.GetPos(), "illegal cycle in initialization of %s", obj.Name) if obj.visited {
check.errorf(obj.GetPos(), "illegal cycle in initialization of constant %s", obj.Name)
obj.Type = Typ[Invalid] obj.Type = Typ[Invalid]
return return
} }
obj.visited = true
spec := obj.spec spec := obj.spec
iota := obj.Val.(int) iota := obj.Val.(int)
obj.Val = nil // mark obj as "visited" for cycle detection obj.Val = nil // set to a valid (but unknown) constant value
// determine spec for type and initialization expressions // determine spec for type and initialization expressions
init := spec init := spec
if len(init.Values) == 0 { if len(init.Values) == 0 {
...@@ -228,7 +230,7 @@ func (check *checker) object(obj Object, cycleOk bool) { ...@@ -228,7 +230,7 @@ func (check *checker) object(obj Object, cycleOk bool) {
return // already checked return // already checked
} }
if obj.visited { if obj.visited {
check.errorf(obj.GetPos(), "illegal cycle in initialization of %s", obj.Name) check.errorf(obj.GetPos(), "illegal cycle in initialization of variable %s", obj.Name)
obj.Type = Typ[Invalid] obj.Type = Typ[Invalid]
return return
} }
......
...@@ -19,6 +19,7 @@ import ( ...@@ -19,6 +19,7 @@ import (
// Representation of constant values. // Representation of constant values.
// //
// invalid -> nil (i.e., we don't know the constant value; this can only happen in erroneous programs)
// bool -> bool (true, false) // bool -> bool (true, false)
// numeric -> int64, *big.Int, *big.Rat, Complex (ordered by increasing data structure "size") // numeric -> int64, *big.Int, *big.Rat, Complex (ordered by increasing data structure "size")
// string -> string // string -> string
...@@ -159,6 +160,8 @@ func makeStringConst(lit string) interface{} { ...@@ -159,6 +160,8 @@ func makeStringConst(lit string) interface{} {
func toImagConst(x interface{}) interface{} { func toImagConst(x interface{}) interface{} {
var im *big.Rat var im *big.Rat
switch x := x.(type) { switch x := x.(type) {
case nil:
im = rat0
case int64: case int64:
im = big.NewRat(x, 1) im = big.NewRat(x, 1)
case *big.Int: case *big.Int:
...@@ -184,6 +187,8 @@ func isZeroConst(x interface{}) bool { ...@@ -184,6 +187,8 @@ func isZeroConst(x interface{}) bool {
// //
func isNegConst(x interface{}) bool { func isNegConst(x interface{}) bool {
switch x := x.(type) { switch x := x.(type) {
case nil:
return false
case int64: case int64:
return x < 0 return x < 0
case *big.Int: case *big.Int:
...@@ -200,6 +205,10 @@ func isNegConst(x interface{}) bool { ...@@ -200,6 +205,10 @@ func isNegConst(x interface{}) bool {
// of precision. // of precision.
// //
func isRepresentableConst(x interface{}, ctxt *Context, as BasicKind) bool { func isRepresentableConst(x interface{}, ctxt *Context, as BasicKind) bool {
if x == nil {
return true // avoid spurious errors
}
switch x := x.(type) { switch x := x.(type) {
case bool: case bool:
return as == Bool || as == UntypedBool return as == Bool || as == UntypedBool
...@@ -387,6 +396,10 @@ func is63bit(x int64) bool { ...@@ -387,6 +396,10 @@ func is63bit(x int64) bool {
// unaryOpConst returns the result of the constant evaluation op x where x is of the given type. // unaryOpConst returns the result of the constant evaluation op x where x is of the given type.
func unaryOpConst(x interface{}, ctxt *Context, op token.Token, typ *Basic) interface{} { func unaryOpConst(x interface{}, ctxt *Context, op token.Token, typ *Basic) interface{} {
if x == nil {
return nil
}
switch op { switch op {
case token.ADD: case token.ADD:
return x // nothing to do return x // nothing to do
...@@ -437,6 +450,10 @@ func unaryOpConst(x interface{}, ctxt *Context, op token.Token, typ *Basic) inte ...@@ -437,6 +450,10 @@ func unaryOpConst(x interface{}, ctxt *Context, op token.Token, typ *Basic) inte
// division. Division by zero leads to a run-time panic. // division. Division by zero leads to a run-time panic.
// //
func binaryOpConst(x, y interface{}, op token.Token, typ *Basic) interface{} { func binaryOpConst(x, y interface{}, op token.Token, typ *Basic) interface{} {
if x == nil || y == nil {
return nil
}
x, y = matchConst(x, y) x, y = matchConst(x, y)
switch x := x.(type) { switch x := x.(type) {
...@@ -591,6 +608,9 @@ func binaryOpConst(x, y interface{}, op token.Token, typ *Basic) interface{} { ...@@ -591,6 +608,9 @@ func binaryOpConst(x, y interface{}, op token.Token, typ *Basic) interface{} {
// //
func shiftConst(x interface{}, s uint, op token.Token) interface{} { func shiftConst(x interface{}, s uint, op token.Token) interface{} {
switch x := x.(type) { switch x := x.(type) {
case nil:
return nil
case int64: case int64:
switch op { switch op {
case token.SHL: case token.SHL:
...@@ -619,6 +639,10 @@ func shiftConst(x interface{}, s uint, op token.Token) interface{} { ...@@ -619,6 +639,10 @@ func shiftConst(x interface{}, s uint, op token.Token) interface{} {
// or NilType). // or NilType).
// //
func compareConst(x, y interface{}, op token.Token) (z bool) { func compareConst(x, y interface{}, op token.Token) (z bool) {
if x == nil || y == nil {
return false
}
x, y = matchConst(x, y) x, y = matchConst(x, y)
// x == y => x == y // x == y => x == y
......
...@@ -580,7 +580,11 @@ func (check *checker) binary(x *operand, lhs, rhs ast.Expr, op token.Token, iota ...@@ -580,7 +580,11 @@ func (check *checker) binary(x *operand, lhs, rhs ast.Expr, op token.Token, iota
} }
if !IsIdentical(x.typ, y.typ) { if !IsIdentical(x.typ, y.typ) {
check.invalidOp(x.pos(), "mismatched types %s and %s", x.typ, y.typ) // only report an error if we have valid types
// (otherwise we had an error reported elsewhere already)
if x.typ != Typ[Invalid] && y.typ != Typ[Invalid] {
check.invalidOp(x.pos(), "mismatched types %s and %s", x.typ, y.typ)
}
x.mode = invalid x.mode = invalid
return return
} }
...@@ -823,8 +827,8 @@ func (check *checker) rawExpr(x *operand, e ast.Expr, hint Type, iota int, cycle ...@@ -823,8 +827,8 @@ func (check *checker) rawExpr(x *operand, e ast.Expr, hint Type, iota int, cycle
check.errorf(e.Pos(), "use of package %s not in selector", obj.Name) check.errorf(e.Pos(), "use of package %s not in selector", obj.Name)
goto Error goto Error
case *Const: case *Const:
if obj.Val == nil { if obj.Type == Typ[Invalid] {
goto Error // cycle detected goto Error
} }
x.mode = constant x.mode = constant
if obj == universeIota { if obj == universeIota {
...@@ -834,7 +838,7 @@ func (check *checker) rawExpr(x *operand, e ast.Expr, hint Type, iota int, cycle ...@@ -834,7 +838,7 @@ func (check *checker) rawExpr(x *operand, e ast.Expr, hint Type, iota int, cycle
} }
x.val = int64(iota) x.val = int64(iota)
} else { } else {
x.val = obj.Val x.val = obj.Val // may be nil if we don't know the constant value
} }
case *TypeName: case *TypeName:
x.mode = typexpr x.mode = typexpr
......
...@@ -38,9 +38,10 @@ type Const struct { ...@@ -38,9 +38,10 @@ type Const struct {
Pkg *Package Pkg *Package
Name string Name string
Type Type Type Type
Val interface{} Val interface{} // nil means unknown constant value due to type error
spec *ast.ValueSpec visited bool // for initialization cycle detection
spec *ast.ValueSpec
} }
// A TypeName represents a declared type. // A TypeName represents a declared type.
......
...@@ -35,149 +35,123 @@ func (check *checker) assignment(x *operand, to Type) bool { ...@@ -35,149 +35,123 @@ func (check *checker) assignment(x *operand, to Type) bool {
} }
// assign1to1 typechecks a single assignment of the form lhs = rhs (if rhs != nil), // 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 operand must be an identifier. // or lhs = x (if rhs == nil). If decl is set, the lhs operand must be an identifier;
// If its type is not set, it is deduced from the type or value of x. If lhs has a // if its type is not set, it is deduced from the type of x or set to Typ[Invalid] in
// type it is used as a hint when evaluating rhs, if present. // case of an error.
// //
func (check *checker) assign1to1(lhs, rhs ast.Expr, x *operand, decl bool, iota int) { func (check *checker) assign1to1(lhs, rhs ast.Expr, x *operand, decl bool, iota int) {
ident, _ := lhs.(*ast.Ident) // Start with rhs so we have an expression type
// for declarations with implicit type.
if x == nil { if x == nil {
assert(rhs != nil)
x = new(operand) x = new(operand)
} check.expr(x, rhs, nil, iota)
// don't exit for declarations - we need the lhs obj first
if ident != nil && ident.Name == "_" { if x.mode == invalid && !decl {
// anything can be assigned to a blank identifier - check rhs only, if present return
if rhs != nil {
check.expr(x, rhs, nil, iota)
} }
return
} }
// x.mode == valid || decl
// lhs may be an identifier
ident, _ := lhs.(*ast.Ident)
// regular assignment; we know x is valid
if !decl { if !decl {
// regular assignment - start with lhs to obtain a type hint // anything can be assigned to the blank identifier
// TODO(gri) clean this up - we don't need type hints anymore if ident != nil && ident.Name == "_" {
return
}
var z operand var z operand
check.expr(&z, lhs, nil, -1) check.expr(&z, lhs, nil, -1)
if z.mode == invalid { if z.mode == invalid {
z.typ = nil // so we can proceed with rhs
}
if rhs != nil {
check.expr(x, rhs, z.typ, -1)
if x.mode == invalid {
return
}
}
if x.mode == invalid || z.mode == invalid {
return return
} }
if !check.assignment(x, z.typ) { // 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 { if x.mode != invalid {
check.errorf(x.pos(), "cannot assign %s to %s", x, &z) check.errorf(x.pos(), "cannot assign %s to %s", x, &z)
} }
return
}
if z.mode == constant {
check.errorf(x.pos(), "cannot assign %s to %s", x, &z)
} }
return return
} }
// declaration - lhs must be an identifier // declaration with initialization; lhs must be an identifier
if ident == nil { if ident == nil {
check.errorf(lhs.Pos(), "cannot declare %s", lhs) check.errorf(lhs.Pos(), "cannot declare %s", lhs)
return return
} }
// lhs may or may not be typed yet // Determine typ of lhs: If the object doesn't have a type
obj := check.lookup(ident) // yet, determine it from the type of x; if x is invalid,
// set the object type to Typ[Invalid].
var typ Type var typ Type
if t := obj.GetType(); t != nil { obj := check.lookup(ident)
typ = t switch obj := obj.(type) {
} default:
unreachable()
if rhs != nil { case nil:
check.expr(x, rhs, typ, iota) // TODO(gri) is this really unreachable?
// continue even if x.mode == invalid unreachable()
}
if typ == nil { case *Const:
// determine lhs type from rhs expression; typ = obj.Type // may already be Typ[Invalid]
// for variables, convert untyped types to if typ == nil {
// default types typ = Typ[Invalid]
typ = Typ[Invalid] if x.mode != invalid {
if x.mode != invalid { typ = x.typ
typ = x.typ }
if _, ok := obj.(*Var); ok && isUntyped(typ) { obj.Type = typ
if x.isNil() { }
check.errorf(x.pos(), "use of untyped nil")
x.mode = invalid case *Var:
} else { 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")
obj.Type = Typ[Invalid]
return
}
typ = defaultType(typ) typ = defaultType(typ)
} }
} }
}
switch obj := obj.(type) {
case *Const:
obj.Type = typ
case *Var:
obj.Type = typ obj.Type = typ
default:
unreachable()
} }
} }
if x.mode != invalid { // nothing else to check if we don't have a valid lhs or rhs
if !check.assignment(x, typ) { if typ == Typ[Invalid] || x.mode == invalid {
if x.mode != invalid { return
switch obj.(type) { }
case *Const:
check.errorf(x.pos(), "cannot assign %s to variable of type %s", x, typ) if !check.assignment(x, typ) {
case *Var: if x.mode != invalid {
check.errorf(x.pos(), "cannot initialize constant of type %s with %s", typ, x) if x.typ != Typ[Invalid] && typ != Typ[Invalid] {
default: check.errorf(x.pos(), "cannot initialize %s (type %s) with %s", ident.Name, typ, x)
unreachable()
}
x.mode = invalid
} }
} }
return
} }
// for constants, set their value // for constants, set their value
if obj, ok := obj.(*Const); ok { if obj, _ := obj.(*Const); obj != nil {
assert(obj.Val == nil) obj.Val = nil // failure case: we don't know the constant value
if x.mode != invalid { if x.mode == constant {
if x.mode == constant { if isConstType(x.typ) {
if isConstType(x.typ) { obj.Val = x.val
obj.Val = x.val } else if x.typ != Typ[Invalid] {
} else { check.errorf(x.pos(), "%s has invalid constant type", x)
check.errorf(x.pos(), "%s has invalid constant type", x)
}
} else {
check.errorf(x.pos(), "%s is not constant", x)
}
}
if obj.Val == nil {
// set the constant to its type's zero value to reduce spurious errors
switch typ := underlying(obj.Type); {
case typ == Typ[Invalid]:
// ignore
case isBoolean(typ):
obj.Val = false
case isNumeric(typ):
obj.Val = int64(0)
case isString(typ):
obj.Val = ""
case hasNil(typ):
obj.Val = nilConst
default:
// in all other cases just prevent use of the constant
// TODO(gri) re-evaluate this code
obj.Val = nilConst
} }
} else if x.mode != invalid {
check.errorf(x.pos(), "%s is not constant", x)
} }
} }
} }
...@@ -494,6 +468,7 @@ func (check *checker) stmt(s ast.Stmt) { ...@@ -494,6 +468,7 @@ func (check *checker) stmt(s ast.Stmt) {
check.expr(&x, tag, nil, -1) check.expr(&x, tag, nil, -1)
check.multipleDefaults(s.Body.List) check.multipleDefaults(s.Body.List)
// TODO(gri) check also correct use of fallthrough
seen := make(map[interface{}]token.Pos) seen := make(map[interface{}]token.Pos)
for _, s := range s.Body.List { for _, s := range s.Body.List {
clause, _ := s.(*ast.CaseClause) clause, _ := s.(*ast.CaseClause)
......
...@@ -154,7 +154,6 @@ func _len() { ...@@ -154,7 +154,6 @@ func _len() {
assert /* ERROR "failed" */ (n == 10) assert /* ERROR "failed" */ (n == 10)
var ch <-chan int var ch <-chan int
const nn = len /* ERROR "not constant" */ (hash[<-ch][len(t)]) const nn = len /* ERROR "not constant" */ (hash[<-ch][len(t)])
_ = nn // TODO(gri) remove this once unused constants get type-checked
// issue 4744 // issue 4744
type T struct{ a [10]int } type T struct{ a [10]int }
......
...@@ -111,8 +111,8 @@ const ( ...@@ -111,8 +111,8 @@ const (
ti5 = ti0 /* ERROR "mismatched types" */ + ti1 ti5 = ti0 /* ERROR "mismatched types" */ + ti1
ti6 = ti1 - ti1 ti6 = ti1 - ti1
ti7 = ti2 /* ERROR "mismatched types" */ * ti1 ti7 = ti2 /* ERROR "mismatched types" */ * ti1
//ti8 = ti3 / ti3 // TODO(gri) enable this ti8 = ti3 / ti3
//ti9 = ti3 % ti3 // TODO(gri) enable this ti9 = ti3 % ti3
ti10 = 1 / 0 /* ERROR "division by zero" */ ti10 = 1 / 0 /* ERROR "division by zero" */
ti11 = ti1 / 0 /* ERROR "division by zero" */ ti11 = ti1 / 0 /* ERROR "division by zero" */
...@@ -135,7 +135,7 @@ const ( ...@@ -135,7 +135,7 @@ const (
tf5 = tf0 + tf1 tf5 = tf0 + tf1
tf6 = tf1 - tf1 tf6 = tf1 - tf1
tf7 = tf2 /* ERROR "mismatched types" */ * tf1 tf7 = tf2 /* ERROR "mismatched types" */ * tf1
// tf8 = tf3 / tf3 // TODO(gri) enable this tf8 = tf3 / tf3
tf9 = tf3 /* ERROR "not defined" */ % tf3 tf9 = tf3 /* ERROR "not defined" */ % tf3
tf10 = 1 / 0 /* ERROR "division by zero" */ tf10 = 1 / 0 /* ERROR "division by zero" */
......
...@@ -28,10 +28,9 @@ type T2 struct { ...@@ -28,10 +28,9 @@ type T2 struct {
func (undeclared /* ERROR "undeclared" */) m() {} func (undeclared /* ERROR "undeclared" */) m() {}
func (x *undeclared /* ERROR "undeclared" */) m() {} func (x *undeclared /* ERROR "undeclared" */) m() {}
// TODO(gri) try to get rid of double error reporting here
func (pi /* ERROR "not a type" */) m1() {} func (pi /* ERROR "not a type" */) m1() {}
func (x pi /* ERROR "not a type" */) m2() {} func (x pi /* ERROR "not a type" */) m2() {}
func (x *pi /* ERROR "not a type" */ ) m3() {} // TODO(gri) not closing the last /* comment crashes the system func (x *pi /* ERROR "not a type" */ ) m3() {}
// Blank types. // Blank types.
type _ struct { m int } type _ struct { m int }
......
...@@ -125,9 +125,10 @@ func shifts4() { ...@@ -125,9 +125,10 @@ func shifts4() {
} }
} }
// TODO(gri) The error messages below depond on adjusting the spec // TODO(gri) The error messages below depend on adjusting the spec
// to reflect what gc is doing at the moment (the spec // to reflect what gc is doing at the moment (the spec
// asks for run-time errors at the moment - see issue 4231). // asks for run-time errors at the moment - see issue 4231).
// TODO(gri) This has been fixed in the spec. Fix this.
// //
func indexes() { func indexes() {
_ = 1 /* ERROR "cannot index" */ [0] _ = 1 /* ERROR "cannot index" */ [0]
......
...@@ -55,10 +55,10 @@ var aliases = [...]*Basic{ ...@@ -55,10 +55,10 @@ var aliases = [...]*Basic{
} }
var predeclaredConstants = [...]*Const{ var predeclaredConstants = [...]*Const{
{nil, "true", Typ[UntypedBool], true, nil}, {Name: "true", Type: Typ[UntypedBool], Val: true},
{nil, "false", Typ[UntypedBool], false, nil}, {Name: "false", Type: Typ[UntypedBool], Val: false},
{nil, "iota", Typ[UntypedInt], zeroConst, nil}, {Name: "iota", Type: Typ[UntypedInt], Val: zeroConst},
{nil, "nil", Typ[UntypedNil], nilConst, nil}, {Name: "nil", Type: Typ[UntypedNil], Val: nilConst},
} }
var predeclaredFunctions = [...]*builtin{ var predeclaredFunctions = [...]*builtin{
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment