mirror of https://github.com/golang/go.git
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 #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>
This commit is contained in:
parent
65f863529c
commit
5a3900b23c
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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 */]
|
||||
}
|
||||
Loading…
Reference in New Issue