Description
This is extracted from an old comment by @rsc on the behavior of type inference as described in the spec (and as currently implemented). Given the following code:
// user-defined implementation of append
func Append[S ~[]T, T any](s S, x ...T) S { /* implementation of append */ return s }
func _() {
type MyPtr *int
var x []MyPtr
_ = append(x, new(int)) // built-in append: ok
_ = Append(x, new(int)) // user-defined append, not ok: []MyPtr does not implement ~[]*int
}
Type inference passes in separate phases of function argument type inference and constraint type inference: In the first phase (function arguments) the following matches are established: S -> []MyPtr
and T -> *int
. At this point we have all type arguments and we're done. After instantiation of Append
we have the non-generic function:
func Append(s []*int, x ...*int) []MyPtr
and now []MyPtr
doesn't implement ~[]*int
(MyPtr
is not identical to *int
).
Per the comment by @rsc, this could work if function argument type inference and constraint type inference were interleaved (and inference using typed function arguments would stop as soon as all type arguments are known).
In such an interleaved world, as soon as we have established the mapping S -> []MyPtr
constraint type inference would match []MyPtr
against []T
and establish the mapping T -> MyPtr
. At this point all type arguments are inferred and upon instantiation of Append
we would get:
func Append(s []MyPtr, x ...MyPtr) []MyPtr
Calling this version of Append
would work because the unnamed type *int
can be assigned to the named type MyPtr
.
It might even be possible to apply constraint type inference to constraints that don't have a single specific type: As soon as we have a type argument inferred from a function argument it could be matched against every specific type in the constraint.
The interleaved type inference behavior is more powerful in this specific case (and thus we could change this in a backward-compatible way).
@rsc, @ianlancetaylor, @findleyr for comments.