diff --git a/src/cmd/compile/internal/types2/infer.go b/src/cmd/compile/internal/types2/infer.go index 025b64d9fc..a57d25b263 100644 --- a/src/cmd/compile/internal/types2/infer.go +++ b/src/cmd/compile/internal/types2/infer.go @@ -236,10 +236,10 @@ func (check *Checker) infer(pos syntax.Pos, tparams []*TypeParam, targs []Type, u.tracef("-- type parameter %s = %s: core(%s) = %s, single = %v", tpar, tx, tpar, core, single) } - // If there is a core term (i.e., a core type with tilde information) - // unify the type parameter with the core type. + // If the type parameter's constraint has a core term (i.e., a core type with tilde information) + // try to unify the type parameter with that core type. if core != nil { - // A type parameter can be unified with its core type in two cases. + // A type parameter can be unified with its constraint's core type in two cases. switch { case tx != nil: if traceInference { @@ -266,33 +266,42 @@ func (check *Checker) infer(pos syntax.Pos, tparams []*TypeParam, targs []Type, if traceInference { u.tracef("-> set type parameter %s to constraint core type %s", tpar, core.typ) } - // The corresponding type argument tx is unknown and there's a single - // specific type and no tilde. + // The corresponding type argument tx is unknown and the core term + // describes a single specific type and no tilde. // In this case the type argument must be that single type; set it. u.set(tpar, core.typ) } - } else { - if tx != nil { - if traceInference { - u.tracef("-> unify type parameter %s (type %s) methods with constraint methods", tpar, tx) - } - // 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. - // TODO(gri) Now that unification handles interfaces, this code can - // be reduced to calling u.unify(tx, tpar.iface(), assign) - // (which will compare signatures exactly as we do below). - // We leave it as is for now because missingMethod provides - // a failure cause which allows for a better error message. - // Eventually, unify should return an error with cause. - var cause string - constraint := tpar.iface() - if m, _ := check.missingMethod(tx, constraint, true, func(x, y Type) bool { return u.unify(x, y, exact) }, &cause); m != nil { - // TODO(gri) better error message (see TODO above) - err.addf(pos, "%s (type %s) does not satisfy %s %s", tpar, tx, tpar.Constraint(), cause) - return nil - } + } + + // Independent of whether there is a core term, if the type argument tx is known + // it must implement the methods of the type constraint, possibly after unification + // of the relevant method signatures, otherwise tx cannot satisfy the constraint. + // This unification step may provide additional type arguments. + // + // Note: The type argument tx may be known but contain references to other type + // parameters (i.e., tx may still be parameterized). + // In this case the methods of tx don't correctly reflect the final method set + // and we may get a missing method error below. Skip this step in this case. + // + // TODO(gri) We should be able continue even with a parameterized tx if we add + // a simplify step beforehand (see below). This will require factoring out the + // simplify phase so we can call it from here. + if tx != nil && !isParameterized(tparams, tx) { + if traceInference { + u.tracef("-> unify type parameter %s (type %s) methods with constraint methods", tpar, tx) + } + // TODO(gri) Now that unification handles interfaces, this code can + // be reduced to calling u.unify(tx, tpar.iface(), assign) + // (which will compare signatures exactly as we do below). + // We leave it as is for now because missingMethod provides + // a failure cause which allows for a better error message. + // Eventually, unify should return an error with cause. + var cause string + constraint := tpar.iface() + if m, _ := check.missingMethod(tx, constraint, true, func(x, y Type) bool { return u.unify(x, y, exact) }, &cause); m != nil { + // TODO(gri) better error message (see TODO above) + err.addf(pos, "%s (type %s) does not satisfy %s %s", tpar, tx, tpar.Constraint(), cause) + return nil } } } diff --git a/src/go/types/infer.go b/src/go/types/infer.go index b0094a6991..3bdbd79870 100644 --- a/src/go/types/infer.go +++ b/src/go/types/infer.go @@ -239,10 +239,10 @@ func (check *Checker) infer(posn positioner, tparams []*TypeParam, targs []Type, u.tracef("-- type parameter %s = %s: core(%s) = %s, single = %v", tpar, tx, tpar, core, single) } - // If there is a core term (i.e., a core type with tilde information) - // unify the type parameter with the core type. + // If the type parameter's constraint has a core term (i.e., a core type with tilde information) + // try to unify the type parameter with that core type. if core != nil { - // A type parameter can be unified with its core type in two cases. + // A type parameter can be unified with its constraint's core type in two cases. switch { case tx != nil: if traceInference { @@ -269,33 +269,42 @@ func (check *Checker) infer(posn positioner, tparams []*TypeParam, targs []Type, if traceInference { u.tracef("-> set type parameter %s to constraint core type %s", tpar, core.typ) } - // The corresponding type argument tx is unknown and there's a single - // specific type and no tilde. + // The corresponding type argument tx is unknown and the core term + // describes a single specific type and no tilde. // In this case the type argument must be that single type; set it. u.set(tpar, core.typ) } - } else { - if tx != nil { - if traceInference { - u.tracef("-> unify type parameter %s (type %s) methods with constraint methods", tpar, tx) - } - // 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. - // TODO(gri) Now that unification handles interfaces, this code can - // be reduced to calling u.unify(tx, tpar.iface(), assign) - // (which will compare signatures exactly as we do below). - // We leave it as is for now because missingMethod provides - // a failure cause which allows for a better error message. - // Eventually, unify should return an error with cause. - var cause string - constraint := tpar.iface() - if m, _ := check.missingMethod(tx, constraint, true, func(x, y Type) bool { return u.unify(x, y, exact) }, &cause); m != nil { - // TODO(gri) better error message (see TODO above) - err.addf(posn, "%s (type %s) does not satisfy %s %s", tpar, tx, tpar.Constraint(), cause) - return nil - } + } + + // Independent of whether there is a core term, if the type argument tx is known + // it must implement the methods of the type constraint, possibly after unification + // of the relevant method signatures, otherwise tx cannot satisfy the constraint. + // This unification step may provide additional type arguments. + // + // Note: The type argument tx may be known but contain references to other type + // parameters (i.e., tx may still be parameterized). + // In this case the methods of tx don't correctly reflect the final method set + // and we may get a missing method error below. Skip this step in this case. + // + // TODO(gri) We should be able continue even with a parameterized tx if we add + // a simplify step beforehand (see below). This will require factoring out the + // simplify phase so we can call it from here. + if tx != nil && !isParameterized(tparams, tx) { + if traceInference { + u.tracef("-> unify type parameter %s (type %s) methods with constraint methods", tpar, tx) + } + // TODO(gri) Now that unification handles interfaces, this code can + // be reduced to calling u.unify(tx, tpar.iface(), assign) + // (which will compare signatures exactly as we do below). + // We leave it as is for now because missingMethod provides + // a failure cause which allows for a better error message. + // Eventually, unify should return an error with cause. + var cause string + constraint := tpar.iface() + if m, _ := check.missingMethod(tx, constraint, true, func(x, y Type) bool { return u.unify(x, y, exact) }, &cause); m != nil { + // TODO(gri) better error message (see TODO above) + err.addf(posn, "%s (type %s) does not satisfy %s %s", tpar, tx, tpar.Constraint(), cause) + return nil } } } diff --git a/src/internal/types/testdata/fixedbugs/issue66751.go b/src/internal/types/testdata/fixedbugs/issue66751.go new file mode 100644 index 0000000000..5a64b4dcc1 --- /dev/null +++ b/src/internal/types/testdata/fixedbugs/issue66751.go @@ -0,0 +1,62 @@ +// Copyright 2024 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(int) {} + +func f[A interface { + ~*B + m(C) +}, B, C any]() { +} + +var _ = f[*S] // must be able to infer all remaining type arguments + +// original test case from issue + +type ptrTo[A any] interface{ ~*A } +type hasFoo[A any] interface{ foo(A) } +type both[A, B any] interface { + ptrTo[A] + hasFoo[B] +} + +type fooer[A any] struct{} + +func (f *fooer[A]) foo(A) {} + +func withPtr[A ptrTo[B], B any]() {} +func withFoo[A hasFoo[B], B any]() {} +func withBoth[A both[B, C], B, C any]() {} + +func _() { + withPtr[*fooer[int]]() // ok + withFoo[*fooer[int]]() // ok + withBoth[*fooer[int]]() // should be able to infer C +} + +// related test case reported in issue + +type X struct{} + +func (x X) M() int { return 42 } + +func CallM1[T interface{ M() R }, R any](t T) R { + return t.M() +} + +func CallM2[T interface { + X + M() R +}, R any](t T) R { + return t.M() +} + +func _() { + CallM1(X{}) // ok + CallM2(X{}) // should be able to infer R +}