]> Cypherpunks.ru repositories - gostls13.git/commitdiff
go/types, types2: implement alternative comparable semantics
authorRobert Griesemer <gri@golang.org>
Wed, 19 Oct 2022 22:17:42 +0000 (15:17 -0700)
committerRobert Griesemer <gri@google.com>
Mon, 24 Oct 2022 16:41:29 +0000 (16:41 +0000)
This is an experiment to see the impact of a potential spec change:
As an exception to the rule that constraint satisfaction is the same
as interface implementation, if the flag Config.AltComparableSemantics
is set, an ordinary (non-type parameter) interface satisfies the
comparable constraint. (In go/types, the flag is not exported to
avoid changing the API.)

Disabled by default. Test files can set the flag by adding

// -altComparableSemantics

as the first line in the file.

For #52509.

Change-Id: Ib491b086feb5563920eaddefcebdacb2c5b72d61
Reviewed-on: https://go-review.googlesource.com/c/go/+/444635
Reviewed-by: Robert Findley <rfindley@google.com>
Run-TryBot: Robert Griesemer <gri@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
Reviewed-by: David Chase <drchase@google.com>
src/cmd/compile/internal/types2/api.go
src/cmd/compile/internal/types2/check_test.go
src/cmd/compile/internal/types2/instantiate.go
src/cmd/compile/internal/types2/lookup.go
src/cmd/compile/internal/types2/operand.go
src/go/types/api.go
src/go/types/check_test.go
src/go/types/instantiate.go
src/go/types/lookup.go
src/go/types/operand.go
src/internal/types/testdata/spec/comparable.go [new file with mode: 0644]

index 1f19fe0927c6ff6eab46dfa07b31dea35e6d5735..f1b8a2045641475f05995641bc0997485ebc400b 100644 (file)
@@ -167,6 +167,10 @@ type Config struct {
        // If DisableUnusedImportCheck is set, packages are not checked
        // for unused imports.
        DisableUnusedImportCheck bool
+
+       // If AltComparableSemantics is set, ordinary (non-type parameter)
+       // interfaces satisfy the comparable constraint.
+       AltComparableSemantics bool
 }
 
 func srcimporter_setUsesCgo(conf *Config) {
@@ -480,7 +484,7 @@ func Implements(V Type, T *Interface) bool {
        if V.Underlying() == Typ[Invalid] {
                return false
        }
-       return (*Checker)(nil).implements(V, T, nil)
+       return (*Checker)(nil).implements(V, T, false, nil)
 }
 
 // Identical reports whether x and y are identical types.
index 2d7783611de5b384b753ed7259a3ee0244905d4c..9a7aef7ac49ce7614f1c768a2fb5564f878dfc40 100644 (file)
@@ -130,6 +130,7 @@ func testFiles(t *testing.T, filenames []string, colDelta uint, manual bool) {
        flags := flag.NewFlagSet("", flag.PanicOnError)
        flags.StringVar(&conf.GoVersion, "lang", "", "")
        flags.BoolVar(&conf.FakeImportC, "fakeImportC", false, "")
+       flags.BoolVar(&conf.AltComparableSemantics, "altComparableSemantics", false, "")
        if err := parseFlags(filenames[0], nil, flags); err != nil {
                t.Fatal(err)
        }
index 043db9c24a1dc3b5d9cadbfbee5d649c15b537cc..55ab7a8d2534938be1a9579cea43a2fa494450c0 100644 (file)
@@ -176,7 +176,7 @@ func (check *Checker) verify(pos syntax.Pos, tparams []*TypeParam, targs []Type,
                // the parameterized type.
                bound := check.subst(pos, tpar.bound, smap, nil, ctxt)
                var cause string
-               if !check.implements(targs[i], bound, &cause) {
+               if !check.implements(targs[i], bound, true, &cause) {
                        return i, errors.New(cause)
                }
        }
@@ -184,11 +184,12 @@ func (check *Checker) verify(pos syntax.Pos, tparams []*TypeParam, targs []Type,
 }
 
 // implements checks if V implements T. The receiver may be nil if implements
-// is called through an exported API call such as AssignableTo.
+// is called through an exported API call such as AssignableTo. If constraint
+// is set, T is a type constraint.
 //
 // If the provided cause is non-nil, it may be set to an error string
 // explaining why V does not implement T.
-func (check *Checker) implements(V, T Type, cause *string) bool {
+func (check *Checker) implements(V, T Type, constraint bool, cause *string) bool {
        Vu := under(V)
        Tu := under(T)
        if Vu == Typ[Invalid] || Tu == Typ[Invalid] {
@@ -245,7 +246,11 @@ func (check *Checker) implements(V, T Type, cause *string) bool {
        // Only check comparability if we don't have a more specific error.
        checkComparability := func() bool {
                // If T is comparable, V must be comparable.
-               if Ti.IsComparable() && !comparable(V, false, nil, nil) {
+               // For constraint satisfaction, use dynamic comparability for the
+               // alternative comparable semantics such that ordinary, non-type
+               // parameter interfaces implement comparable.
+               dynamic := constraint && check != nil && check.conf.AltComparableSemantics
+               if Ti.IsComparable() && !comparable(V, dynamic, nil, nil) {
                        if cause != nil {
                                *cause = check.sprintf("%s does not implement comparable", V)
                        }
index 3e04798815cdea341eef0462cfe21c155af37c3b..21cad044335157cb8730b3c62e7b8ee7f0c16c5d 100644 (file)
@@ -472,7 +472,7 @@ func (check *Checker) newAssertableTo(V *Interface, T Type) bool {
        if IsInterface(T) {
                return true
        }
-       return check.implements(T, V, nil)
+       return check.implements(T, V, false, nil)
 }
 
 // deref dereferences typ if it is a *Pointer and returns its base and true.
index 7f10b75612d5c54183db491a4c5cfa4cad9a006d..bdbbfc1ecbec1bcd322d7e4ecea7c0d1dda55701 100644 (file)
@@ -289,7 +289,7 @@ func (x *operand) assignableTo(check *Checker, T Type, cause *string) (bool, Cod
        // T is an interface type and x implements T and T is not a type parameter.
        // Also handle the case where T is a pointer to an interface.
        if _, ok := Tu.(*Interface); ok && Tp == nil || isInterfacePtr(Tu) {
-               if !check.implements(V, T, cause) {
+               if !check.implements(V, T, false, cause) {
                        return false, InvalidIfaceAssign
                }
                return true, 0
@@ -297,7 +297,7 @@ func (x *operand) assignableTo(check *Checker, T Type, cause *string) (bool, Cod
 
        // If V is an interface, check if a missing type assertion is the problem.
        if Vi, _ := Vu.(*Interface); Vi != nil && Vp == nil {
-               if check.implements(T, V, nil) {
+               if check.implements(T, V, false, nil) {
                        // T implements V, so give hint about type assertion.
                        if cause != nil {
                                *cause = "need type assertion"
index 31f1bdd98ecbbe761e4dc672aeb3a34aa71ffd88..06a5cd8c2b9bcf51621fee7719fa2271d36a02eb 100644 (file)
@@ -167,6 +167,10 @@ type Config struct {
        // If DisableUnusedImportCheck is set, packages are not checked
        // for unused imports.
        DisableUnusedImportCheck bool
+
+       // If altComparableSemantics is set, ordinary (non-type parameter)
+       // interfaces satisfy the comparable constraint.
+       altComparableSemantics bool
 }
 
 func srcimporter_setUsesCgo(conf *Config) {
@@ -463,7 +467,7 @@ func Implements(V Type, T *Interface) bool {
        if V.Underlying() == Typ[Invalid] {
                return false
        }
-       return (*Checker)(nil).implements(V, T, nil)
+       return (*Checker)(nil).implements(V, T, false, nil)
 }
 
 // Identical reports whether x and y are identical types.
index 1ca522c07993a67b9d2961b52b9c9a587c1bdd50..201cf14f3556f52166123d38559416509e6e3721 100644 (file)
@@ -217,6 +217,7 @@ func testFiles(t *testing.T, sizes Sizes, filenames []string, srcs [][]byte, man
        flags := flag.NewFlagSet("", flag.PanicOnError)
        flags.StringVar(&conf.GoVersion, "lang", "", "")
        flags.BoolVar(&conf.FakeImportC, "fakeImportC", false, "")
+       flags.BoolVar(addrAltComparableSemantics(&conf), "altComparableSemantics", false, "")
        if err := parseFlags(filenames[0], srcs[0], flags); err != nil {
                t.Fatal(err)
        }
@@ -293,6 +294,12 @@ func readCode(err Error) int {
        return int(v.FieldByName("go116code").Int())
 }
 
+// addrAltComparableSemantics(conf) returns &conf.altComparableSemantics (unexported field).
+func addrAltComparableSemantics(conf *Config) *bool {
+       v := reflect.Indirect(reflect.ValueOf(conf))
+       return (*bool)(v.FieldByName("altComparableSemantics").Addr().UnsafePointer())
+}
+
 // TestManual is for manual testing of a package - either provided
 // as a list of filenames belonging to the package, or a directory
 // name containing the package files - after the test arguments
index df7d35998a9fbac78a7929bd20c1fc6ef4aa4367..24a9f280b06b14ddcb1e2ae1d69dab81117b3097 100644 (file)
@@ -176,7 +176,7 @@ func (check *Checker) verify(pos token.Pos, tparams []*TypeParam, targs []Type,
                // the parameterized type.
                bound := check.subst(pos, tpar.bound, smap, nil, ctxt)
                var cause string
-               if !check.implements(targs[i], bound, &cause) {
+               if !check.implements(targs[i], bound, true, &cause) {
                        return i, errors.New(cause)
                }
        }
@@ -184,11 +184,12 @@ func (check *Checker) verify(pos token.Pos, tparams []*TypeParam, targs []Type,
 }
 
 // implements checks if V implements T. The receiver may be nil if implements
-// is called through an exported API call such as AssignableTo.
+// is called through an exported API call such as AssignableTo. If constraint
+// is set, T is a type constraint.
 //
 // If the provided cause is non-nil, it may be set to an error string
 // explaining why V does not implement T.
-func (check *Checker) implements(V, T Type, cause *string) bool {
+func (check *Checker) implements(V, T Type, constraint bool, cause *string) bool {
        Vu := under(V)
        Tu := under(T)
        if Vu == Typ[Invalid] || Tu == Typ[Invalid] {
@@ -245,7 +246,11 @@ func (check *Checker) implements(V, T Type, cause *string) bool {
        // Only check comparability if we don't have a more specific error.
        checkComparability := func() bool {
                // If T is comparable, V must be comparable.
-               if Ti.IsComparable() && !comparable(V, false, nil, nil) {
+               // For constraint satisfaction, use dynamic comparability for the
+               // alternative comparable semantics such that ordinary, non-type
+               // parameter interfaces implement comparable.
+               dynamic := constraint && check != nil && check.conf.altComparableSemantics
+               if Ti.IsComparable() && !comparable(V, dynamic, nil, nil) {
                        if cause != nil {
                                *cause = check.sprintf("%s does not implement comparable", V)
                        }
index 828c8813670ed5201fb7ef47a3bd2521de2a1d58..2fac097ccb773177ef262e64f4234dcf0f8017f9 100644 (file)
@@ -471,7 +471,7 @@ func (check *Checker) newAssertableTo(V *Interface, T Type) bool {
        if IsInterface(T) {
                return true
        }
-       return check.implements(T, V, nil)
+       return check.implements(T, V, false, nil)
 }
 
 // deref dereferences typ if it is a *Pointer and returns its base and true.
index 62be4eee343bdf32cf1930514b14d54df31ec66a..e21a51a77b84ecdb66354141e0b53a31d006cd46 100644 (file)
@@ -278,7 +278,7 @@ func (x *operand) assignableTo(check *Checker, T Type, cause *string) (bool, Cod
        // T is an interface type and x implements T and T is not a type parameter.
        // Also handle the case where T is a pointer to an interface.
        if _, ok := Tu.(*Interface); ok && Tp == nil || isInterfacePtr(Tu) {
-               if !check.implements(V, T, cause) {
+               if !check.implements(V, T, false, cause) {
                        return false, InvalidIfaceAssign
                }
                return true, 0
@@ -286,7 +286,7 @@ func (x *operand) assignableTo(check *Checker, T Type, cause *string) (bool, Cod
 
        // If V is an interface, check if a missing type assertion is the problem.
        if Vi, _ := Vu.(*Interface); Vi != nil && Vp == nil {
-               if check.implements(T, V, nil) {
+               if check.implements(T, V, false, nil) {
                        // T implements V, so give hint about type assertion.
                        if cause != nil {
                                *cause = "need type assertion"
diff --git a/src/internal/types/testdata/spec/comparable.go b/src/internal/types/testdata/spec/comparable.go
new file mode 100644 (file)
index 0000000..8dbbb4e
--- /dev/null
@@ -0,0 +1,28 @@
+// -altComparableSemantics
+
+// Copyright 2022 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 p
+
+func f1[_ comparable]()              {}
+func f2[_ interface{ comparable }]() {}
+
+type T interface{ m() }
+
+func _[P comparable, Q ~int, R any]() {
+       _ = f1[int]
+       _ = f1[T /* T does implement comparable */]
+       _ = f1[any /* any does implement comparable */]
+       _ = f1[P]
+       _ = f1[Q]
+       _ = f1[R /* ERROR R does not implement comparable */]
+
+       _ = f2[int]
+       _ = f2[T /* T does implement comparable */]
+       _ = f2[any /* any does implement comparable */]
+       _ = f2[P]
+       _ = f2[Q]
+       _ = f2[R /* ERROR R does not implement comparable */]
+}