1
0
mirror of https://github.com/golang/go synced 2024-11-26 07:27:59 -07:00

[dev.typeparams] cmd/compile/internal/types2: report type of nil based on context

With this CL, the type reported for uses of the predeclared
identifier nil changes from untyped nil to the type of the
context within which nil is used, matching the behaviour of
types2 for other untyped types.

If an untyped nil value is assigned or converted to an
interface, the nil expression is given the interface type.

The predicate TypeAndValue.IsNil doesn't change in behavior,
it still reports whether the relevant expression is a (typed
or untyped) nil value.

Change-Id: Id766468f3f3f2a53e4c55e1e6cd521e459c4a94f
Reviewed-on: https://go-review.googlesource.com/c/go/+/284218
Trust: Robert Griesemer <gri@golang.org>
Reviewed-by: Robert Findley <rfindley@google.com>
Reviewed-by: Matthew Dempsky <mdempsky@google.com>
This commit is contained in:
Robert Griesemer 2021-01-15 18:19:00 -08:00
parent 48a3cb399d
commit d8796b5670
10 changed files with 97 additions and 88 deletions

View File

@ -325,16 +325,17 @@ func (tv TypeAndValue) IsBuiltin() bool {
// nil Value.
func (tv TypeAndValue) IsValue() bool {
switch tv.mode {
case constant_, variable, mapindex, value, commaok, commaerr:
case constant_, variable, mapindex, value, nilvalue, commaok, commaerr:
return true
}
return false
}
// IsNil reports whether the corresponding expression denotes the
// predeclared value nil.
// predeclared value nil. Depending on context, it may have been
// given a type different from UntypedNil.
func (tv TypeAndValue) IsNil() bool {
return tv.mode == value && tv.Type == Typ[UntypedNil]
return tv.mode == nilvalue
}
// Addressable reports whether the corresponding expression

View File

@ -198,37 +198,41 @@ func TestTypesInfo(t *testing.T) {
{`package b4; var x interface{} = "foo"`, `"foo"`, `string`},
// uses of nil
{`package n0; var _ *int = nil`, `nil`, `untyped nil`},
{`package n1; var _ func() = nil`, `nil`, `untyped nil`},
{`package n2; var _ []byte = nil`, `nil`, `untyped nil`},
{`package n3; var _ map[int]int = nil`, `nil`, `untyped nil`},
{`package n4; var _ chan int = nil`, `nil`, `untyped nil`},
{`package n5; var _ interface{} = nil`, `nil`, `untyped nil`},
{`package n6; import "unsafe"; var _ unsafe.Pointer = nil`, `nil`, `untyped nil`},
{`package n0; var _ *int = nil`, `nil`, `*int`},
{`package n1; var _ func() = nil`, `nil`, `func()`},
{`package n2; var _ []byte = nil`, `nil`, `[]byte`},
{`package n3; var _ map[int]int = nil`, `nil`, `map[int]int`},
{`package n4; var _ chan int = nil`, `nil`, `chan int`},
{`package n5a; var _ interface{} = (*int)(nil)`, `nil`, `*int`},
{`package n5b; var _ interface{m()} = nil`, `nil`, `interface{m()}`},
{`package n6; import "unsafe"; var _ unsafe.Pointer = nil`, `nil`, `unsafe.Pointer`},
{`package n10; var (x *int; _ = x == nil)`, `nil`, `untyped nil`},
{`package n11; var (x func(); _ = x == nil)`, `nil`, `untyped nil`},
{`package n12; var (x []byte; _ = x == nil)`, `nil`, `untyped nil`},
{`package n13; var (x map[int]int; _ = x == nil)`, `nil`, `untyped nil`},
{`package n14; var (x chan int; _ = x == nil)`, `nil`, `untyped nil`},
{`package n15; var (x interface{}; _ = x == nil)`, `nil`, `untyped nil`},
{`package n15; import "unsafe"; var (x unsafe.Pointer; _ = x == nil)`, `nil`, `untyped nil`},
{`package n10; var (x *int; _ = x == nil)`, `nil`, `*int`},
{`package n11; var (x func(); _ = x == nil)`, `nil`, `func()`},
{`package n12; var (x []byte; _ = x == nil)`, `nil`, `[]byte`},
{`package n13; var (x map[int]int; _ = x == nil)`, `nil`, `map[int]int`},
{`package n14; var (x chan int; _ = x == nil)`, `nil`, `chan int`},
{`package n15a; var (x interface{}; _ = x == (*int)(nil))`, `nil`, `*int`},
{`package n15b; var (x interface{m()}; _ = x == nil)`, `nil`, `interface{m()}`},
{`package n15; import "unsafe"; var (x unsafe.Pointer; _ = x == nil)`, `nil`, `unsafe.Pointer`},
{`package n20; var _ = (*int)(nil)`, `nil`, `untyped nil`},
{`package n21; var _ = (func())(nil)`, `nil`, `untyped nil`},
{`package n22; var _ = ([]byte)(nil)`, `nil`, `untyped nil`},
{`package n23; var _ = (map[int]int)(nil)`, `nil`, `untyped nil`},
{`package n24; var _ = (chan int)(nil)`, `nil`, `untyped nil`},
{`package n25; var _ = (interface{})(nil)`, `nil`, `untyped nil`},
{`package n26; import "unsafe"; var _ = unsafe.Pointer(nil)`, `nil`, `untyped nil`},
{`package n20; var _ = (*int)(nil)`, `nil`, `*int`},
{`package n21; var _ = (func())(nil)`, `nil`, `func()`},
{`package n22; var _ = ([]byte)(nil)`, `nil`, `[]byte`},
{`package n23; var _ = (map[int]int)(nil)`, `nil`, `map[int]int`},
{`package n24; var _ = (chan int)(nil)`, `nil`, `chan int`},
{`package n25a; var _ = (interface{})((*int)(nil))`, `nil`, `*int`},
{`package n25b; var _ = (interface{m()})(nil)`, `nil`, `interface{m()}`},
{`package n26; import "unsafe"; var _ = unsafe.Pointer(nil)`, `nil`, `unsafe.Pointer`},
{`package n30; func f(*int) { f(nil) }`, `nil`, `untyped nil`},
{`package n31; func f(func()) { f(nil) }`, `nil`, `untyped nil`},
{`package n32; func f([]byte) { f(nil) }`, `nil`, `untyped nil`},
{`package n33; func f(map[int]int) { f(nil) }`, `nil`, `untyped nil`},
{`package n34; func f(chan int) { f(nil) }`, `nil`, `untyped nil`},
{`package n35; func f(interface{}) { f(nil) }`, `nil`, `untyped nil`},
{`package n35; import "unsafe"; func f(unsafe.Pointer) { f(nil) }`, `nil`, `untyped nil`},
{`package n30; func f(*int) { f(nil) }`, `nil`, `*int`},
{`package n31; func f(func()) { f(nil) }`, `nil`, `func()`},
{`package n32; func f([]byte) { f(nil) }`, `nil`, `[]byte`},
{`package n33; func f(map[int]int) { f(nil) }`, `nil`, `map[int]int`},
{`package n34; func f(chan int) { f(nil) }`, `nil`, `chan int`},
{`package n35a; func f(interface{}) { f((*int)(nil)) }`, `nil`, `*int`},
{`package n35b; func f(interface{m()}) { f(nil) }`, `nil`, `interface{m()}`},
{`package n35; import "unsafe"; func f(unsafe.Pointer) { f(nil) }`, `nil`, `unsafe.Pointer`},
// comma-ok expressions
{`package p0; var x interface{}; var _, _ = x.(int)`,

View File

@ -20,7 +20,7 @@ func (check *Checker) assignment(x *operand, T Type, context string) {
switch x.mode {
case invalid:
return // error reported before
case constant_, variable, mapindex, value, commaok, commaerr:
case constant_, variable, mapindex, value, nilvalue, commaok, commaerr:
// ok
default:
// we may get here because of other problems (issue #39634, crash 12)
@ -35,12 +35,13 @@ func (check *Checker) assignment(x *operand, T Type, context string) {
// 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 T == nil || IsInterface(T) {
if T == nil && x.typ == Typ[UntypedNil] {
if x.isNil() {
if T == nil {
check.errorf(x, "use of untyped nil in %s", context)
x.mode = invalid
return
}
} else if T == nil || IsInterface(T) {
target = Default(x.typ)
}
check.convertUntyped(x, target)
@ -192,6 +193,9 @@ func (check *Checker) assignVar(lhs syntax.Expr, x *operand) Type {
return nil
case variable, mapindex:
// ok
case nilvalue:
check.errorf(&z, "cannot assign to nil") // default would print "untyped nil"
return nil
default:
if sel, ok := z.expr.(*syntax.SelectorExpr); ok {
var op operand

View File

@ -49,15 +49,17 @@ func (check *Checker) conversion(x *operand, T Type) {
// given a type explicitly by a constant declaration or conversion,...".
if isUntyped(x.typ) {
final := T
// - For conversions to interfaces, use the argument's default type.
// - For conversions to interfaces, except for untyped nil arguments,
// use the argument's default type.
// - For conversions of untyped constants to non-constant types, also
// use the default type (e.g., []byte("foo") should report string
// not []byte as type for the constant "foo").
// - Keep untyped nil for untyped nil arguments.
// - For integer to string conversions, keep the argument type.
// (See also the TODO below.)
if IsInterface(T) || constArg && !isConstType(T) || x.isNil() {
final = Default(x.typ) // default type of untyped nil is untyped nil
if x.typ == Typ[UntypedNil] {
// ok
} else if IsInterface(T) || constArg && !isConstType(T) {
final = Default(x.typ)
} else if isInteger(x.typ) && isString(T) {
final = x.typ
}

View File

@ -606,18 +606,15 @@ func (check *Checker) convertUntyped(x *operand, target Type) {
}
for _, t := range unpack(types) {
check.convertUntypedInternal(x, t)
x := *x // make a copy; convertUntypedInternal modifies x
check.convertUntypedInternal(&x, t)
if x.mode == invalid {
goto Error
}
}
// keep nil untyped (was bug #39755)
if x.isNil() {
target = Typ[UntypedNil]
}
x.typ = target
check.updateExprType(x.expr, target, true) // UntypedNils are final
check.updateExprType(x.expr, target, true)
return
}
@ -634,6 +631,14 @@ Error:
func (check *Checker) convertUntypedInternal(x *operand, target Type) {
assert(isTyped(target))
if x.isNil() {
assert(isUntyped(x.typ))
if hasNil(target) {
goto OK
}
goto Error
}
// typed target
switch t := optype(target.Under()).(type) {
case *Basic:
@ -648,7 +653,7 @@ func (check *Checker) convertUntypedInternal(x *operand, target Type) {
// 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.
// the value nil. Nil was handled upfront.
switch x.typ.(*Basic).kind {
case UntypedBool:
if !isBoolean(target) {
@ -662,12 +667,6 @@ func (check *Checker) convertUntypedInternal(x *operand, target Type) {
// 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
}
target = Typ[UntypedNil]
default:
goto Error
}
@ -678,34 +677,21 @@ func (check *Checker) convertUntypedInternal(x *operand, target Type) {
return x.mode != invalid
})
case *Interface:
// Update operand types to the default type rather then
// the target (interface) type: values must have concrete
// dynamic types. If the value is nil, keep it untyped
// (this is important for tools such as go vet which need
// the dynamic type for argument checking of say, print
// functions)
if x.isNil() {
target = Typ[UntypedNil]
} else {
// cannot assign untyped values to non-empty interfaces
check.completeInterface(nopos, t)
if !t.Empty() {
goto Error
}
target = Default(x.typ)
// Update operand types to the default type rather then the target
// (interface) type: values must have concrete dynamic types.
// Untyped nil was handled upfront.
check.completeInterface(nopos, t)
if !t.Empty() {
goto Error // cannot assign untyped values to non-empty interfaces
}
case *Pointer, *Signature, *Slice, *Map, *Chan:
if !x.isNil() {
goto Error
}
// keep nil untyped - see comment for interfaces, above
target = Typ[UntypedNil]
target = Default(x.typ)
default:
goto Error
}
OK:
x.typ = target
check.updateExprType(x.expr, target, true) // UntypedNils are final
check.updateExprType(x.expr, target, true)
return
Error:

View File

@ -76,7 +76,7 @@ var (
}
case *syntax.Name:
if x.Value == "nil" {
want = Typ[UntypedNil]
want = NewInterfaceType(nil, nil) // interface{}
}
}
if want != nil && !Identical(tv.Type, want) {

View File

@ -27,6 +27,7 @@ const (
variable // operand is an addressable variable
mapindex // operand is a map index expression (acts like a variable on lhs, commaok on rhs of an assignment)
value // operand is a computed value
nilvalue // operand is the nil value
commaok // like value, but operand may be used in a comma,ok expression
commaerr // like commaok, but second value is error, not boolean
cgofunc // operand is a cgo function
@ -41,6 +42,7 @@ var operandModeString = [...]string{
variable: "variable",
mapindex: "map index expression",
value: "value",
nilvalue: "nil",
commaok: "comma, ok expression",
commaerr: "comma, error expression",
cgofunc: "cgo function",
@ -96,6 +98,9 @@ func (x *operand) Pos() syntax.Pos {
// value <expr> (<untyped kind> <mode> )
// value <expr> ( <mode> of type <typ>)
//
// nilvalue untyped nil
// nilvalue nil ( of type <typ>)
//
// commaok <expr> (<untyped kind> <mode> )
// commaok <expr> ( <mode> of type <typ>)
//
@ -106,6 +111,18 @@ func (x *operand) Pos() syntax.Pos {
// cgofunc <expr> ( <mode> of type <typ>)
//
func operandString(x *operand, qf Qualifier) string {
// special-case nil
if x.mode == nilvalue {
switch x.typ {
case nil, Typ[Invalid]:
return "nil (with invalid type)"
case Typ[UntypedNil]:
return "untyped nil"
default:
return fmt.Sprintf("nil (of type %s)", TypeString(x.typ, qf))
}
}
var buf bytes.Buffer
var expr string
@ -222,10 +239,8 @@ func (x *operand) setConst(k syntax.LitKind, lit string) {
x.val = val
}
// isNil reports whether x is the nil value.
func (x *operand) isNil() bool {
return x.mode == value && x.typ == Typ[UntypedNil]
}
// isNil reports whether x is a typed or the untyped nil value.
func (x *operand) isNil() bool { return x.mode == nilvalue }
// TODO(gri) The functions operand.assignableTo, checker.convertUntyped,
// checker.representable, and checker.assignment are

View File

@ -69,10 +69,10 @@ func assignments1() {
// test cases for issue 5800
var (
_ int = nil /* ERROR "untyped nil value" */
_ [10]int = nil /* ERROR "untyped nil value" */
_ int = nil /* ERROR "cannot convert untyped nil" */
_ [10]int = nil /* ERROR "cannot convert untyped nil" */
_ []byte = nil
_ struct{} = nil /* ERROR "untyped nil value" */
_ struct{} = nil /* ERROR "cannot convert untyped nil" */
_ func() = nil
_ map[int]string = nil
_ chan int = nil

View File

@ -111,7 +111,7 @@ func (check *Checker) ident(x *operand, e *syntax.Name, def *Named, wantType boo
x.mode = builtin
case *Nil:
x.mode = value
x.mode = nilvalue
default:
unreachable()
@ -631,11 +631,8 @@ func (check *Checker) typOrNil(e syntax.Expr) Type {
case typexpr:
check.instantiatedOperand(&x)
return x.typ
case value:
if x.isNil() {
return nil
}
fallthrough
case nilvalue:
return nil
default:
check.errorf(&x, "%s is not a type", &x)
}

View File

@ -9,5 +9,5 @@
package p
func f() uintptr {
return nil // ERROR "cannot use nil as type uintptr in return argument|incompatible type|cannot convert nil"
return nil // ERROR "cannot use nil as type uintptr in return argument|incompatible type|cannot convert untyped nil"
}