1
0
mirror of https://github.com/golang/go synced 2024-11-17 21:54:49 -07:00

go/types, types2: consider methods when unifying type parameters and constraints

An inferred type argument must implement its type parameter's constraint's
methods whether or not a core type exists. This allows us to infer type
parameters used in method signatures.

Fixes #51593.

Change-Id: I1fddb05a71d442641b4311d8e30a13ea9bdb4db5
Reviewed-on: https://go-review.googlesource.com/c/go/+/472298
TryBot-Result: Gopher Robot <gobot@golang.org>
Auto-Submit: Robert Griesemer <gri@google.com>
Run-TryBot: Robert Griesemer <gri@google.com>
Reviewed-by: Robert Griesemer <gri@google.com>
Reviewed-by: Robert Findley <rfindley@google.com>
This commit is contained in:
Robert Griesemer 2023-02-28 12:56:06 -08:00 committed by Gopher Robot
parent 09852e75ac
commit b44f2222b5
5 changed files with 77 additions and 12 deletions

View File

@ -326,7 +326,7 @@ func TestCheck(t *testing.T) {
} }
func TestSpec(t *testing.T) { testDirFiles(t, "../../../../internal/types/testdata/spec", 0, false) } func TestSpec(t *testing.T) { testDirFiles(t, "../../../../internal/types/testdata/spec", 0, false) }
func TestExamples(t *testing.T) { func TestExamples(t *testing.T) {
testDirFiles(t, "../../../../internal/types/testdata/examples", 60, false) testDirFiles(t, "../../../../internal/types/testdata/examples", 125, false)
} // TODO(gri) narrow column tolerance } // TODO(gri) narrow column tolerance
func TestFixedbugs(t *testing.T) { func TestFixedbugs(t *testing.T) {
testDirFiles(t, "../../../../internal/types/testdata/fixedbugs", 100, false) testDirFiles(t, "../../../../internal/types/testdata/fixedbugs", 100, false)

View File

@ -186,12 +186,12 @@ func (check *Checker) infer(pos syntax.Pos, tparams []*TypeParam, targs []Type,
u.tracef("type parameters: %s", tparams) u.tracef("type parameters: %s", tparams)
} }
// Repeatedly apply constraint type inference as long as // Unify type parameters with their constraints as long
// progress is being made. // as progress is being made.
// //
// This is an O(n^2) algorithm where n is the number of // This is an O(n^2) algorithm where n is the number of
// type parameters: if there is progress, at least one // type parameters: if there is progress, at least one
// type argument is inferred per iteration and we have // type argument is inferred per iteration, and we have
// a doubly nested loop. // a doubly nested loop.
// //
// In practice this is not a problem because the number // In practice this is not a problem because the number
@ -205,6 +205,11 @@ func (check *Checker) infer(pos syntax.Pos, tparams []*TypeParam, targs []Type,
nn := u.unknowns() nn := u.unknowns()
for _, tpar := range tparams { for _, tpar := range tparams {
tx := u.at(tpar)
if traceInference && tx != nil {
u.tracef("%s = %s", tpar, tx)
}
// If there is a core term (i.e., a core type with tilde information) // If there is a core term (i.e., a core type with tilde information)
// unify the type parameter with the core type. // unify the type parameter with the core type.
if core, single := coreTerm(tpar); core != nil { if core, single := coreTerm(tpar); core != nil {
@ -212,7 +217,6 @@ func (check *Checker) infer(pos syntax.Pos, tparams []*TypeParam, targs []Type,
u.tracef("core(%s) = %s (single = %v)", tpar, core, single) u.tracef("core(%s) = %s (single = %v)", tpar, core, single)
} }
// A type parameter can be unified with its core type in two cases. // A type parameter can be unified with its core type in two cases.
tx := u.at(tpar)
switch { switch {
case tx != nil: case tx != nil:
// The corresponding type argument tx is known. There are 2 cases: // The corresponding type argument tx is known. There are 2 cases:
@ -239,6 +243,17 @@ func (check *Checker) infer(pos syntax.Pos, tparams []*TypeParam, targs []Type,
if traceInference { if traceInference {
u.tracef("core(%s) = nil", tpar) u.tracef("core(%s) = nil", tpar)
} }
if tx != nil {
// We don't have a core type, but the type argument tx is known.
// It must have (at least) all the methods of the type constraint,
// and the method signatures must unify; otherwise tx cannot satisfy
// the constraint.
constraint := tpar.iface()
if m, wrong := check.missingMethod(tx, constraint, true, u.unify); m != nil {
check.errorf(pos, CannotInferTypeArgs, "%s does not satisfy %s %s", tx, constraint, check.missingMethodCause(tx, constraint, m, wrong))
return nil
}
}
} }
} }
@ -273,7 +288,7 @@ func (check *Checker) infer(pos syntax.Pos, tparams []*TypeParam, targs []Type,
j++ j++
} }
} }
// untyped[:j] are the undices of parameters without a type yet // untyped[:j] are the indices of parameters without a type yet
for _, i := range untyped[:j] { for _, i := range untyped[:j] {
tpar := params.At(i).typ.(*TypeParam) tpar := params.At(i).typ.(*TypeParam)
arg := args[i] arg := args[i]

View File

@ -188,12 +188,12 @@ func (check *Checker) infer(posn positioner, tparams []*TypeParam, targs []Type,
u.tracef("type parameters: %s", tparams) u.tracef("type parameters: %s", tparams)
} }
// Repeatedly apply constraint type inference as long as // Unify type parameters with their constraints as long
// progress is being made. // as progress is being made.
// //
// This is an O(n^2) algorithm where n is the number of // This is an O(n^2) algorithm where n is the number of
// type parameters: if there is progress, at least one // type parameters: if there is progress, at least one
// type argument is inferred per iteration and we have // type argument is inferred per iteration, and we have
// a doubly nested loop. // a doubly nested loop.
// //
// In practice this is not a problem because the number // In practice this is not a problem because the number
@ -207,6 +207,11 @@ func (check *Checker) infer(posn positioner, tparams []*TypeParam, targs []Type,
nn := u.unknowns() nn := u.unknowns()
for _, tpar := range tparams { for _, tpar := range tparams {
tx := u.at(tpar)
if traceInference && tx != nil {
u.tracef("%s = %s", tpar, tx)
}
// If there is a core term (i.e., a core type with tilde information) // If there is a core term (i.e., a core type with tilde information)
// unify the type parameter with the core type. // unify the type parameter with the core type.
if core, single := coreTerm(tpar); core != nil { if core, single := coreTerm(tpar); core != nil {
@ -214,7 +219,6 @@ func (check *Checker) infer(posn positioner, tparams []*TypeParam, targs []Type,
u.tracef("core(%s) = %s (single = %v)", tpar, core, single) u.tracef("core(%s) = %s (single = %v)", tpar, core, single)
} }
// A type parameter can be unified with its core type in two cases. // A type parameter can be unified with its core type in two cases.
tx := u.at(tpar)
switch { switch {
case tx != nil: case tx != nil:
// The corresponding type argument tx is known. There are 2 cases: // The corresponding type argument tx is known. There are 2 cases:
@ -241,6 +245,17 @@ func (check *Checker) infer(posn positioner, tparams []*TypeParam, targs []Type,
if traceInference { if traceInference {
u.tracef("core(%s) = nil", tpar) u.tracef("core(%s) = nil", tpar)
} }
if tx != nil {
// We don't have a core type, but the type argument tx is known.
// It must have (at least) all the methods of the type constraint,
// and the method signatures must unify; otherwise tx cannot satisfy
// the constraint.
constraint := tpar.iface()
if m, wrong := check.missingMethod(tx, constraint, true, u.unify); m != nil {
check.errorf(posn, CannotInferTypeArgs, "%s does not satisfy %s %s", tx, constraint, check.missingMethodCause(tx, constraint, m, wrong))
return nil
}
}
} }
} }
@ -275,7 +290,7 @@ func (check *Checker) infer(posn positioner, tparams []*TypeParam, targs []Type,
j++ j++
} }
} }
// untyped[:j] are the undices of parameters without a type yet // untyped[:j] are the indices of parameters without a type yet
for _, i := range untyped[:j] { for _, i := range untyped[:j] {
tpar := params.At(i).typ.(*TypeParam) tpar := params.At(i).typ.(*TypeParam)
arg := args[i] arg := args[i]

View File

@ -114,3 +114,38 @@ func _() {
// List[Elem]. // List[Elem].
related3 /* ERROR "cannot infer Slice" */ [int]() related3 /* ERROR "cannot infer Slice" */ [int]()
} }
func wantsMethods[P interface{ m1(Q); m2() R }, Q, R any](P) {}
type hasMethods1 struct{}
func (hasMethods1) m1(int)
func (hasMethods1) m2() string
type hasMethods2 struct{}
func (*hasMethods2) m1(int)
func (*hasMethods2) m2() string
type hasMethods3 interface{
m1(float64)
m2() complex128
}
type hasMethods4 interface{
m1()
}
func _() {
// wantsMethod can be called with arguments that have the relevant methods
// and wantsMethod's type arguments are inferred from those types' method
// signatures.
wantsMethods(hasMethods1{})
wantsMethods(&hasMethods1{})
// TODO(gri) improve error message (the cause is ptr vs non-pointer receiver)
wantsMethods /* ERROR "hasMethods2 does not satisfy interface{m1(Q); m2() R} (wrong type for method m1)" */ (hasMethods2{})
wantsMethods(&hasMethods2{})
wantsMethods(hasMethods3(nil))
wantsMethods /* ERROR "any does not satisfy interface{m1(Q); m2() R} (missing method m1)" */ (any(nil))
wantsMethods /* ERROR "hasMethods4 does not satisfy interface{m1(Q); m2() R} (wrong type for method m1)" */ (hasMethods4(nil))
}

View File

@ -9,5 +9,5 @@ func f[P interface{ m(R) }, R any]() {}
type T = interface { m(int) } type T = interface { m(int) }
func _() { func _() {
_ = f /* ERROR "cannot infer R" */ [T] // don't crash in type inference _ = f[T]
} }