diff --git a/cmd/vet/testdata/print.go b/cmd/vet/testdata/print.go index 9168d4c739..232c7b3bf5 100644 --- a/cmd/vet/testdata/print.go +++ b/cmd/vet/testdata/print.go @@ -124,7 +124,7 @@ func PrintfTests() { 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 a80939ffb8..41ce644291 100644 --- a/cmd/vet/types.go +++ b/cmd/vet/types.go @@ -62,8 +62,8 @@ func (pkg *Package) isStruct(c *ast.CompositeLit) (bool, string) { 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 @@ func (f *File) matchArgTypeInternal(t printfArgType, typ types.Type, arg ast.Exp 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 3eb39411b5..aa691bc2fc 100644 --- a/go/exact/exact.go +++ b/go/exact/exact.go @@ -25,7 +25,6 @@ const ( Unknown Kind = iota // non-numeric values - Nil Bool String @@ -53,7 +52,6 @@ type Value interface { type ( unknownVal struct{} - nilVal struct{} boolVal bool stringVal string int64Val int64 @@ -63,7 +61,6 @@ type ( ) 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 (floatVal) Kind() Kind { return Float } 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 floatVal) String() string { return x.val.String() } 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 @@ func normComplex(re, im *big.Rat) Value { // 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 @@ func match(x, y Value) (_, _ Value) { // 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 aed87b7699..061eec96b0 100644 --- a/go/exact/exact_test.go +++ b/go/exact/exact_test.go @@ -112,8 +112,6 @@ func val(lit string) Value { 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 8e218e30a8..de387738d5 100644 --- a/go/types/api.go +++ b/go/types/api.go @@ -174,12 +174,28 @@ func (conf *Config) Check(path string, fset *token.FileSet, files []*ast.File, i 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 4f0db273ac..6a9f3eab51 100644 --- a/go/types/assignments.go +++ b/go/types/assignments.go @@ -13,27 +13,62 @@ import ( "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 @@ func (check *checker) assignVar(lhs ast.Expr, x *operand) Type { // 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 e306186e39..d8b499be86 100644 --- a/go/types/call.go +++ b/go/types/call.go @@ -381,10 +381,8 @@ func (check *checker) selector(x *operand, e *ast.SelectorExpr) { // 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 6ee8e062f7..f0b129d631 100644 --- a/go/types/check.go +++ b/go/types/check.go @@ -79,7 +79,7 @@ func (check *checker) recordTypeAndValue(x ast.Expr, typ Type, val exact.Value) } 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 @@ func (conf *Config) check(pkgPath string, fset *token.FileSet, files []*ast.File // 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 a32b8dc38a..c8f88437b7 100644 --- a/go/types/conversions.go +++ b/go/types/conversions.go @@ -50,7 +50,7 @@ func (check *checker) conversion(x *operand, T Type) { // 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 d75298bae8..761e70906a 100644 --- a/go/types/expr.go +++ b/go/types/expr.go @@ -89,7 +89,8 @@ func (check *checker) unary(x *operand, op token.Token) { } 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 @@ func (check *checker) unary(x *operand, op token.Token) { 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 @@ func (check *checker) unary(x *operand, op token.Token) { } if !check.op(unaryOpPredicates, x, op) { - goto Error + x.mode = invalid + return } if x.mode == constant { @@ -122,16 +126,14 @@ func (check *checker) unary(x *operand, op token.Token) { 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 @@ func isRepresentableConst(x exact.Value, conf *Config, as BasicKind, rounded *ex case exact.String: return as == String || as == UntypedString - case exact.Nil: - return as == UntypedNil || as == UnsafePointer - default: unreachable() } @@ -328,16 +327,24 @@ func isRepresentableConst(x exact.Value, conf *Config, as BasicKind, rounded *ex 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 @@ func (check *checker) updateExprType(x ast.Expr, typ Type, final bool) { // 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 @@ func (check *checker) convertUntyped(x *operand, target Type) { // 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 @@ func (check *checker) binary(x *operand, lhs, rhs ast.Expr, op token.Token) { 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 8f8390b15a..398bb626ab 100644 --- a/go/types/lookup.go +++ b/go/types/lookup.go @@ -233,17 +233,17 @@ func consolidateMultiples(list []embeddedType) []embeddedType { 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 @@ func MissingMethod(typ Type, T *Interface, static bool) (method *Func, wrongType // 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 @@ func MissingMethod(typ Type, T *Interface, static bool) (method *Func, wrongType // 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 020a3e1624..d15e0204f8 100644 --- a/go/types/objects.go +++ b/go/types/objects.go @@ -266,3 +266,10 @@ type Builtin struct { 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 6e49475269..b73f87ad4b 100644 --- a/go/types/operand.go +++ b/go/types/operand.go @@ -184,13 +184,13 @@ func (x *operand) setConst(tok token.Token, lit string) { 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 778bf88fc4..c9cb12725e 100644 --- a/go/types/predicates.go +++ b/go/types/predicates.go @@ -49,6 +49,11 @@ func isString(typ Type) bool { 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 @@ func isConstType(typ Type) bool { 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 @@ func isComparable(typ Type) bool { 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 8aa6fb8f5d..0abac1ac98 100644 --- a/go/types/testdata/builtins.src +++ b/go/types/testdata/builtins.src @@ -322,7 +322,7 @@ func make1() { _ = 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 6515e2aa8b..19e45898e0 100644 --- a/go/types/testdata/const0.src +++ b/go/types/testdata/const0.src @@ -253,7 +253,7 @@ var _ = assert(_x == 3) // 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 df4d52293b..88e9fad3c1 100644 --- a/go/types/testdata/const1.src +++ b/go/types/testdata/const1.src @@ -61,7 +61,7 @@ const ( _ 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 @@ const ( _ 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 @@ const ( _ 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 @@ const ( _ 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 @@ const ( _ 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 @@ const ( _ 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 @@ const ( _ 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 @@ const ( _ 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 @@ const ( _ 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 @@ const ( _ 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 @@ const ( _ 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 32c74b68af..039a5fb851 100644 --- a/go/types/testdata/expr3.src +++ b/go/types/testdata/expr3.src @@ -12,7 +12,7 @@ func indexes() { 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 @@ func struct_literals() { _ = 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 @@ func array_literals() { _ = 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 @@ func array_literals() { 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 @@ func slice_literals() { _ = 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 @@ func slice_literals() { 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 19394114c0..aecdc4c774 100644 --- a/go/types/testdata/stmt0.src +++ b/go/types/testdata/stmt0.src @@ -67,10 +67,10 @@ func assignments1() { // 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 a6ad5069da..acc098ee1e 100644 --- a/go/types/typexpr.go +++ b/go/types/typexpr.go @@ -97,6 +97,10 @@ func (check *checker) ident(x *operand, e *ast.Ident, def *Named, cycleOk bool) 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 @@ func (check *checker) typ(e ast.Expr, def *Named, cycleOk bool) Type { } 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 @@ func (check *checker) typOrNil(e ast.Expr) Type { 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 70876c69eb..cf96f6d806 100644 --- a/go/types/universe.go +++ b/go/types/universe.go @@ -80,7 +80,6 @@ var predeclaredConsts = [...]struct { {"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 defPredeclaredConsts() { } } +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 @@ func init() { defPredeclaredTypes() defPredeclaredConsts() + defPredeclaredNil() defPredeclaredFuncs() universeIota = Universe.Lookup("iota").(*Const) diff --git a/ssa/builder.go b/ssa/builder.go index e6dc553924..a23b8777aa 100644 --- a/ssa/builder.go +++ b/ssa/builder.go @@ -637,9 +637,12 @@ func (b *builder) expr0(fn *Function, e ast.Expr) Value { 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 2f9acd0bb3..6c9ee301fd 100644 --- a/ssa/const.go +++ b/ssa/const.go @@ -31,7 +31,7 @@ func intConst(i int64) *Const { // 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 zeroConst(t types.Type) *Const { 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 @@ func (c *Const) Pos() token.Pos { // 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 dc498b64a1..db5465e8f6 100644 --- a/ssa/ssa.go +++ b/ssa/ssa.go @@ -363,7 +363,7 @@ type Parameter struct { // // 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. //