From 56dcf976561ff6c666d9d1fe6231557ac2072883 Mon Sep 17 00:00:00 2001 From: Robert Griesemer Date: Fri, 22 Oct 2021 09:49:15 -0700 Subject: [PATCH] cmd/compile/internal/types2: generalize assignability to generic types Similar to conversions, handle ordinary cases first, followed by type-by-type assignability tests in case of type parameters with specific types in their type sets. Add a new class of type checker tests, in testdata/spec, which I hope we can populate over time with tests following the spec organization. Moved the conversions.go2 tests in the same dir. Change-Id: Iac253ae375c08022bdc39e92e3951ec3f509e432 Reviewed-on: https://go-review.googlesource.com/c/go/+/357917 Trust: Robert Griesemer Reviewed-by: Robert Findley --- src/cmd/compile/internal/types2/check_test.go | 1 + src/cmd/compile/internal/types2/operand.go | 107 ++++++-- .../types2/testdata/spec/assignability.go2 | 234 ++++++++++++++++++ .../{examples => spec}/conversions.go2 | 0 4 files changed, 316 insertions(+), 26 deletions(-) create mode 100644 src/cmd/compile/internal/types2/testdata/spec/assignability.go2 rename src/cmd/compile/internal/types2/testdata/{examples => spec}/conversions.go2 (100%) diff --git a/src/cmd/compile/internal/types2/check_test.go b/src/cmd/compile/internal/types2/check_test.go index e71df87f2c..d4c7b7b39b 100644 --- a/src/cmd/compile/internal/types2/check_test.go +++ b/src/cmd/compile/internal/types2/check_test.go @@ -278,6 +278,7 @@ func TestManual(t *testing.T) { // TODO(gri) go/types has extra TestLongConstants and TestIndexRepresentability tests func TestCheck(t *testing.T) { DefPredeclaredTestFuncs(); testDirFiles(t, "testdata/check", 75, false) } // TODO(gri) narrow column tolerance +func TestSpec(t *testing.T) { DefPredeclaredTestFuncs(); testDirFiles(t, "testdata/spec", 0, false) } func TestExamples(t *testing.T) { testDirFiles(t, "testdata/examples", 0, false) } func TestFixedbugs(t *testing.T) { testDirFiles(t, "testdata/fixedbugs", 0, false) } diff --git a/src/cmd/compile/internal/types2/operand.go b/src/cmd/compile/internal/types2/operand.go index 69426f4d03..92ae0a95fc 100644 --- a/src/cmd/compile/internal/types2/operand.go +++ b/src/cmd/compile/internal/types2/operand.go @@ -249,53 +249,46 @@ func (x *operand) assignableTo(check *Checker, T Type, reason *string) (bool, er V := x.typ - const debugAssignableTo = false - if debugAssignableTo && check != nil { - check.dump("V = %s", V) - check.dump("T = %s", T) - } - // x's type is identical to T if Identical(V, T) { return true, 0 } - Vu := optype(V) - Tu := optype(T) - - if debugAssignableTo && check != nil { - check.dump("Vu = %s", Vu) - check.dump("Tu = %s", Tu) - } + Vu := under(V) + Tu := under(T) + Vp, _ := Vu.(*TypeParam) + Tp, _ := Tu.(*TypeParam) // x is an untyped value representable by a value of type T. if isUntyped(Vu) { - if t, ok := under(T).(*TypeParam); ok { - return t.is(func(t *term) bool { - // TODO(gri) this could probably be more efficient + assert(Vp == nil) + if Tp != nil { + // T is a type parameter: x is assignable to T if it is + // representable by each specific type in the type set of T. + return Tp.is(func(t *term) bool { if t == nil { return false } - if t.tilde { - // TODO(gri) We need to check assignability - // for the underlying type of x. - } - ok, _ := x.assignableTo(check, t.typ, reason) - return ok + // A term may be a tilde term but the underlying + // type of an untyped value doesn't change so we + // don't need to do anything special. + newType, _, _ := check.implicitTypeAndValue(x, t.typ) + return newType != nil }), _IncompatibleAssign } - newType, _, _ := check.implicitTypeAndValue(x, Tu) + newType, _, _ := check.implicitTypeAndValue(x, T) return newType != nil, _IncompatibleAssign } // Vu is typed // x's type V and T have identical underlying types // and at least one of V or T is not a named type - if Identical(Vu, Tu) && (!isNamed(V) || !isNamed(T)) { + // and neither is a type parameter. + if Identical(Vu, Tu) && (!isNamed(V) || !isNamed(T)) && Vp == nil && Tp == nil { return true, 0 } - // T is an interface type and x implements T + // T is an interface type and x implements T and T is not a type parameter if Ti, ok := Tu.(*Interface); ok { if m, wrongType := check.missingMethod(V, Ti, true); m != nil /* Implements(V, Ti) */ { if reason != nil { @@ -325,7 +318,69 @@ func (x *operand) assignableTo(check *Checker, T Type, reason *string) (bool, er } } - return false, _IncompatibleAssign + // common case: if we don't have type parameters, we're done + if Vp == nil && Tp == nil { + return false, _IncompatibleAssign + } + + // determine type parameter operands with specific type terms + if Vp != nil && !Vp.hasTerms() { + Vp = nil + } + if Tp != nil && !Tp.hasTerms() { + Tp = nil + } + + errorf := func(format string, args ...interface{}) { + if check != nil && reason != nil { + msg := check.sprintf(format, args...) + if *reason != "" { + msg += "\n\t" + *reason + } + *reason = msg + } + } + + ok := false + code := _IncompatibleAssign + switch { + case Vp != nil && Tp != nil: + x := *x // don't clobber outer x + ok = Vp.is(func(V *term) bool { + x.typ = V.typ + return Tp.is(func(T *term) bool { + ok, code = x.assignableTo(check, T.typ, reason) + if !ok { + errorf("cannot assign %s (in %s) to %s (in %s)", V.typ, Vp, T.typ, Tp) + return false + } + return true + }) + }) + case Vp != nil: + x := *x // don't clobber outer x + ok = Vp.is(func(V *term) bool { + x.typ = V.typ + ok, code = x.assignableTo(check, T, reason) + if !ok { + errorf("cannot assign %s (in %s) to %s", V.typ, Vp, T) + return false + } + return true + }) + case Tp != nil: + x := *x // don't clobber outer x + ok = Tp.is(func(T *term) bool { + ok, code = x.assignableTo(check, T.typ, reason) + if !ok { + errorf("cannot assign %s to %s (in %s)", x.typ, T.typ, Tp) + return false + } + return true + }) + } + + return ok, code } // kind2tok translates syntax.LitKinds into token.Tokens. diff --git a/src/cmd/compile/internal/types2/testdata/spec/assignability.go2 b/src/cmd/compile/internal/types2/testdata/spec/assignability.go2 new file mode 100644 index 0000000000..1507cabb1d --- /dev/null +++ b/src/cmd/compile/internal/types2/testdata/spec/assignability.go2 @@ -0,0 +1,234 @@ +// Copyright 2021 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package assignability + +// See the end of this package for the declarations +// of the types and variables used in these tests. + +// "x's type is identical to T" +func _[TP any](X TP) { + b = b + a = a + l = l + s = s + p = p + f = f + i = i + m = m + c = c + d = d + + B = B + A = A + L = L + S = S + P = P + F = F + I = I + M = M + C = C + D = D + X = X +} + +// "x's type V and T have identical underlying types and at least one +// of V or T is not a defined type and neither is a type parameter" +func _[TP1, TP2 Interface](X1 TP1, X2 TP2) { + b = B // ERROR cannot use B .* as int value + a = A + l = L + s = S + p = P + f = F + i = I + m = M + c = C + d = D + + B = b // ERROR cannot use b .* as Basic value + A = a + L = l + S = s + P = p + F = f + I = i + M = m + C = c + D = d + X1 = i // ERROR cannot use i .* as TP1 value + X1 = X2 // ERROR cannot use X2 .* as TP1 value +} + +// "T is an interface type and x implements T and T is not a type parameter" +func _[TP Interface](X TP) { + i = d // ERROR missing method m + i = D + i = X + X = i // ERROR cannot use i .* as TP value +} + +// "x is a bidirectional channel value, T is a channel type, x's type V and T have identical element types, and at least one of V or T is not a defined type" +type ( + _SendChan = chan<- int + _RecvChan = <-chan int + + SendChan _SendChan + RecvChan _RecvChan +) + +func _[ + _CC ~_Chan, + _SC ~_SendChan, + _RC ~_RecvChan, + + CC Chan, + SC SendChan, + RC RecvChan, +]() { + var ( + _ _SendChan = c + _ _RecvChan = c + _ _Chan = c + + _ _SendChan = C + _ _RecvChan = C + _ _Chan = C + + _ SendChan = c + _ RecvChan = c + _ Chan = c + + _ SendChan = C // ERROR cannot use C .* as SendChan value + _ RecvChan = C // ERROR cannot use C .* as RecvChan value + _ Chan = C + _ Chan = make /* ERROR cannot use make\(chan Basic\) .* as Chan value */ (chan Basic) + ) + + var ( + _ _CC = C + _ _SC = C + _ _RC = C + + _ CC = _CC(nil) + _ SC = _CC(nil) + _ RC = _CC(nil) + + _ CC = C + _ SC = C // ERROR cannot use C .* as SC value .* cannot assign Chan to SendChan + _ RC = C // ERROR cannot use C .* as RC value .* cannot assign Chan to RecvChan + ) +} + +// "x is the predeclared identifier nil and T is a pointer, function, slice, map, channel, or interface type" +func _[TP Interface](X TP) { + b = nil // ERROR cannot use untyped nil + a = nil // ERROR cannot use untyped nil + l = nil + s = nil // ERROR cannot use untyped nil + p = nil + f = nil + i = nil + m = nil + c = nil + d = nil // ERROR cannot use untyped nil + + B = nil // ERROR cannot use untyped nil + A = nil // ERROR cannot use untyped nil + L = nil + S = nil // ERROR cannot use untyped nil + P = nil + F = nil + I = nil + M = nil + C = nil + D = nil // ERROR cannot use untyped nil + X = nil // ERROR cannot use untyped nil +} + +// "x is an untyped constant representable by a value of type T" +func _[ + Int8 ~int8, + Int16 ~int16, + Int32 ~int32, + Int64 ~int64, + Int8_16 ~int8 | ~int16, +]( + i8 Int8, + i16 Int16, + i32 Int32, + i64 Int64, + i8_16 Int8_16, +) { + b = 42 + b = 42.0 + // etc. + + i8 = -1 << 7 + i8 = 1<<7 - 1 + i16 = -1 << 15 + i16 = 1<<15 - 1 + i32 = -1 << 31 + i32 = 1<<31 - 1 + i64 = -1 << 63 + i64 = 1<<63 - 1 + + i8_16 = -1 << 7 + i8_16 = 1<<7 - 1 + i8_16 = - /* ERROR cannot use .* as Int8_16 */ 1 << 15 + i8_16 = 1 /* ERROR cannot use .* as Int8_16 */ <<15 - 1 +} + +// proto-types for tests + +type ( + _Basic = int + _Array = [10]int + _Slice = []int + _Struct = struct{ f int } + _Pointer = *int + _Func = func(x int) string + _Interface = interface{ m() int } + _Map = map[string]int + _Chan = chan int + + Basic _Basic + Array _Array + Slice _Slice + Struct _Struct + Pointer _Pointer + Func _Func + Interface _Interface + Map _Map + Chan _Chan + Defined _Struct +) + +func (Defined) m() int + +// proto-variables for tests + +var ( + b _Basic + a _Array + l _Slice + s _Struct + p _Pointer + f _Func + i _Interface + m _Map + c _Chan + d _Struct + + B Basic + A Array + L Slice + S Struct + P Pointer + F Func + I Interface + M Map + C Chan + D Defined +) diff --git a/src/cmd/compile/internal/types2/testdata/examples/conversions.go2 b/src/cmd/compile/internal/types2/testdata/spec/conversions.go2 similarity index 100% rename from src/cmd/compile/internal/types2/testdata/examples/conversions.go2 rename to src/cmd/compile/internal/types2/testdata/spec/conversions.go2 -- 2.48.1