Revert "go/types, types2: consider shared methods when unifying against interfaces"

This reverts commit c4afec232c.

Reason for revert: submitted accidentally via auto-commit

Change-Id: Idbfd90a4f1c2d582d86fc8aa45e037c406adbd40
Reviewed-on: https://go-review.googlesource.com/c/go/+/497655
Reviewed-by: Robert Findley <rfindley@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
Reviewed-by: Robert Griesemer <gri@google.com>
Run-TryBot: Robert Griesemer <gri@google.com>
This commit is contained in:
Robert Griesemer 2023-05-23 21:03:28 +00:00
parent c4afec232c
commit 527d0e8a91
11 changed files with 18 additions and 364 deletions

View File

@ -169,11 +169,6 @@ type Config struct {
// If DisableUnusedImportCheck is set, packages are not checked
// for unused imports.
DisableUnusedImportCheck bool
// If EnableInterfaceInference is set, type inference uses
// shared methods for improved type inference involving
// interfaces.
EnableInterfaceInference bool
}
func srcimporter_setUsesCgo(conf *Config) {

View File

@ -126,7 +126,6 @@ func testFiles(t *testing.T, filenames []string, srcs [][]byte, colDelta uint, m
flags := flag.NewFlagSet("", flag.PanicOnError)
flags.StringVar(&conf.GoVersion, "lang", "", "")
flags.BoolVar(&conf.FakeImportC, "fakeImportC", false, "")
flags.BoolVar(&conf.EnableInterfaceInference, "EnableInterfaceInference", false, "")
if err := parseFlags(srcs[0], flags); err != nil {
t.Fatal(err)
}

View File

@ -96,7 +96,7 @@ func (check *Checker) infer(pos syntax.Pos, tparams []*TypeParam, targs []Type,
// Unify parameter and argument types for generic parameters with typed arguments
// and collect the indices of generic parameters with untyped arguments.
// Terminology: generic parameter = function parameter with a type-parameterized type
u := check.newUnifier(tparams, targs)
u := newUnifier(tparams, targs)
errorf := func(kind string, tpar, targ Type, arg *operand) {
// provide a better error message if we can

View File

@ -67,7 +67,6 @@ const (
// corresponding types inferred for each type parameter.
// A unifier is created by calling newUnifier.
type unifier struct {
check *Checker
// handles maps each type parameter to its inferred type through
// an indirection *Type called (inferred type) "handle".
// Initially, each type parameter has its own, separate handle,
@ -85,7 +84,7 @@ type unifier struct {
// and corresponding type argument lists. The type argument list may be shorter
// than the type parameter list, and it may contain nil types. Matching type
// parameters and arguments must have the same index.
func (check *Checker) newUnifier(tparams []*TypeParam, targs []Type) *unifier {
func newUnifier(tparams []*TypeParam, targs []Type) *unifier {
assert(len(tparams) >= len(targs))
handles := make(map[*TypeParam]*Type, len(tparams))
// Allocate all handles up-front: in a correct program, all type parameters
@ -99,7 +98,7 @@ func (check *Checker) newUnifier(tparams []*TypeParam, targs []Type) *unifier {
}
handles[x] = &t
}
return &unifier{check, handles, 0}
return &unifier{handles, 0}
}
// unify attempts to unify x and y and reports whether it succeeded.
@ -281,9 +280,7 @@ func (u *unifier) nify(x, y Type, p *ifacePair) (result bool) {
// the same type structure are permitted as long as at least one of them
// is not a defined type. To accommodate for that possibility, we continue
// unification with the underlying type of a defined type if the other type
// is a type literal. However, if the type literal is an interface and we
// set EnableInterfaceInference, we continue with the defined type because
// otherwise we may lose its methods.
// is a type literal.
// We also continue if the other type is a basic type because basic types
// are valid underlying types and may appear as core types of type constraints.
// If we exclude them, inferred defined types for type parameters may not
@ -295,7 +292,7 @@ func (u *unifier) nify(x, y Type, p *ifacePair) (result bool) {
// we will fail at function instantiation or argument assignment time.
//
// If we have at least one defined type, there is one in y.
if ny, _ := y.(*Named); ny != nil && isTypeLit(x) && !(u.check.conf.EnableInterfaceInference && IsInterface(x)) {
if ny, _ := y.(*Named); ny != nil && isTypeLit(x) {
if traceInference {
u.tracef("%s ≡ under %s", x, ny)
}
@ -359,104 +356,6 @@ func (u *unifier) nify(x, y Type, p *ifacePair) (result bool) {
x, y = y, x
}
// If EnableInterfaceInference is set and both types are interfaces, one
// interface must have a subset of the methods of the other and corresponding
// method signatures must unify.
// If only one type is an interface, all its methods must be present in the
// other type and corresponding method signatures must unify.
if u.check.conf.EnableInterfaceInference {
xi, _ := x.(*Interface)
yi, _ := y.(*Interface)
// If we have two interfaces, check the type terms for equivalence,
// and unify common methods if possible.
if xi != nil && yi != nil {
xset := xi.typeSet()
yset := yi.typeSet()
if xset.comparable != yset.comparable {
return false
}
// For now we require terms to be equal.
// We should be able to relax this as well, eventually.
if !xset.terms.equal(yset.terms) {
return false
}
// Interface types are the only types where cycles can occur
// that are not "terminated" via named types; and such cycles
// can only be created via method parameter types that are
// anonymous interfaces (directly or indirectly) embedding
// the current interface. Example:
//
// type T interface {
// m() interface{T}
// }
//
// If two such (differently named) interfaces are compared,
// endless recursion occurs if the cycle is not detected.
//
// If x and y were compared before, they must be equal
// (if they were not, the recursion would have stopped);
// search the ifacePair stack for the same pair.
//
// This is a quadratic algorithm, but in practice these stacks
// are extremely short (bounded by the nesting depth of interface
// type declarations that recur via parameter types, an extremely
// rare occurrence). An alternative implementation might use a
// "visited" map, but that is probably less efficient overall.
q := &ifacePair{xi, yi, p}
for p != nil {
if p.identical(q) {
return true // same pair was compared before
}
p = p.prev
}
// The method set of x must be a subset of the method set
// of y or vice versa, and the common methods must unify.
xmethods := xset.methods
ymethods := yset.methods
// The smaller method set must be the subset, if it exists.
if len(xmethods) > len(ymethods) {
xmethods, ymethods = ymethods, xmethods
}
// len(xmethods) <= len(ymethods)
// Collect the ymethods in a map for quick lookup.
ymap := make(map[string]*Func, len(ymethods))
for _, ym := range ymethods {
ymap[ym.Id()] = ym
}
// All xmethods must exist in ymethods and corresponding signatures must unify.
for _, xm := range xmethods {
if ym := ymap[xm.Id()]; ym == nil || !u.nify(xm.typ, ym.typ, p) {
return false
}
}
return true
}
// We don't have two interfaces. If we have one, make sure it's in xi.
if yi != nil {
xi = yi
y = x
}
// If we have one interface, at a minimum each of the interface methods
// must be implemented and thus unify with a corresponding method from
// the non-interface type, otherwise unification fails.
if xi != nil {
// All xi methods must exist in y and corresponding signatures must unify.
xmethods := xi.typeSet().methods
for _, xm := range xmethods {
obj, _, _ := LookupFieldOrMethod(y, false, xm.pkg, xm.name)
if ym, _ := obj.(*Func); ym == nil || !u.nify(xm.typ, ym.typ, p) {
return false
}
}
return true
}
// Neither x nor y are interface types.
// They must be structurally equivalent to unify.
}
switch x := x.(type) {
case *Basic:
// Basic types are singletons except for the rune and byte
@ -537,8 +436,6 @@ func (u *unifier) nify(x, y Type, p *ifacePair) (result bool) {
}
case *Interface:
assert(!u.check.conf.EnableInterfaceInference) // handled before this switch
// Two interface types unify if they have the same set of methods with
// the same names, and corresponding function types unify.
// Lower-case method names from different packages are always different.
@ -611,49 +508,10 @@ func (u *unifier) nify(x, y Type, p *ifacePair) (result bool) {
}
case *Named:
// Two defined types unify if their type names originate
// Two named types unify if their type names originate
// in the same type declaration. If they are instantiated,
// their type argument lists must unify.
if y, ok := y.(*Named); ok {
sameOrig := indenticalOrigin(x, y)
if u.check.conf.EnableInterfaceInference {
xu := x.under()
yu := y.under()
xi, _ := xu.(*Interface)
yi, _ := yu.(*Interface)
// If one or both defined types are interfaces, use interface unification,
// unless they originated in the same type declaration.
if xi != nil && yi != nil {
// If both interfaces originate in the same declaration,
// their methods unify if the type parameters unify.
// Unify the type parameters rather than the methods in
// case the type parameters are not used in the methods
// (and to preserve existing behavior in this case).
if sameOrig {
xargs := x.TypeArgs().list()
yargs := y.TypeArgs().list()
assert(len(xargs) == len(yargs))
for i, xarg := range xargs {
if !u.nify(xarg, yargs[i], p) {
return false
}
}
return true
}
return u.nify(xu, yu, p)
}
// We don't have two interfaces. If we have one, make sure it's in xi.
if yi != nil {
xi = yi
y = x
}
// If xi is an interface, use interface unification.
if xi != nil {
return u.nify(xi, y, p)
}
// In all other cases, the type arguments and origins must match.
}
// Check type arguments before origins so they unify
// even if the origins don't match; for better error
// messages (see go.dev/issue/53692).
@ -667,7 +525,7 @@ func (u *unifier) nify(x, y Type, p *ifacePair) (result bool) {
return false
}
}
return sameOrig
return indenticalOrigin(x, y)
}
case *TypeParam:

View File

@ -170,11 +170,6 @@ type Config struct {
// If DisableUnusedImportCheck is set, packages are not checked
// for unused imports.
DisableUnusedImportCheck bool
// If _EnableInterfaceInference is set, type inference uses
// shared methods for improved type inference involving
// interfaces.
_EnableInterfaceInference bool
}
func srcimporter_setUsesCgo(conf *Config) {

View File

@ -137,7 +137,6 @@ func testFiles(t *testing.T, filenames []string, srcs [][]byte, manual bool, opt
flags := flag.NewFlagSet("", flag.PanicOnError)
flags.StringVar(&conf.GoVersion, "lang", "", "")
flags.BoolVar(&conf.FakeImportC, "fakeImportC", false, "")
flags.BoolVar(boolFieldAddr(&conf, "_EnableInterfaceInference"), "EnableInterfaceInference", false, "")
if err := parseFlags(srcs[0], flags); err != nil {
t.Fatal(err)
}

View File

@ -137,13 +137,10 @@ var filemap = map[string]action{
"typeterm_test.go": nil,
"typeterm.go": nil,
"under.go": nil,
"unify.go": func(f *ast.File) {
fixSprintf(f)
renameIdent(f, "EnableInterfaceInference", "_EnableInterfaceInference")
},
"universe.go": fixGlobalTypVarDecl,
"util_test.go": fixTokenPos,
"validtype.go": nil,
"unify.go": fixSprintf,
"universe.go": fixGlobalTypVarDecl,
"util_test.go": fixTokenPos,
"validtype.go": nil,
}
// TODO(gri) We should be able to make these rewriters more configurable/composable.

View File

@ -98,7 +98,7 @@ func (check *Checker) infer(posn positioner, tparams []*TypeParam, targs []Type,
// Unify parameter and argument types for generic parameters with typed arguments
// and collect the indices of generic parameters with untyped arguments.
// Terminology: generic parameter = function parameter with a type-parameterized type
u := check.newUnifier(tparams, targs)
u := newUnifier(tparams, targs)
errorf := func(kind string, tpar, targ Type, arg *operand) {
// provide a better error message if we can

View File

@ -69,7 +69,6 @@ const (
// corresponding types inferred for each type parameter.
// A unifier is created by calling newUnifier.
type unifier struct {
check *Checker
// handles maps each type parameter to its inferred type through
// an indirection *Type called (inferred type) "handle".
// Initially, each type parameter has its own, separate handle,
@ -87,7 +86,7 @@ type unifier struct {
// and corresponding type argument lists. The type argument list may be shorter
// than the type parameter list, and it may contain nil types. Matching type
// parameters and arguments must have the same index.
func (check *Checker) newUnifier(tparams []*TypeParam, targs []Type) *unifier {
func newUnifier(tparams []*TypeParam, targs []Type) *unifier {
assert(len(tparams) >= len(targs))
handles := make(map[*TypeParam]*Type, len(tparams))
// Allocate all handles up-front: in a correct program, all type parameters
@ -101,7 +100,7 @@ func (check *Checker) newUnifier(tparams []*TypeParam, targs []Type) *unifier {
}
handles[x] = &t
}
return &unifier{check, handles, 0}
return &unifier{handles, 0}
}
// unify attempts to unify x and y and reports whether it succeeded.
@ -283,9 +282,7 @@ func (u *unifier) nify(x, y Type, p *ifacePair) (result bool) {
// the same type structure are permitted as long as at least one of them
// is not a defined type. To accommodate for that possibility, we continue
// unification with the underlying type of a defined type if the other type
// is a type literal. However, if the type literal is an interface and we
// set EnableInterfaceInference, we continue with the defined type because
// otherwise we may lose its methods.
// is a type literal.
// We also continue if the other type is a basic type because basic types
// are valid underlying types and may appear as core types of type constraints.
// If we exclude them, inferred defined types for type parameters may not
@ -297,7 +294,7 @@ func (u *unifier) nify(x, y Type, p *ifacePair) (result bool) {
// we will fail at function instantiation or argument assignment time.
//
// If we have at least one defined type, there is one in y.
if ny, _ := y.(*Named); ny != nil && isTypeLit(x) && !(u.check.conf._EnableInterfaceInference && IsInterface(x)) {
if ny, _ := y.(*Named); ny != nil && isTypeLit(x) {
if traceInference {
u.tracef("%s ≡ under %s", x, ny)
}
@ -361,104 +358,6 @@ func (u *unifier) nify(x, y Type, p *ifacePair) (result bool) {
x, y = y, x
}
// If EnableInterfaceInference is set and both types are interfaces, one
// interface must have a subset of the methods of the other and corresponding
// method signatures must unify.
// If only one type is an interface, all its methods must be present in the
// other type and corresponding method signatures must unify.
if u.check.conf._EnableInterfaceInference {
xi, _ := x.(*Interface)
yi, _ := y.(*Interface)
// If we have two interfaces, check the type terms for equivalence,
// and unify common methods if possible.
if xi != nil && yi != nil {
xset := xi.typeSet()
yset := yi.typeSet()
if xset.comparable != yset.comparable {
return false
}
// For now we require terms to be equal.
// We should be able to relax this as well, eventually.
if !xset.terms.equal(yset.terms) {
return false
}
// Interface types are the only types where cycles can occur
// that are not "terminated" via named types; and such cycles
// can only be created via method parameter types that are
// anonymous interfaces (directly or indirectly) embedding
// the current interface. Example:
//
// type T interface {
// m() interface{T}
// }
//
// If two such (differently named) interfaces are compared,
// endless recursion occurs if the cycle is not detected.
//
// If x and y were compared before, they must be equal
// (if they were not, the recursion would have stopped);
// search the ifacePair stack for the same pair.
//
// This is a quadratic algorithm, but in practice these stacks
// are extremely short (bounded by the nesting depth of interface
// type declarations that recur via parameter types, an extremely
// rare occurrence). An alternative implementation might use a
// "visited" map, but that is probably less efficient overall.
q := &ifacePair{xi, yi, p}
for p != nil {
if p.identical(q) {
return true // same pair was compared before
}
p = p.prev
}
// The method set of x must be a subset of the method set
// of y or vice versa, and the common methods must unify.
xmethods := xset.methods
ymethods := yset.methods
// The smaller method set must be the subset, if it exists.
if len(xmethods) > len(ymethods) {
xmethods, ymethods = ymethods, xmethods
}
// len(xmethods) <= len(ymethods)
// Collect the ymethods in a map for quick lookup.
ymap := make(map[string]*Func, len(ymethods))
for _, ym := range ymethods {
ymap[ym.Id()] = ym
}
// All xmethods must exist in ymethods and corresponding signatures must unify.
for _, xm := range xmethods {
if ym := ymap[xm.Id()]; ym == nil || !u.nify(xm.typ, ym.typ, p) {
return false
}
}
return true
}
// We don't have two interfaces. If we have one, make sure it's in xi.
if yi != nil {
xi = yi
y = x
}
// If we have one interface, at a minimum each of the interface methods
// must be implemented and thus unify with a corresponding method from
// the non-interface type, otherwise unification fails.
if xi != nil {
// All xi methods must exist in y and corresponding signatures must unify.
xmethods := xi.typeSet().methods
for _, xm := range xmethods {
obj, _, _ := LookupFieldOrMethod(y, false, xm.pkg, xm.name)
if ym, _ := obj.(*Func); ym == nil || !u.nify(xm.typ, ym.typ, p) {
return false
}
}
return true
}
// Neither x nor y are interface types.
// They must be structurally equivalent to unify.
}
switch x := x.(type) {
case *Basic:
// Basic types are singletons except for the rune and byte
@ -539,8 +438,6 @@ func (u *unifier) nify(x, y Type, p *ifacePair) (result bool) {
}
case *Interface:
assert(!u.check.conf._EnableInterfaceInference) // handled before this switch
// Two interface types unify if they have the same set of methods with
// the same names, and corresponding function types unify.
// Lower-case method names from different packages are always different.
@ -613,49 +510,10 @@ func (u *unifier) nify(x, y Type, p *ifacePair) (result bool) {
}
case *Named:
// Two defined types unify if their type names originate
// Two named types unify if their type names originate
// in the same type declaration. If they are instantiated,
// their type argument lists must unify.
if y, ok := y.(*Named); ok {
sameOrig := indenticalOrigin(x, y)
if u.check.conf._EnableInterfaceInference {
xu := x.under()
yu := y.under()
xi, _ := xu.(*Interface)
yi, _ := yu.(*Interface)
// If one or both defined types are interfaces, use interface unification,
// unless they originated in the same type declaration.
if xi != nil && yi != nil {
// If both interfaces originate in the same declaration,
// their methods unify if the type parameters unify.
// Unify the type parameters rather than the methods in
// case the type parameters are not used in the methods
// (and to preserve existing behavior in this case).
if sameOrig {
xargs := x.TypeArgs().list()
yargs := y.TypeArgs().list()
assert(len(xargs) == len(yargs))
for i, xarg := range xargs {
if !u.nify(xarg, yargs[i], p) {
return false
}
}
return true
}
return u.nify(xu, yu, p)
}
// We don't have two interfaces. If we have one, make sure it's in xi.
if yi != nil {
xi = yi
y = x
}
// If xi is an interface, use interface unification.
if xi != nil {
return u.nify(xi, y, p)
}
// In all other cases, the type arguments and origins must match.
}
// Check type arguments before origins so they unify
// even if the origins don't match; for better error
// messages (see go.dev/issue/53692).
@ -669,7 +527,7 @@ func (u *unifier) nify(x, y Type, p *ifacePair) (result bool) {
return false
}
}
return sameOrig
return indenticalOrigin(x, y)
}
case *TypeParam:

View File

@ -1,23 +0,0 @@
// -EnableInterfaceInference
// Copyright 2023 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
type S struct{}
func (S) M() byte {
return 0
}
type I[T any] interface {
M() T
}
func f[T any](x I[T]) {}
func _() {
f(S{})
}

View File

@ -1,24 +0,0 @@
// -EnableInterfaceInference
// Copyright 2023 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
type I1[T any] interface {
m1(T)
}
type I2[T any] interface {
I1[T]
m2(T)
}
var V1 I1[int]
var V2 I2[int]
func g[T any](I1[T]) {}
func _() {
g(V1)
g(V2)
}