Skip to content

Commit 8c2f7cd

Browse files
griesemerromaindoumenc
authored andcommitted
go/types, types2: implement alternative comparable semantics
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 golang#52509. Change-Id: Ib491b086feb5563920eaddefcebdacb2c5b72d61 Reviewed-on: https://go-review.googlesource.com/c/go/+/444635 Reviewed-by: Robert Findley <[email protected]> Run-TryBot: Robert Griesemer <[email protected]> TryBot-Result: Gopher Robot <[email protected]> Reviewed-by: David Chase <[email protected]>
1 parent 27cde35 commit 8c2f7cd

File tree

11 files changed

+70
-16
lines changed

11 files changed

+70
-16
lines changed

src/cmd/compile/internal/types2/api.go

+5-1
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,10 @@ type Config struct {
167167
// If DisableUnusedImportCheck is set, packages are not checked
168168
// for unused imports.
169169
DisableUnusedImportCheck bool
170+
171+
// If AltComparableSemantics is set, ordinary (non-type parameter)
172+
// interfaces satisfy the comparable constraint.
173+
AltComparableSemantics bool
170174
}
171175

172176
func srcimporter_setUsesCgo(conf *Config) {
@@ -480,7 +484,7 @@ func Implements(V Type, T *Interface) bool {
480484
if V.Underlying() == Typ[Invalid] {
481485
return false
482486
}
483-
return (*Checker)(nil).implements(V, T, nil)
487+
return (*Checker)(nil).implements(V, T, false, nil)
484488
}
485489

486490
// Identical reports whether x and y are identical types.

src/cmd/compile/internal/types2/check_test.go

+1
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,7 @@ func testFiles(t *testing.T, filenames []string, colDelta uint, manual bool) {
130130
flags := flag.NewFlagSet("", flag.PanicOnError)
131131
flags.StringVar(&conf.GoVersion, "lang", "", "")
132132
flags.BoolVar(&conf.FakeImportC, "fakeImportC", false, "")
133+
flags.BoolVar(&conf.AltComparableSemantics, "altComparableSemantics", false, "")
133134
if err := parseFlags(filenames[0], nil, flags); err != nil {
134135
t.Fatal(err)
135136
}

src/cmd/compile/internal/types2/instantiate.go

+9-4
Original file line numberDiff line numberDiff line change
@@ -176,19 +176,20 @@ func (check *Checker) verify(pos syntax.Pos, tparams []*TypeParam, targs []Type,
176176
// the parameterized type.
177177
bound := check.subst(pos, tpar.bound, smap, nil, ctxt)
178178
var cause string
179-
if !check.implements(targs[i], bound, &cause) {
179+
if !check.implements(targs[i], bound, true, &cause) {
180180
return i, errors.New(cause)
181181
}
182182
}
183183
return -1, nil
184184
}
185185

186186
// implements checks if V implements T. The receiver may be nil if implements
187-
// is called through an exported API call such as AssignableTo.
187+
// is called through an exported API call such as AssignableTo. If constraint
188+
// is set, T is a type constraint.
188189
//
189190
// If the provided cause is non-nil, it may be set to an error string
190191
// explaining why V does not implement T.
191-
func (check *Checker) implements(V, T Type, cause *string) bool {
192+
func (check *Checker) implements(V, T Type, constraint bool, cause *string) bool {
192193
Vu := under(V)
193194
Tu := under(T)
194195
if Vu == Typ[Invalid] || Tu == Typ[Invalid] {
@@ -245,7 +246,11 @@ func (check *Checker) implements(V, T Type, cause *string) bool {
245246
// Only check comparability if we don't have a more specific error.
246247
checkComparability := func() bool {
247248
// If T is comparable, V must be comparable.
248-
if Ti.IsComparable() && !comparable(V, false, nil, nil) {
249+
// For constraint satisfaction, use dynamic comparability for the
250+
// alternative comparable semantics such that ordinary, non-type
251+
// parameter interfaces implement comparable.
252+
dynamic := constraint && check != nil && check.conf.AltComparableSemantics
253+
if Ti.IsComparable() && !comparable(V, dynamic, nil, nil) {
249254
if cause != nil {
250255
*cause = check.sprintf("%s does not implement comparable", V)
251256
}

src/cmd/compile/internal/types2/lookup.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -472,7 +472,7 @@ func (check *Checker) newAssertableTo(V *Interface, T Type) bool {
472472
if IsInterface(T) {
473473
return true
474474
}
475-
return check.implements(T, V, nil)
475+
return check.implements(T, V, false, nil)
476476
}
477477

478478
// deref dereferences typ if it is a *Pointer and returns its base and true.

src/cmd/compile/internal/types2/operand.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -289,15 +289,15 @@ func (x *operand) assignableTo(check *Checker, T Type, cause *string) (bool, Cod
289289
// T is an interface type and x implements T and T is not a type parameter.
290290
// Also handle the case where T is a pointer to an interface.
291291
if _, ok := Tu.(*Interface); ok && Tp == nil || isInterfacePtr(Tu) {
292-
if !check.implements(V, T, cause) {
292+
if !check.implements(V, T, false, cause) {
293293
return false, InvalidIfaceAssign
294294
}
295295
return true, 0
296296
}
297297

298298
// If V is an interface, check if a missing type assertion is the problem.
299299
if Vi, _ := Vu.(*Interface); Vi != nil && Vp == nil {
300-
if check.implements(T, V, nil) {
300+
if check.implements(T, V, false, nil) {
301301
// T implements V, so give hint about type assertion.
302302
if cause != nil {
303303
*cause = "need type assertion"

src/go/types/api.go

+5-1
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,10 @@ type Config struct {
167167
// If DisableUnusedImportCheck is set, packages are not checked
168168
// for unused imports.
169169
DisableUnusedImportCheck bool
170+
171+
// If altComparableSemantics is set, ordinary (non-type parameter)
172+
// interfaces satisfy the comparable constraint.
173+
altComparableSemantics bool
170174
}
171175

172176
func srcimporter_setUsesCgo(conf *Config) {
@@ -463,7 +467,7 @@ func Implements(V Type, T *Interface) bool {
463467
if V.Underlying() == Typ[Invalid] {
464468
return false
465469
}
466-
return (*Checker)(nil).implements(V, T, nil)
470+
return (*Checker)(nil).implements(V, T, false, nil)
467471
}
468472

469473
// Identical reports whether x and y are identical types.

src/go/types/check_test.go

+7
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,7 @@ func testFiles(t *testing.T, sizes Sizes, filenames []string, srcs [][]byte, man
217217
flags := flag.NewFlagSet("", flag.PanicOnError)
218218
flags.StringVar(&conf.GoVersion, "lang", "", "")
219219
flags.BoolVar(&conf.FakeImportC, "fakeImportC", false, "")
220+
flags.BoolVar(addrAltComparableSemantics(&conf), "altComparableSemantics", false, "")
220221
if err := parseFlags(filenames[0], srcs[0], flags); err != nil {
221222
t.Fatal(err)
222223
}
@@ -293,6 +294,12 @@ func readCode(err Error) int {
293294
return int(v.FieldByName("go116code").Int())
294295
}
295296

297+
// addrAltComparableSemantics(conf) returns &conf.altComparableSemantics (unexported field).
298+
func addrAltComparableSemantics(conf *Config) *bool {
299+
v := reflect.Indirect(reflect.ValueOf(conf))
300+
return (*bool)(v.FieldByName("altComparableSemantics").Addr().UnsafePointer())
301+
}
302+
296303
// TestManual is for manual testing of a package - either provided
297304
// as a list of filenames belonging to the package, or a directory
298305
// name containing the package files - after the test arguments

src/go/types/instantiate.go

+9-4
Original file line numberDiff line numberDiff line change
@@ -176,19 +176,20 @@ func (check *Checker) verify(pos token.Pos, tparams []*TypeParam, targs []Type,
176176
// the parameterized type.
177177
bound := check.subst(pos, tpar.bound, smap, nil, ctxt)
178178
var cause string
179-
if !check.implements(targs[i], bound, &cause) {
179+
if !check.implements(targs[i], bound, true, &cause) {
180180
return i, errors.New(cause)
181181
}
182182
}
183183
return -1, nil
184184
}
185185

186186
// implements checks if V implements T. The receiver may be nil if implements
187-
// is called through an exported API call such as AssignableTo.
187+
// is called through an exported API call such as AssignableTo. If constraint
188+
// is set, T is a type constraint.
188189
//
189190
// If the provided cause is non-nil, it may be set to an error string
190191
// explaining why V does not implement T.
191-
func (check *Checker) implements(V, T Type, cause *string) bool {
192+
func (check *Checker) implements(V, T Type, constraint bool, cause *string) bool {
192193
Vu := under(V)
193194
Tu := under(T)
194195
if Vu == Typ[Invalid] || Tu == Typ[Invalid] {
@@ -245,7 +246,11 @@ func (check *Checker) implements(V, T Type, cause *string) bool {
245246
// Only check comparability if we don't have a more specific error.
246247
checkComparability := func() bool {
247248
// If T is comparable, V must be comparable.
248-
if Ti.IsComparable() && !comparable(V, false, nil, nil) {
249+
// For constraint satisfaction, use dynamic comparability for the
250+
// alternative comparable semantics such that ordinary, non-type
251+
// parameter interfaces implement comparable.
252+
dynamic := constraint && check != nil && check.conf.altComparableSemantics
253+
if Ti.IsComparable() && !comparable(V, dynamic, nil, nil) {
249254
if cause != nil {
250255
*cause = check.sprintf("%s does not implement comparable", V)
251256
}

src/go/types/lookup.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -471,7 +471,7 @@ func (check *Checker) newAssertableTo(V *Interface, T Type) bool {
471471
if IsInterface(T) {
472472
return true
473473
}
474-
return check.implements(T, V, nil)
474+
return check.implements(T, V, false, nil)
475475
}
476476

477477
// deref dereferences typ if it is a *Pointer and returns its base and true.

src/go/types/operand.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -278,15 +278,15 @@ func (x *operand) assignableTo(check *Checker, T Type, cause *string) (bool, Cod
278278
// T is an interface type and x implements T and T is not a type parameter.
279279
// Also handle the case where T is a pointer to an interface.
280280
if _, ok := Tu.(*Interface); ok && Tp == nil || isInterfacePtr(Tu) {
281-
if !check.implements(V, T, cause) {
281+
if !check.implements(V, T, false, cause) {
282282
return false, InvalidIfaceAssign
283283
}
284284
return true, 0
285285
}
286286

287287
// If V is an interface, check if a missing type assertion is the problem.
288288
if Vi, _ := Vu.(*Interface); Vi != nil && Vp == nil {
289-
if check.implements(T, V, nil) {
289+
if check.implements(T, V, false, nil) {
290290
// T implements V, so give hint about type assertion.
291291
if cause != nil {
292292
*cause = "need type assertion"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
// -altComparableSemantics
2+
3+
// Copyright 2022 The Go Authors. All rights reserved.
4+
// Use of this source code is governed by a BSD-style
5+
// license that can be found in the LICENSE file.
6+
7+
package p
8+
9+
func f1[_ comparable]() {}
10+
func f2[_ interface{ comparable }]() {}
11+
12+
type T interface{ m() }
13+
14+
func _[P comparable, Q ~int, R any]() {
15+
_ = f1[int]
16+
_ = f1[T /* T does implement comparable */]
17+
_ = f1[any /* any does implement comparable */]
18+
_ = f1[P]
19+
_ = f1[Q]
20+
_ = f1[R /* ERROR R does not implement comparable */]
21+
22+
_ = f2[int]
23+
_ = f2[T /* T does implement comparable */]
24+
_ = f2[any /* any does implement comparable */]
25+
_ = f2[P]
26+
_ = f2[Q]
27+
_ = f2[R /* ERROR R does not implement comparable */]
28+
}

0 commit comments

Comments
 (0)