go/types, types2: make the new comparable semantics the default

Ordinary interface types now satisfy comparable constraints. This
is a fully backward-compatible change: it simply permits additional
code to be valid that wasn't valid before.

This change makes the new comparable semantics the default behavior,
depending on the Go -lang version.

It also renames the flag types2.Config.AltComparableSemantics to
types2.Config.OldComparableSemantics and inverts its meaning
(or types.Config.oldComparableSemantics respectively).

Add new predicate Satisfies (matching the predicate Implements but
for constraint satisfaction), per the proposal description.

Adjust some existing tests by setting -oldComparableSemantics
and add some new tests that verify version-dependent behavior.

The compiler flag -oldcomparable may be used to temporarily
switch back to the Go 1.18/1.19 behavior should this change
cause problems, or to identify that a problem is unrelated
to this change. The flag will be removed for Go 1.21.

For #52509.
For #56548.
For #57011.

Change-Id: I8b3b3d9d492fc24b0693567055f0053ccb5aeb42
Reviewed-on: https://go-review.googlesource.com/c/go/+/454575
TryBot-Result: Gopher Robot <gobot@golang.org>
Reviewed-by: Robert Griesemer <gri@google.com>
Run-TryBot: Robert Griesemer <gri@google.com>
Reviewed-by: Robert Findley <rfindley@google.com>
This commit is contained in:
Robert Griesemer 2022-12-01 09:39:45 -08:00 committed by Robert Griesemer
parent af1a5d9287
commit 8fd2875c3e
15 changed files with 149 additions and 28 deletions

1
api/next/56548.txt Normal file
View File

@ -0,0 +1 @@
pkg go/types, func Satisfies(Type, *Interface) bool #56548

View File

@ -122,8 +122,8 @@ type CmdFlags struct {
SymABIs string "help:\"read symbol ABIs from `file`\"" SymABIs string "help:\"read symbol ABIs from `file`\""
TraceProfile string "help:\"write an execution trace to `file`\"" TraceProfile string "help:\"write an execution trace to `file`\""
TrimPath string "help:\"remove `prefix` from recorded source file paths\"" TrimPath string "help:\"remove `prefix` from recorded source file paths\""
WB bool "help:\"enable write barrier\"" // TODO: remove WB bool "help:\"enable write barrier\"" // TODO: remove
AltComparable bool "help:\"enable alternative comparable semantics\"" // experiment - remove eventually OldComparable bool "help:\"enable old comparable semantics\"" // TODO: remove for Go 1.21
PgoProfile string "help:\"read profile from `file`\"" PgoProfile string "help:\"read profile from `file`\""
// Configuration derived from flags; not a flag itself. // Configuration derived from flags; not a flag itself.

View File

@ -57,7 +57,7 @@ func checkFiles(noders []*noder) (posMap, *types2.Package, *types2.Info) {
}, },
Importer: &importer, Importer: &importer,
Sizes: &gcSizes{}, Sizes: &gcSizes{},
AltComparableSemantics: base.Flag.AltComparable, // experiment - remove eventually OldComparableSemantics: base.Flag.OldComparable, // default is new comparable semantics
} }
info := &types2.Info{ info := &types2.Info{
StoreTypesInSyntax: true, StoreTypesInSyntax: true,

View File

@ -168,9 +168,10 @@ type Config struct {
// for unused imports. // for unused imports.
DisableUnusedImportCheck bool DisableUnusedImportCheck bool
// If AltComparableSemantics is set, ordinary (non-type parameter) // If OldComparableSemantics is set, ordinary (non-type parameter)
// interfaces satisfy the comparable constraint. // interfaces do not satisfy the comparable constraint.
AltComparableSemantics bool // TODO(gri) remove this flag for Go 1.21
OldComparableSemantics bool
} }
func srcimporter_setUsesCgo(conf *Config) { func srcimporter_setUsesCgo(conf *Config) {
@ -487,6 +488,14 @@ func Implements(V Type, T *Interface) bool {
return (*Checker)(nil).implements(V, T, false, nil) return (*Checker)(nil).implements(V, T, false, nil)
} }
// Satisfies reports whether type V satisfies the constraint T.
//
// The behavior of Satisfies is unspecified if V is Typ[Invalid] or an uninstantiated
// generic type.
func Satisfies(V Type, T *Interface) bool {
return (*Checker)(nil).implements(V, T, true, nil)
}
// Identical reports whether x and y are identical types. // Identical reports whether x and y are identical types.
// Receivers of Signature types are ignored. // Receivers of Signature types are ignored.
func Identical(x, y Type) bool { func Identical(x, y Type) bool {

View File

@ -130,7 +130,7 @@ func testFiles(t *testing.T, filenames []string, colDelta uint, manual bool) {
flags := flag.NewFlagSet("", flag.PanicOnError) flags := flag.NewFlagSet("", flag.PanicOnError)
flags.StringVar(&conf.GoVersion, "lang", "", "") flags.StringVar(&conf.GoVersion, "lang", "", "")
flags.BoolVar(&conf.FakeImportC, "fakeImportC", false, "") flags.BoolVar(&conf.FakeImportC, "fakeImportC", false, "")
flags.BoolVar(&conf.AltComparableSemantics, "altComparableSemantics", false, "") flags.BoolVar(&conf.OldComparableSemantics, "oldComparableSemantics", false, "")
if err := parseFlags(filenames[0], nil, flags); err != nil { if err := parseFlags(filenames[0], nil, flags); err != nil {
t.Fatal(err) t.Fatal(err)
} }

View File

@ -245,18 +245,39 @@ func (check *Checker) implements(V, T Type, constraint bool, cause *string) bool
// Only check comparability if we don't have a more specific error. // Only check comparability if we don't have a more specific error.
checkComparability := func() bool { checkComparability := func() bool {
if !Ti.IsComparable() {
return true
}
// If T is comparable, V must be comparable. // If T is comparable, V must be comparable.
// For constraint satisfaction, use dynamic comparability for the // If V is strictly comparable, we're done.
// alternative comparable semantics such that ordinary, non-type if comparable(V, false /* strict comparability */, nil, nil) {
// parameter interfaces implement comparable. return true
dynamic := constraint && check != nil && check.conf.AltComparableSemantics }
if Ti.IsComparable() && !comparable(V, dynamic, nil, nil) { // If check.conf.OldComparableSemantics is set (by the compiler or
// a test), we only consider strict comparability and we're done.
// TODO(gri) remove this check for Go 1.21
if check != nil && check.conf.OldComparableSemantics {
if cause != nil { if cause != nil {
*cause = check.sprintf("%s does not implement comparable", V) *cause = check.sprintf("%s does not implement comparable", V)
} }
return false return false
} }
return true // For constraint satisfaction, use dynamic (spec) comparability
// so that ordinary, non-type parameter interfaces implement comparable.
if constraint && comparable(V, true /* spec comparability */, nil, nil) {
// V is comparable if we are at Go 1.20 or higher.
if check == nil || check.allowVersion(check.pkg, 1, 20) {
return true
}
if cause != nil {
*cause = check.sprintf("%s to implement comparable requires go1.20 or later", V)
}
return false
}
if cause != nil {
*cause = check.sprintf("%s does not implement comparable", V)
}
return false
} }
// V must also be in the set of types of T, if any. // V must also be in the set of types of T, if any.

View File

@ -168,9 +168,10 @@ type Config struct {
// for unused imports. // for unused imports.
DisableUnusedImportCheck bool DisableUnusedImportCheck bool
// If altComparableSemantics is set, ordinary (non-type parameter) // If oldComparableSemantics is set, ordinary (non-type parameter)
// interfaces satisfy the comparable constraint. // interfaces do not satisfy the comparable constraint.
altComparableSemantics bool // TODO(gri) remove this flag for Go 1.21
oldComparableSemantics bool
} }
func srcimporter_setUsesCgo(conf *Config) { func srcimporter_setUsesCgo(conf *Config) {
@ -470,6 +471,14 @@ func Implements(V Type, T *Interface) bool {
return (*Checker)(nil).implements(V, T, false, nil) return (*Checker)(nil).implements(V, T, false, nil)
} }
// Satisfies reports whether type V satisfies the constraint T.
//
// The behavior of Satisfies is unspecified if V is Typ[Invalid] or an uninstantiated
// generic type.
func Satisfies(V Type, T *Interface) bool {
return (*Checker)(nil).implements(V, T, true, nil)
}
// Identical reports whether x and y are identical types. // Identical reports whether x and y are identical types.
// Receivers of Signature types are ignored. // Receivers of Signature types are ignored.
func Identical(x, y Type) bool { func Identical(x, y Type) bool {

View File

@ -217,7 +217,7 @@ func testFiles(t *testing.T, sizes Sizes, filenames []string, srcs [][]byte, man
flags := flag.NewFlagSet("", flag.PanicOnError) flags := flag.NewFlagSet("", flag.PanicOnError)
flags.StringVar(&conf.GoVersion, "lang", "", "") flags.StringVar(&conf.GoVersion, "lang", "", "")
flags.BoolVar(&conf.FakeImportC, "fakeImportC", false, "") flags.BoolVar(&conf.FakeImportC, "fakeImportC", false, "")
flags.BoolVar(addrAltComparableSemantics(&conf), "altComparableSemantics", false, "") flags.BoolVar(addrOldComparableSemantics(&conf), "oldComparableSemantics", false, "")
if err := parseFlags(filenames[0], srcs[0], flags); err != nil { if err := parseFlags(filenames[0], srcs[0], flags); err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -294,10 +294,10 @@ func readCode(err Error) int {
return int(v.FieldByName("go116code").Int()) return int(v.FieldByName("go116code").Int())
} }
// addrAltComparableSemantics(conf) returns &conf.altComparableSemantics (unexported field). // addrOldComparableSemantics(conf) returns &conf.oldComparableSemantics (unexported field).
func addrAltComparableSemantics(conf *Config) *bool { func addrOldComparableSemantics(conf *Config) *bool {
v := reflect.Indirect(reflect.ValueOf(conf)) v := reflect.Indirect(reflect.ValueOf(conf))
return (*bool)(v.FieldByName("altComparableSemantics").Addr().UnsafePointer()) return (*bool)(v.FieldByName("oldComparableSemantics").Addr().UnsafePointer())
} }
// TestManual is for manual testing of a package - either provided // TestManual is for manual testing of a package - either provided

View File

@ -245,18 +245,39 @@ func (check *Checker) implements(V, T Type, constraint bool, cause *string) bool
// Only check comparability if we don't have a more specific error. // Only check comparability if we don't have a more specific error.
checkComparability := func() bool { checkComparability := func() bool {
if !Ti.IsComparable() {
return true
}
// If T is comparable, V must be comparable. // If T is comparable, V must be comparable.
// For constraint satisfaction, use dynamic comparability for the // If V is strictly comparable, we're done.
// alternative comparable semantics such that ordinary, non-type if comparable(V, false /* strict comparability */, nil, nil) {
// parameter interfaces implement comparable. return true
dynamic := constraint && check != nil && check.conf.altComparableSemantics }
if Ti.IsComparable() && !comparable(V, dynamic, nil, nil) { // If check.conf.OldComparableSemantics is set (by the compiler or
// a test), we only consider strict comparability and we're done.
// TODO(gri) remove this check for Go 1.21
if check != nil && check.conf.oldComparableSemantics {
if cause != nil { if cause != nil {
*cause = check.sprintf("%s does not implement comparable", V) *cause = check.sprintf("%s does not implement comparable", V)
} }
return false return false
} }
return true // For constraint satisfaction, use dynamic (spec) comparability
// so that ordinary, non-type parameter interfaces implement comparable.
if constraint && comparable(V, true /* spec comparability */, nil, nil) {
// V is comparable if we are at Go 1.20 or higher.
if check == nil || check.allowVersion(check.pkg, 1, 20) {
return true
}
if cause != nil {
*cause = check.sprintf("%s to implement comparable requires go1.20 or later", V)
}
return false
}
if cause != nil {
*cause = check.sprintf("%s does not implement comparable", V)
}
return false
} }
// V must also be in the set of types of T, if any. // V must also be in the set of types of T, if any.

View File

@ -1,3 +1,5 @@
// -oldComparableSemantics
// Copyright 2020 The Go Authors. All rights reserved. // Copyright 2020 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.

View File

@ -1,3 +1,5 @@
// -oldComparableSemantics
// Copyright 2022 The Go Authors. All rights reserved. // Copyright 2022 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.

View File

@ -1,3 +1,5 @@
// -oldComparableSemantics
// Copyright 2022 The Go Authors. All rights reserved. // Copyright 2022 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.

View File

@ -1,5 +1,3 @@
// -altComparableSemantics
// Copyright 2022 The Go Authors. All rights reserved. // Copyright 2022 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.

View File

@ -0,0 +1,28 @@
// -lang=go1.19
// 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 /* ERROR T to implement comparable requires go1\.20 or later */]
_ = f1[any /* ERROR any to implement comparable requires go1\.20 or later */]
_ = f1[P]
_ = f1[Q]
_ = f1[R /* ERROR R does not implement comparable */]
_ = f2[int]
_ = f2[T /* ERROR T to implement comparable requires go1\.20 or later */]
_ = f2[any /* ERROR any to implement comparable requires go1\.20 or later */]
_ = f2[P]
_ = f2[Q]
_ = f2[R /* ERROR R does not implement comparable */]
}

View File

@ -0,0 +1,28 @@
// -oldComparableSemantics
// 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 /* ERROR T does not implement comparable */]
_ = f1[any /* ERROR any does not implement comparable */]
_ = f1[P]
_ = f1[Q]
_ = f1[R /* ERROR R does not implement comparable */]
_ = f2[int]
_ = f2[T /* ERROR T does not implement comparable */]
_ = f2[any /* ERROR any does not implement comparable */]
_ = f2[P]
_ = f2[Q]
_ = f2[R /* ERROR R does not implement comparable */]
}