From b44f2222b5e3d9de0d214101bf458251ac30ffe3 Mon Sep 17 00:00:00 2001 From: Robert Griesemer Date: Tue, 28 Feb 2023 12:56:06 -0800 Subject: [PATCH] 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 Auto-Submit: Robert Griesemer Run-TryBot: Robert Griesemer Reviewed-by: Robert Griesemer Reviewed-by: Robert Findley --- src/cmd/compile/internal/types2/check_test.go | 2 +- src/cmd/compile/internal/types2/infer.go | 25 ++++++++++--- src/go/types/infer.go | 25 ++++++++++--- .../types/testdata/examples/inference.go | 35 +++++++++++++++++++ .../types/testdata/fixedbugs/issue51593.go | 2 +- 5 files changed, 77 insertions(+), 12 deletions(-) diff --git a/src/cmd/compile/internal/types2/check_test.go b/src/cmd/compile/internal/types2/check_test.go index 98ace528e9..26bb1aed9e 100644 --- a/src/cmd/compile/internal/types2/check_test.go +++ b/src/cmd/compile/internal/types2/check_test.go @@ -326,7 +326,7 @@ func TestCheck(t *testing.T) { } func TestSpec(t *testing.T) { testDirFiles(t, "../../../../internal/types/testdata/spec", 0, false) } 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 func TestFixedbugs(t *testing.T) { testDirFiles(t, "../../../../internal/types/testdata/fixedbugs", 100, false) diff --git a/src/cmd/compile/internal/types2/infer.go b/src/cmd/compile/internal/types2/infer.go index 2328671f10..49cf4601b8 100644 --- a/src/cmd/compile/internal/types2/infer.go +++ b/src/cmd/compile/internal/types2/infer.go @@ -186,12 +186,12 @@ func (check *Checker) infer(pos syntax.Pos, tparams []*TypeParam, targs []Type, u.tracef("type parameters: %s", tparams) } - // Repeatedly apply constraint type inference as long as - // progress is being made. + // Unify type parameters with their constraints as long + // as progress is being made. // // This is an O(n^2) algorithm where n is the number of // 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. // // 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() 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) // unify the type parameter with the core type. 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) } // A type parameter can be unified with its core type in two cases. - tx := u.at(tpar) switch { case tx != nil: // 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 { 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++ } } - // 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] { tpar := params.At(i).typ.(*TypeParam) arg := args[i] diff --git a/src/go/types/infer.go b/src/go/types/infer.go index 4143d2aabe..014036e206 100644 --- a/src/go/types/infer.go +++ b/src/go/types/infer.go @@ -188,12 +188,12 @@ func (check *Checker) infer(posn positioner, tparams []*TypeParam, targs []Type, u.tracef("type parameters: %s", tparams) } - // Repeatedly apply constraint type inference as long as - // progress is being made. + // Unify type parameters with their constraints as long + // as progress is being made. // // This is an O(n^2) algorithm where n is the number of // 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. // // 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() 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) // unify the type parameter with the core type. 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) } // A type parameter can be unified with its core type in two cases. - tx := u.at(tpar) switch { case tx != nil: // 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 { 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++ } } - // 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] { tpar := params.At(i).typ.(*TypeParam) arg := args[i] diff --git a/src/internal/types/testdata/examples/inference.go b/src/internal/types/testdata/examples/inference.go index 34aa5fcad8..2e88041df0 100644 --- a/src/internal/types/testdata/examples/inference.go +++ b/src/internal/types/testdata/examples/inference.go @@ -114,3 +114,38 @@ func _() { // List[Elem]. 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)) +} diff --git a/src/internal/types/testdata/fixedbugs/issue51593.go b/src/internal/types/testdata/fixedbugs/issue51593.go index f7b3a49988..62b0a5625a 100644 --- a/src/internal/types/testdata/fixedbugs/issue51593.go +++ b/src/internal/types/testdata/fixedbugs/issue51593.go @@ -9,5 +9,5 @@ func f[P interface{ m(R) }, R any]() {} type T = interface { m(int) } func _() { - _ = f /* ERROR "cannot infer R" */ [T] // don't crash in type inference + _ = f[T] }