go.tools/go/types: nil is not a constant + misc. cleanups
- removed support for nil constants from go/exact
- instead define a singleton Nil Object (the nil _value_)
- in assignments, follow more closely spec wording
(pending spec CL 14415043)
- removed use of goto in checker.unary
- cleanup around handling of isRepresentable for
constants, with better error messages
- fix missing checks in checker.convertUntyped
- added isTyped (== !isUntyped) and isInterface predicates
- fixed hasNil predicate: unsafe.Pointer also has nil
- adjusted ssa per adonovan
- implememted types.Implements (wrapper arounfd types.MissingMethod)
- use types.Implements in vet (and fix a bug)
R=adonovan, r
CC=golang-dev
https://golang.org/cl/14438052
diff --git a/cmd/vet/testdata/print.go b/cmd/vet/testdata/print.go
index 9168d4c..232c7b3 100644
--- a/cmd/vet/testdata/print.go
+++ b/cmd/vet/testdata/print.go
@@ -124,7 +124,7 @@
fmt.Printf("%t", notstringerarrayv) // ERROR "arg notstringerarrayv for printf verb %t of wrong type"
fmt.Printf("%q", notstringerarrayv) // ERROR "arg notstringerarrayv for printf verb %q of wrong type"
fmt.Printf("%d", Formatter(true)) // ERROR "arg Formatter\(true\) for printf verb %d of wrong type"
- fmt.Printf("%s", nonemptyinterface) // ERROR "for printf verb %s of wrong type" (Disabled temporarily because of bug in IsAssignableTo)
+ fmt.Printf("%s", nonemptyinterface) // correct (the dynamic type of nonemptyinterface may be a stringer)
fmt.Printf("%.*s %d %g", 3, "hi", 23, 'x') // ERROR "arg 'x' for printf verb %g of wrong type"
fmt.Println() // not an error
fmt.Println("%s", "hi") // ERROR "possible formatting directive in Println call"
diff --git a/cmd/vet/types.go b/cmd/vet/types.go
index a80939f..41ce644 100644
--- a/cmd/vet/types.go
+++ b/cmd/vet/types.go
@@ -62,8 +62,8 @@
var (
stringerMethodType = types.New("func() string")
- errorType = types.New("interface{ Error() string }")
- stringerType = types.New("interface{ String() string }")
+ errorType = types.New("interface{ Error() string }").(*types.Interface)
+ stringerType = types.New("interface{ String() string }").(*types.Interface)
// One day this might work. See issue 6259.
// formatterType = types.New("interface{Format(f fmt.State, c rune)}")
)
@@ -103,9 +103,9 @@
if hasMethod(typ, "Format") {
return true
}
- // If we can use a string, does arg implement the Stringer or Error interface?
+ // If we can use a string, might arg (dynamically) implement the Stringer or Error interface?
if t&argString != 0 {
- if types.IsAssignableTo(typ, errorType) || types.IsAssignableTo(typ, stringerType) {
+ if types.Implements(typ, errorType, false) || types.Implements(typ, stringerType, false) {
return true
}
}
diff --git a/go/exact/exact.go b/go/exact/exact.go
index 3eb3941..aa691bc 100644
--- a/go/exact/exact.go
+++ b/go/exact/exact.go
@@ -25,7 +25,6 @@
Unknown Kind = iota
// non-numeric values
- Nil
Bool
String
@@ -53,7 +52,6 @@
type (
unknownVal struct{}
- nilVal struct{}
boolVal bool
stringVal string
int64Val int64
@@ -63,7 +61,6 @@
)
func (unknownVal) Kind() Kind { return Unknown }
-func (nilVal) Kind() Kind { return Nil }
func (boolVal) Kind() Kind { return Bool }
func (stringVal) Kind() Kind { return String }
func (int64Val) Kind() Kind { return Int }
@@ -72,7 +69,6 @@
func (complexVal) Kind() Kind { return Complex }
func (unknownVal) String() string { return "unknown" }
-func (nilVal) String() string { return "nil" }
func (x boolVal) String() string { return fmt.Sprintf("%v", bool(x)) }
func (x stringVal) String() string { return strconv.Quote(string(x)) }
func (x int64Val) String() string { return strconv.FormatInt(int64(x), 10) }
@@ -81,7 +77,6 @@
func (x complexVal) String() string { return fmt.Sprintf("(%s + %si)", x.re, x.im) }
func (unknownVal) implementsValue() {}
-func (nilVal) implementsValue() {}
func (boolVal) implementsValue() {}
func (stringVal) implementsValue() {}
func (int64Val) implementsValue() {}
@@ -122,9 +117,6 @@
// MakeUnknown returns the Unknown value.
func MakeUnknown() Value { return unknownVal{} }
-// MakeNil returns the Nil value.
-func MakeNil() Value { return nilVal{} }
-
// MakeBool returns the Bool value for x.
func MakeBool(b bool) Value { return boolVal(b) }
@@ -447,7 +439,7 @@
// ord(x) <= ord(y)
switch x := x.(type) {
- case unknownVal, nilVal, boolVal, stringVal, complexVal:
+ case unknownVal, boolVal, stringVal, complexVal:
return x, y
case int64Val:
diff --git a/go/exact/exact_test.go b/go/exact/exact_test.go
index aed87b7..061eec9 100644
--- a/go/exact/exact_test.go
+++ b/go/exact/exact_test.go
@@ -112,8 +112,6 @@
switch lit {
case "?":
return MakeUnknown()
- case "nil":
- return MakeNil()
case "true":
return MakeBool(true)
case "false":
diff --git a/go/types/api.go b/go/types/api.go
index 8e218e3..de38773 100644
--- a/go/types/api.go
+++ b/go/types/api.go
@@ -174,12 +174,28 @@
return pkg, err
}
-// IsAssignableTo reports whether a value of type V
-// is assignable to a variable of type T.
+// IsAssignableTo reports whether a value of type V is assignable to a variable of type T.
func IsAssignableTo(V, T Type) bool {
x := operand{mode: value, typ: V}
return x.isAssignableTo(nil, T) // config not needed for non-constant x
}
+// Implements reports whether a value of type V implements T, as follows:
+//
+// 1) For non-interface types V, or if static is set, V implements T if all
+// methods of T are present in V. Informally, this reports whether V is a
+// subtype of T.
+//
+// 2) For interface types V, and if static is not set, V implements T if all
+// methods of T which are also present in V have matching types. Informally,
+// this indicates whether a type assertion x.(T) where x is of type V would
+// be legal (the concrete dynamic type of x may implement T even if V does
+// not statically implement it).
+//
+func Implements(V Type, T *Interface, static bool) bool {
+ f, _ := MissingMethod(V, T, static)
+ return f == nil
+}
+
// BUG(gri): Interface vs non-interface comparisons are not correctly implemented.
// BUG(gri): Switch statements don't check duplicate cases for all types for which it is required.
diff --git a/go/types/assignments.go b/go/types/assignments.go
index 4f0db27..6a9f3ea 100644
--- a/go/types/assignments.go
+++ b/go/types/assignments.go
@@ -13,27 +13,62 @@
"code.google.com/p/go.tools/go/exact"
)
-// assignment reports whether x can be assigned to a variable of type 'to',
+// 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. 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
+// Use T == nil to indicate assignment to an untyped blank identifier.
+//
+// TODO(gri) Should find a better way to handle in-band errors.
+// TODO(gri) The T == nil mechanism is not yet used - should simplify callers eventually.
+//
+func (check *checker) assignment(x *operand, T Type) bool {
+ switch x.mode {
+ case invalid:
+ return true // error reported before
+ case constant, variable, value, valueok:
+ // ok
+ default:
+ unreachable()
}
- if t, ok := x.typ.(*Tuple); ok {
+ // x must be a single value
+ // (tuple types are never named - no need for underlying type)
+ if t, _ := x.typ.(*Tuple); t != nil {
assert(t.Len() > 1)
check.errorf(x.pos(), "%d-valued expression %s used as single value", t.Len(), x)
x.mode = invalid
return false
}
- check.convertUntyped(x, to)
+ // 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 x.mode == constant && isUntyped(x.typ) && (T == nil || isInterface(T)) {
+ check.convertUntyped(x, defaultType(x.typ))
+ if x.mode == invalid {
+ return false
+ }
+ }
- return x.mode != invalid && x.isAssignableTo(check.conf, to)
+ // 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 && (x.mode != constant || isTyped(x.typ)) && !x.isNil() {
+ return true
+ }
+
+ // If we still have an untyped x, try to convert it to T.
+ if isUntyped(x.typ) {
+ check.convertUntyped(x, T)
+ if x.mode == invalid {
+ return false
+ }
+ }
+
+ return x.isAssignableTo(check.conf, T)
}
func (check *checker) initConst(lhs *Const, x *operand) {
@@ -120,7 +155,7 @@
// If the lhs is untyped, determine the default type.
// The spec is unclear about this, but gc appears to
// do this.
- // TODO(gri) This is still not correct (try: _ = nil; _ = 1<<1e3)
+ // TODO(gri) This is still not correct (_ = 1<<1e3)
typ := x.typ
if isUntyped(typ) {
// convert untyped types to default types
diff --git a/go/types/call.go b/go/types/call.go
index e306186..d8b499b 100644
--- a/go/types/call.go
+++ b/go/types/call.go
@@ -381,10 +381,8 @@
// includes the methods of typ.
// Variables are addressable, so we can always take their
// address.
- if _, ok := typ.(*Pointer); !ok {
- if _, ok := typ.Underlying().(*Interface); !ok {
- typ = &Pointer{base: typ}
- }
+ if _, ok := typ.(*Pointer); !ok && !isInterface(typ) {
+ typ = &Pointer{base: typ}
}
}
// If we created a synthetic pointer type above, we will throw
diff --git a/go/types/check.go b/go/types/check.go
index 6ee8e06..f0b129d 100644
--- a/go/types/check.go
+++ b/go/types/check.go
@@ -79,7 +79,7 @@
}
func (check *checker) recordCommaOkTypes(x ast.Expr, t1, t2 Type) {
- assert(x != nil && !isUntyped(t1) && !isUntyped(t2) && isBoolean(t2))
+ assert(x != nil && isTyped(t1) && isTyped(t2) && isBoolean(t2))
if m := check.Types; m != nil {
assert(m[x] != nil) // should have been recorded already
pos := x.Pos()
@@ -181,8 +181,8 @@
// remaining untyped expressions must indeed be untyped
if debug {
for x, info := range check.untyped {
- if !isUntyped(info.typ) {
- check.dump("%s: %s (type %s) is not untyped", x.Pos(), x, info.typ)
+ if isTyped(info.typ) {
+ check.dump("%s: %s (type %s) is typed", x.Pos(), x, info.typ)
panic(0)
}
}
diff --git a/go/types/conversions.go b/go/types/conversions.go
index a32b8dc..c8f8843 100644
--- a/go/types/conversions.go
+++ b/go/types/conversions.go
@@ -50,7 +50,7 @@
// For conversions to interfaces, use the argument type's
// default type instead. Keep untyped nil for untyped nil
// arguments.
- if _, ok := T.Underlying().(*Interface); ok {
+ if isInterface(T) {
final = defaultType(x.typ)
}
}
diff --git a/go/types/expr.go b/go/types/expr.go
index d75298b..761e709 100644
--- a/go/types/expr.go
+++ b/go/types/expr.go
@@ -89,7 +89,8 @@
}
if x.mode != variable {
check.invalidOp(x.pos(), "cannot take address of %s", x)
- goto Error
+ x.mode = invalid
+ return
}
x.typ = &Pointer{base: x.typ}
return
@@ -98,11 +99,13 @@
typ, ok := x.typ.Underlying().(*Chan)
if !ok {
check.invalidOp(x.pos(), "cannot receive from non-channel %s", x)
- goto Error
+ x.mode = invalid
+ return
}
if typ.dir&ast.RECV == 0 {
check.invalidOp(x.pos(), "cannot receive from send-only channel %s", x)
- goto Error
+ x.mode = invalid
+ return
}
x.mode = valueok
x.typ = typ.elt
@@ -110,7 +113,8 @@
}
if !check.op(unaryOpPredicates, x, op) {
- goto Error
+ x.mode = invalid
+ return
}
if x.mode == constant {
@@ -122,16 +126,14 @@
x.val = exact.UnaryOp(op, x.val, size)
// Typed constants must be representable in
// their type after each constant operation.
- check.isRepresentable(x, typ)
+ if isTyped(typ) {
+ check.isRepresentableAs(x, typ)
+ }
return
}
x.mode = value
// x.typ remains unchanged
- return
-
-Error:
- x.mode = invalid
}
func isShift(op token.Token) bool {
@@ -318,9 +320,6 @@
case exact.String:
return as == String || as == UntypedString
- case exact.Nil:
- return as == UntypedNil || as == UnsafePointer
-
default:
unreachable()
}
@@ -328,16 +327,24 @@
return false
}
-// isRepresentable checks that a constant operand is representable in the given type.
-func (check *checker) isRepresentable(x *operand, typ *Basic) {
- if x.mode != constant || isUntyped(typ) {
- return
- }
-
+// isRepresentableAs checks that a constant operand is representable in the given basic type.
+func (check *checker) isRepresentableAs(x *operand, typ *Basic) {
+ assert(x.mode == constant)
if !isRepresentableConst(x.val, check.conf, typ.kind, &x.val) {
var msg string
if isNumeric(x.typ) && isNumeric(typ) {
- msg = "%s overflows (or cannot be accurately represented as) %s"
+ // numeric conversion : error msg
+ //
+ // integer -> integer : overflows
+ // integer -> float : overflows (actually not possible)
+ // float -> integer : truncated
+ // float -> float : overflows
+ //
+ if !isInteger(x.typ) && isInteger(typ) {
+ msg = "%s truncated to %s"
+ } else {
+ msg = "%s overflows %s"
+ }
} else {
msg = "cannot convert %s to %s"
}
@@ -457,7 +464,7 @@
// convertUntyped attempts to set the type of an untyped value to the target type.
func (check *checker) convertUntyped(x *operand, target Type) {
- if x.mode == invalid || !isUntyped(x.typ) {
+ if x.mode == invalid || isTyped(x.typ) {
return
}
@@ -481,15 +488,38 @@
// typed target
switch t := target.Underlying().(type) {
- case nil:
- // We may reach here due to previous type errors.
- // Be conservative and don't crash.
- x.mode = invalid
- return
case *Basic:
- check.isRepresentable(x, t)
- if x.mode == invalid {
- return // error already reported
+ if x.mode == constant {
+ check.isRepresentableAs(x, t)
+ if x.mode == invalid {
+ return
+ }
+ } else {
+ // Non-constant untyped values may appear as the
+ // result of comparisons (untyped bool), intermediate
+ // (delayed-checked) rhs operands of shifts, and as
+ // the value nil.
+ switch x.typ.(*Basic).kind {
+ case UntypedBool:
+ if !isBoolean(target) {
+ goto Error
+ }
+ case UntypedInt, UntypedRune, UntypedFloat, UntypedComplex:
+ if !isNumeric(target) {
+ goto Error
+ }
+ case UntypedString:
+ // Non-constant untyped string values are not
+ // permitted by the spec and should not occur.
+ unreachable()
+ case UntypedNil:
+ // Unsafe.Pointer is a basic type that includes nil.
+ if !hasNil(target) {
+ goto Error
+ }
+ default:
+ goto Error
+ }
}
case *Interface:
if !x.isNil() && t.NumMethods() > 0 /* empty interfaces are ok */ {
@@ -737,7 +767,9 @@
x.val = exact.BinaryOp(x.val, op, y.val)
// Typed constants must be representable in
// their type after each constant operation.
- check.isRepresentable(x, typ)
+ if isTyped(typ) {
+ check.isRepresentableAs(x, typ)
+ }
return
}
diff --git a/go/types/lookup.go b/go/types/lookup.go
index 8f8390b..398bb62 100644
--- a/go/types/lookup.go
+++ b/go/types/lookup.go
@@ -233,17 +233,17 @@
return list[:n]
}
-// MissingMethod returns (nil, false) if typ implements T, otherwise it
+// MissingMethod returns (nil, false) if V implements T, otherwise it
// returns a missing method required by T and whether it is missing or
// just has the wrong type.
//
-// For typ of interface type, if static is set, implements checks that all
-// methods of T are present in typ (e.g., for a conversion T(x) where x is
-// of type typ). Otherwise, implements only checks that methods of T which
-// are also present in typ have matching types (e.g., for a type assertion
-// x.(T) where x is of interface type typ).
+// For non-interface types V, or if static is set, V implements T if all
+// methods of T are present in V. Otherwise (V is an interface and static
+// is not set), MissingMethod only checks that methods of T which are also
+// present in V have matching types (e.g., for a type assertion x.(T) where
+// x is of interface type typ).
//
-func MissingMethod(typ Type, T *Interface, static bool) (method *Func, wrongType bool) {
+func MissingMethod(V Type, T *Interface, static bool) (method *Func, wrongType bool) {
// fast path for common case
if T.NumMethods() == 0 {
return
@@ -251,7 +251,7 @@
// TODO(gri) Consider using method sets here. Might be more efficient.
- if ityp, _ := typ.Underlying().(*Interface); ityp != nil {
+ if ityp, _ := V.Underlying().(*Interface); ityp != nil {
for _, m := range T.methods {
_, obj := lookupMethod(ityp.methods, m.pkg, m.name)
switch {
@@ -268,7 +268,7 @@
// A concrete type implements T if it implements all methods of T.
for _, m := range T.methods {
- obj, _, indirect := lookupFieldOrMethod(typ, m.pkg, m.name)
+ obj, _, indirect := lookupFieldOrMethod(V, m.pkg, m.name)
if obj == nil {
return m, false
}
diff --git a/go/types/objects.go b/go/types/objects.go
index 020a3e1..d15e020 100644
--- a/go/types/objects.go
+++ b/go/types/objects.go
@@ -266,3 +266,10 @@
func newBuiltin(id builtinId) *Builtin {
return &Builtin{object{name: predeclaredFuncs[id].name, typ: Typ[Invalid]}, id}
}
+
+// Nil represents the predeclared value nil.
+type Nil struct {
+ object
+}
+
+func (*Nil) String() string { return "nil" }
diff --git a/go/types/operand.go b/go/types/operand.go
index 6e49475..b73f87a 100644
--- a/go/types/operand.go
+++ b/go/types/operand.go
@@ -184,13 +184,13 @@
x.val = val
}
-// isNil reports whether x is the predeclared nil constant.
+// isNil reports whether x is the nil value.
func (x *operand) isNil() bool {
- return x.mode == constant && x.val.Kind() == exact.Nil
+ return x.mode == value && x.typ == Typ[UntypedNil]
}
// TODO(gri) The functions operand.isAssignableTo, checker.convertUntyped,
-// checker.isRepresentable, and checker.assignOperand are
+// checker.isRepresentable, and checker.assignment are
// overlapping in functionality. Need to simplify and clean up.
// isAssignableTo reports whether x is assignable to a variable of type T.
diff --git a/go/types/predicates.go b/go/types/predicates.go
index 778bf88..c9cb127 100644
--- a/go/types/predicates.go
+++ b/go/types/predicates.go
@@ -49,6 +49,11 @@
return ok && t.info&IsString != 0
}
+func isTyped(typ Type) bool {
+ t, ok := typ.Underlying().(*Basic)
+ return !ok || t.info&IsUntyped == 0
+}
+
func isUntyped(typ Type) bool {
t, ok := typ.Underlying().(*Basic)
return ok && t.info&IsUntyped != 0
@@ -64,6 +69,11 @@
return ok && t.info&IsConstType != 0
}
+func isInterface(typ Type) bool {
+ _, ok := typ.Underlying().(*Interface)
+ return ok
+}
+
func isComparable(typ Type) bool {
switch t := typ.Underlying().(type) {
case *Basic:
@@ -84,8 +94,11 @@
return false
}
+// hasNil reports whether a type includes the nil value.
func hasNil(typ Type) bool {
- switch typ.Underlying().(type) {
+ switch t := typ.Underlying().(type) {
+ case *Basic:
+ return t.kind == UnsafePointer
case *Slice, *Pointer, *Signature, *Interface, *Map, *Chan:
return true
}
diff --git a/go/types/testdata/builtins.src b/go/types/testdata/builtins.src
index 8aa6fb8..0abac1a 100644
--- a/go/types/testdata/builtins.src
+++ b/go/types/testdata/builtins.src
@@ -322,7 +322,7 @@
_ = make([]int, int /* ERROR not an expression */)
_ = make([]int, 10, float32 /* ERROR not an expression */)
_ = make([]int, "foo" /* ERROR cannot convert */)
- _ = make([]int, 10, 2.3 /* ERROR overflows */)
+ _ = make([]int, 10, 2.3 /* ERROR truncated */)
_ = make([]int, 5, 10.0)
_ = make([]int, 0i)
_ = make([]int, 1.0)
diff --git a/go/types/testdata/const0.src b/go/types/testdata/const0.src
index 6515e2a..19e4589 100644
--- a/go/types/testdata/const0.src
+++ b/go/types/testdata/const0.src
@@ -253,7 +253,7 @@
// special cases
const (
- _n0 = nil /* ERROR "invalid constant type" */
+ _n0 = nil /* ERROR "not constant" */
_n1 = [ /* ERROR "not constant" */ ]int{}
)
diff --git a/go/types/testdata/const1.src b/go/types/testdata/const1.src
index df4d522..88e9fad 100644
--- a/go/types/testdata/const1.src
+++ b/go/types/testdata/const1.src
@@ -61,7 +61,7 @@
_ int8 = minInt8
_ int8 = maxInt8
_ int8 = maxInt8 /* ERROR "overflows" */ + 1
- _ int8 = smallestFloat64 /* ERROR "overflows" */
+ _ int8 = smallestFloat64 /* ERROR "truncated" */
_ = int8(minInt8 /* ERROR "cannot convert" */ - 1)
_ = int8(minInt8)
@@ -75,7 +75,7 @@
_ int16 = minInt16
_ int16 = maxInt16
_ int16 = maxInt16 /* ERROR "overflows" */ + 1
- _ int16 = smallestFloat64 /* ERROR "overflows" */
+ _ int16 = smallestFloat64 /* ERROR "truncated" */
_ = int16(minInt16 /* ERROR "cannot convert" */ - 1)
_ = int16(minInt16)
@@ -89,7 +89,7 @@
_ int32 = minInt32
_ int32 = maxInt32
_ int32 = maxInt32 /* ERROR "overflows" */ + 1
- _ int32 = smallestFloat64 /* ERROR "overflows" */
+ _ int32 = smallestFloat64 /* ERROR "truncated" */
_ = int32(minInt32 /* ERROR "cannot convert" */ - 1)
_ = int32(minInt32)
@@ -103,7 +103,7 @@
_ int64 = minInt64
_ int64 = maxInt64
_ int64 = maxInt64 /* ERROR "overflows" */ + 1
- _ int64 = smallestFloat64 /* ERROR "overflows" */
+ _ int64 = smallestFloat64 /* ERROR "truncated" */
_ = int64(minInt64 /* ERROR "cannot convert" */ - 1)
_ = int64(minInt64)
@@ -117,7 +117,7 @@
_ int = minInt
_ int = maxInt
_ int = maxInt /* ERROR "overflows" */ + 1
- _ int = smallestFloat64 /* ERROR "overflows" */
+ _ int = smallestFloat64 /* ERROR "truncated" */
_ = int(minInt /* ERROR "cannot convert" */ - 1)
_ = int(minInt)
@@ -131,7 +131,7 @@
_ uint8 = 0
_ uint8 = maxUint8
_ uint8 = maxUint8 /* ERROR "overflows" */ + 1
- _ uint8 = smallestFloat64 /* ERROR "overflows" */
+ _ uint8 = smallestFloat64 /* ERROR "truncated" */
_ = uint8(0 /* ERROR "cannot convert" */ - 1)
_ = uint8(0)
@@ -145,7 +145,7 @@
_ uint16 = 0
_ uint16 = maxUint16
_ uint16 = maxUint16 /* ERROR "overflows" */ + 1
- _ uint16 = smallestFloat64 /* ERROR "overflows" */
+ _ uint16 = smallestFloat64 /* ERROR "truncated" */
_ = uint16(0 /* ERROR "cannot convert" */ - 1)
_ = uint16(0)
@@ -159,7 +159,7 @@
_ uint32 = 0
_ uint32 = maxUint32
_ uint32 = maxUint32 /* ERROR "overflows" */ + 1
- _ uint32 = smallestFloat64 /* ERROR "overflows" */
+ _ uint32 = smallestFloat64 /* ERROR "truncated" */
_ = uint32(0 /* ERROR "cannot convert" */ - 1)
_ = uint32(0)
@@ -173,7 +173,7 @@
_ uint64 = 0
_ uint64 = maxUint64
_ uint64 = maxUint64 /* ERROR "overflows" */ + 1
- _ uint64 = smallestFloat64 /* ERROR "overflows" */
+ _ uint64 = smallestFloat64 /* ERROR "truncated" */
_ = uint64(0 /* ERROR "cannot convert" */ - 1)
_ = uint64(0)
@@ -187,7 +187,7 @@
_ uint = 0
_ uint = maxUint
_ uint = maxUint /* ERROR "overflows" */ + 1
- _ uint = smallestFloat64 /* ERROR "overflows" */
+ _ uint = smallestFloat64 /* ERROR "truncated" */
_ = uint(0 /* ERROR "cannot convert" */ - 1)
_ = uint(0)
@@ -201,7 +201,7 @@
_ uintptr = 0
_ uintptr = maxUintptr
_ uintptr = maxUintptr /* ERROR "overflows" */ + 1
- _ uintptr = smallestFloat64 /* ERROR "overflows" */
+ _ uintptr = smallestFloat64 /* ERROR "truncated" */
_ = uintptr(0 /* ERROR "cannot convert" */ - 1)
_ = uintptr(0)
diff --git a/go/types/testdata/expr3.src b/go/types/testdata/expr3.src
index 32c74b6..039a5fb 100644
--- a/go/types/testdata/expr3.src
+++ b/go/types/testdata/expr3.src
@@ -12,7 +12,7 @@
var a [10]int
_ = a[true /* ERROR "cannot convert" */ ]
_ = a["foo" /* ERROR "cannot convert" */ ]
- _ = a[1.1 /* ERROR "overflows" */ ]
+ _ = a[1.1 /* ERROR "truncated" */ ]
_ = a[1.0]
_ = a[- /* ERROR "negative" */ 1]
_ = a[- /* ERROR "negative" */ 1 :]
@@ -180,7 +180,7 @@
_ = T0{1, b /* ERROR "mixture" */ : 2, 3}
_ = T0{1, 2} /* ERROR "too few values" */
_ = T0{1, 2, 3, 4 /* ERROR "too many values" */ }
- _ = T0{1, "foo" /* ERROR "cannot convert" */, 3.4 /* ERROR "overflows" */}
+ _ = T0{1, "foo" /* ERROR "cannot convert" */, 3.4 /* ERROR "truncated" */}
// invalid type
type P *struct{
@@ -210,7 +210,7 @@
_ = A1{5: 5, 6, 7, 3: 3, 1 /* ERROR "overflows" */ <<100: 4, 5 /* ERROR "duplicate index" */ }
_ = A1{5: 5, 6, 7, 4: 4, 1 /* ERROR "overflows" */ <<100: 4}
_ = A1{2.0}
- _ = A1{2.1 /* ERROR "overflows" */ }
+ _ = A1{2.1 /* ERROR "truncated" */ }
_ = A1{"foo" /* ERROR "cannot convert" */ }
// indices must be integer constants
@@ -218,7 +218,7 @@
const f = 2.1
const s = "foo"
_ = A1{i /* ERROR "index i must be integer constant" */ : 0}
- _ = A1{f /* ERROR "cannot be .* represented" */ : 0}
+ _ = A1{f /* ERROR "truncated" */ : 0}
_ = A1{s /* ERROR "cannot convert" */ : 0}
a0 := [...]int{}
@@ -267,7 +267,7 @@
_ = S0{5: 5, 6, 7, 3: 3, 1 /* ERROR "overflows" */ <<100: 4, 5 /* ERROR "duplicate index" */ }
_ = S0{5: 5, 6, 7, 4: 4, 1 /* ERROR "overflows" */ <<100: 4}
_ = S0{2.0}
- _ = S0{2.1 /* ERROR "overflows" */ }
+ _ = S0{2.1 /* ERROR "truncated" */ }
_ = S0{"foo" /* ERROR "cannot convert" */ }
// indices must be resolved correctly
@@ -281,7 +281,7 @@
const f = 2.1
const s = "foo"
_ = S0{i /* ERROR "index i must be integer constant" */ : 0}
- _ = S0{f /* ERROR "cannot be .* represented" */ : 0}
+ _ = S0{f /* ERROR "truncated" */ : 0}
_ = S0{s /* ERROR "cannot convert" */ : 0}
}
diff --git a/go/types/testdata/stmt0.src b/go/types/testdata/stmt0.src
index 1939411..aecdc4c 100644
--- a/go/types/testdata/stmt0.src
+++ b/go/types/testdata/stmt0.src
@@ -67,10 +67,10 @@
// test cases for issue 5800
var (
- _ int = nil /* ERROR "cannot convert nil" */
- _ [10]int = nil /* ERROR "cannot convert nil" */
+ _ int = nil /* ERROR "untyped nil value" */
+ _ [10]int = nil /* ERROR "untyped nil value" */
_ []byte = nil
- _ struct{} = nil /* ERROR "cannot convert nil" */
+ _ struct{} = nil /* ERROR "untyped nil value" */
_ func() = nil
_ map[int]string = nil
_ chan int = nil
diff --git a/go/types/typexpr.go b/go/types/typexpr.go
index a6ad506..acc098e 100644
--- a/go/types/typexpr.go
+++ b/go/types/typexpr.go
@@ -97,6 +97,10 @@
x.mode = builtin
x.id = obj.id
+ case *Nil:
+ // no need to "use" the nil object
+ x.mode = value
+
default:
unreachable()
}
@@ -118,7 +122,7 @@
}
t := check.typ0(e, def, cycleOk)
- assert(e != nil && t != nil && !isUntyped(t))
+ assert(e != nil && t != nil && isTyped(t))
check.recordTypeAndValue(e, t, nil)
@@ -353,7 +357,7 @@
check.errorf(x.pos(), "%s used as type", &x)
case typexpr:
return x.typ
- case constant:
+ case value:
if x.isNil() {
return nil
}
diff --git a/go/types/universe.go b/go/types/universe.go
index 70876c6..cf96f6d 100644
--- a/go/types/universe.go
+++ b/go/types/universe.go
@@ -80,7 +80,6 @@
{"true", UntypedBool, exact.MakeBool(true)},
{"false", UntypedBool, exact.MakeBool(false)},
{"iota", UntypedInt, exact.MakeInt64(0)},
- {"nil", UntypedNil, exact.MakeNil()},
}
func defPredeclaredConsts() {
@@ -89,6 +88,10 @@
}
}
+func defPredeclaredNil() {
+ def(&Nil{object{name: "nil", typ: Typ[UntypedNil]}})
+}
+
// A builtinId is the id of a builtin function.
type builtinId int
@@ -172,6 +175,7 @@
defPredeclaredTypes()
defPredeclaredConsts()
+ defPredeclaredNil()
defPredeclaredFuncs()
universeIota = Universe.Lookup("iota").(*Const)
diff --git a/ssa/builder.go b/ssa/builder.go
index e6dc553..a23b877 100644
--- a/ssa/builder.go
+++ b/ssa/builder.go
@@ -637,9 +637,12 @@
case *ast.Ident:
obj := fn.Pkg.objectOf(e)
- // Universal built-in?
- if obj, ok := obj.(*types.Builtin); ok {
+ // Universal built-in or nil?
+ switch obj := obj.(type) {
+ case *types.Builtin:
return fn.Prog.builtins[obj]
+ case *types.Nil:
+ return nilConst(fn.Pkg.typeOf(e))
}
// Package-level func or var?
if v := b.lookup(fn.Pkg, obj); v != nil {
diff --git a/ssa/const.go b/ssa/const.go
index 2f9acd0..6c9ee30 100644
--- a/ssa/const.go
+++ b/ssa/const.go
@@ -31,7 +31,7 @@
// be any reference type, including interfaces.
//
func nilConst(typ types.Type) *Const {
- return NewConst(exact.MakeNil(), typ)
+ return NewConst(nil, typ)
}
// zeroConst returns a new "zero" constant of the specified type,
@@ -67,7 +67,9 @@
func (c *Const) Name() string {
var s string
- if c.Value.Kind() == exact.String {
+ if c.Value == nil {
+ s = "nil"
+ } else if c.Value.Kind() == exact.String {
s = exact.StringVal(c.Value)
const max = 20
if len(s) > max {
@@ -94,7 +96,7 @@
// IsNil returns true if this constant represents a typed or untyped nil value.
func (c *Const) IsNil() bool {
- return c.Value.Kind() == exact.Nil
+ return c.Value == nil
}
// Int64 returns the numeric value of this constant truncated to fit
diff --git a/ssa/ssa.go b/ssa/ssa.go
index dc498b6..db5465e 100644
--- a/ssa/ssa.go
+++ b/ssa/ssa.go
@@ -363,7 +363,7 @@
//
// Value holds the exact value of the constant, independent of its
// Type(), using the same representation as package go/exact uses for
-// constants.
+// constants, or nil for a typed nil value.
//
// Pos() returns token.NoPos.
//