Skip to content

Commit

Permalink
go/types, types2: consider methods when unifying type parameters and …
Browse files Browse the repository at this point in the history
…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>
  • Loading branch information
griesemer authored and gopherbot committed Mar 1, 2023
1 parent 09852e7 commit b44f222
Show file tree
Hide file tree
Showing 5 changed files with 77 additions and 12 deletions.
2 changes: 1 addition & 1 deletion src/cmd/compile/internal/types2/check_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
25 changes: 20 additions & 5 deletions src/cmd/compile/internal/types2/infer.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -205,14 +205,18 @@ 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 {
if traceInference {
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:
Expand All @@ -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
}
}
}
}

Expand Down Expand Up @@ -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]
Expand Down
25 changes: 20 additions & 5 deletions src/go/types/infer.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

35 changes: 35 additions & 0 deletions src/internal/types/testdata/examples/inference.go
Original file line number Diff line number Diff line change
Expand Up @@ -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))
}
2 changes: 1 addition & 1 deletion src/internal/types/testdata/fixedbugs/issue51593.go
Original file line number Diff line number Diff line change
Expand Up @@ -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]
}

0 comments on commit b44f222

Please sign in to comment.