go/types, types2: eliminate need to sort arguments for type inference
When unifying types, we always consider underlying types if inference
would fail otherwise. If a type parameter has a (non-defined) type
inferred and later matches against a defined type, make sure to keep
that defined type instead.
For #43056.
Change-Id: I24e4cd2939df7c8069e505be10914017c1c1c288
Reviewed-on: https://go-review.googlesource.com/c/go/+/464348
Reviewed-by: Robert Griesemer <gri@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
Reviewed-by: Robert Findley <rfindley@google.com>
Auto-Submit: Robert Griesemer <gri@google.com>
Run-TryBot: Robert Griesemer <gri@google.com>
diff --git a/src/cmd/compile/internal/types2/infer.go b/src/cmd/compile/internal/types2/infer.go
index 671ce6a..6bf7c55 100644
--- a/src/cmd/compile/internal/types2/infer.go
+++ b/src/cmd/compile/internal/types2/infer.go
@@ -56,51 +56,6 @@
// Rename type parameters to avoid conflicts in recursive instantiation scenarios.
tparams, params = check.renameTParams(pos, tparams, params)
- // If we have more than 2 arguments, we may have arguments with named and unnamed types.
- // If that is the case, permutate params and args such that the arguments with named
- // types are first in the list. This doesn't affect type inference if all types are taken
- // as is. But when we have inexact unification enabled (as is the case for function type
- // inference), when a named type is unified with an unnamed type, unification proceeds
- // with the underlying type of the named type because otherwise unification would fail
- // right away. This leads to an asymmetry in type inference: in cases where arguments of
- // named and unnamed types are passed to parameters with identical type, different types
- // (named vs underlying) may be inferred depending on the order of the arguments.
- // By ensuring that named types are seen first, order dependence is avoided and unification
- // succeeds where it can (go.dev/issue/43056).
- const enableArgSorting = true
- if m := len(args); m >= 2 && enableArgSorting {
- // Determine indices of arguments with named and unnamed types.
- var named, unnamed []int
- for i, arg := range args {
- if hasName(arg.typ) {
- named = append(named, i)
- } else {
- unnamed = append(unnamed, i)
- }
- }
-
- // If we have named and unnamed types, move the arguments with
- // named types first. Update the parameter list accordingly.
- // Make copies so as not to clobber the incoming slices.
- if len(named) != 0 && len(unnamed) != 0 {
- params2 := make([]*Var, m)
- args2 := make([]*operand, m)
- i := 0
- for _, j := range named {
- params2[i] = params.At(j)
- args2[i] = args[j]
- i++
- }
- for _, j := range unnamed {
- params2[i] = params.At(j)
- args2[i] = args[j]
- i++
- }
- params = NewTuple(params2...)
- args = args2
- }
- }
-
// --- 1 ---
// Continue with the type arguments we have. Avoid matching generic
// parameters that already have type arguments against function arguments:
diff --git a/src/cmd/compile/internal/types2/infer2.go b/src/cmd/compile/internal/types2/infer2.go
index 6f0c1dd..f8a96c9 100644
--- a/src/cmd/compile/internal/types2/infer2.go
+++ b/src/cmd/compile/internal/types2/infer2.go
@@ -68,51 +68,6 @@
// Rename type parameters to avoid conflicts in recursive instantiation scenarios.
tparams, params = check.renameTParams(pos, tparams, params)
- // If we have more than 2 arguments, we may have arguments with named and unnamed types.
- // If that is the case, permutate params and args such that the arguments with named
- // types are first in the list. This doesn't affect type inference if all types are taken
- // as is. But when we have inexact unification enabled (as is the case for function type
- // inference), when a named type is unified with an unnamed type, unification proceeds
- // with the underlying type of the named type because otherwise unification would fail
- // right away. This leads to an asymmetry in type inference: in cases where arguments of
- // named and unnamed types are passed to parameters with identical type, different types
- // (named vs underlying) may be inferred depending on the order of the arguments.
- // By ensuring that named types are seen first, order dependence is avoided and unification
- // succeeds where it can (go.dev/issue/43056).
- const enableArgSorting = true
- if m := len(args); m >= 2 && enableArgSorting {
- // Determine indices of arguments with named and unnamed types.
- var named, unnamed []int
- for i, arg := range args {
- if hasName(arg.typ) {
- named = append(named, i)
- } else {
- unnamed = append(unnamed, i)
- }
- }
-
- // If we have named and unnamed types, move the arguments with
- // named types first. Update the parameter list accordingly.
- // Make copies so as not to clobber the incoming slices.
- if len(named) != 0 && len(unnamed) != 0 {
- params2 := make([]*Var, m)
- args2 := make([]*operand, m)
- i := 0
- for _, j := range named {
- params2[i] = params.At(j)
- args2[i] = args[j]
- i++
- }
- for _, j := range unnamed {
- params2[i] = params.At(j)
- args2[i] = args[j]
- i++
- }
- params = NewTuple(params2...)
- args = args2
- }
- }
-
// Make sure we have a "full" list of type arguments, some of which may
// be nil (unknown). Make a copy so as to not clobber the incoming slice.
if len(targs) < n {
diff --git a/src/cmd/compile/internal/types2/unify.go b/src/cmd/compile/internal/types2/unify.go
index 48be5ae..e73fd80 100644
--- a/src/cmd/compile/internal/types2/unify.go
+++ b/src/cmd/compile/internal/types2/unify.go
@@ -161,15 +161,13 @@
}
// set sets the type t for type parameter x;
-// t must not be nil and it must not have been set before.
+// t must not be nil.
func (u *unifier) set(x *TypeParam, t Type) {
assert(t != nil)
if traceInference {
u.tracef("%s ➞ %s", x, t)
}
- h := u.handles[x]
- assert(*h == nil)
- *h = t
+ *u.handles[x] = t
}
// unknowns returns the number of type parameters for which no type has been set yet.
@@ -259,7 +257,7 @@
}
// Cases where at least one of x or y is a type parameter recorded with u.
- // If we have ar least one type parameter, there is one in x.
+ // If we have at least one type parameter, there is one in x.
switch px, py := u.asTypeParam(x), u.asTypeParam(y); {
case px != nil && py != nil:
// both x and y are type parameters
@@ -271,8 +269,19 @@
case px != nil:
// x is a type parameter, y is not
- if tx := u.at(px); tx != nil {
- return u.nifyEq(tx, y, p)
+ if x := u.at(px); x != nil {
+ // x has an inferred type which must match y
+ if u.nifyEq(x, y, p) {
+ // If we have a match, possibly through underlying types,
+ // and y is a defined type, make sure we record that type
+ // for type parameter x, which may have until now only
+ // recorded an underlying type (go.dev/issue/43056).
+ if _, ok := y.(*Named); ok {
+ u.set(px, y)
+ }
+ return true
+ }
+ return false
}
// otherwise, infer type from y
u.set(px, y)
diff --git a/src/go/types/infer.go b/src/go/types/infer.go
index 93a43d3..a65cdce 100644
--- a/src/go/types/infer.go
+++ b/src/go/types/infer.go
@@ -58,51 +58,6 @@
// Rename type parameters to avoid conflicts in recursive instantiation scenarios.
tparams, params = check.renameTParams(posn.Pos(), tparams, params)
- // If we have more than 2 arguments, we may have arguments with named and unnamed types.
- // If that is the case, permutate params and args such that the arguments with named
- // types are first in the list. This doesn't affect type inference if all types are taken
- // as is. But when we have inexact unification enabled (as is the case for function type
- // inference), when a named type is unified with an unnamed type, unification proceeds
- // with the underlying type of the named type because otherwise unification would fail
- // right away. This leads to an asymmetry in type inference: in cases where arguments of
- // named and unnamed types are passed to parameters with identical type, different types
- // (named vs underlying) may be inferred depending on the order of the arguments.
- // By ensuring that named types are seen first, order dependence is avoided and unification
- // succeeds where it can (go.dev/issue/43056).
- const enableArgSorting = true
- if m := len(args); m >= 2 && enableArgSorting {
- // Determine indices of arguments with named and unnamed types.
- var named, unnamed []int
- for i, arg := range args {
- if hasName(arg.typ) {
- named = append(named, i)
- } else {
- unnamed = append(unnamed, i)
- }
- }
-
- // If we have named and unnamed types, move the arguments with
- // named types first. Update the parameter list accordingly.
- // Make copies so as not to clobber the incoming slices.
- if len(named) != 0 && len(unnamed) != 0 {
- params2 := make([]*Var, m)
- args2 := make([]*operand, m)
- i := 0
- for _, j := range named {
- params2[i] = params.At(j)
- args2[i] = args[j]
- i++
- }
- for _, j := range unnamed {
- params2[i] = params.At(j)
- args2[i] = args[j]
- i++
- }
- params = NewTuple(params2...)
- args = args2
- }
- }
-
// --- 1 ---
// Continue with the type arguments we have. Avoid matching generic
// parameters that already have type arguments against function arguments:
diff --git a/src/go/types/infer2.go b/src/go/types/infer2.go
index a0c2ac1..d763e3b 100644
--- a/src/go/types/infer2.go
+++ b/src/go/types/infer2.go
@@ -70,51 +70,6 @@
// Rename type parameters to avoid conflicts in recursive instantiation scenarios.
tparams, params = check.renameTParams(posn.Pos(), tparams, params)
- // If we have more than 2 arguments, we may have arguments with named and unnamed types.
- // If that is the case, permutate params and args such that the arguments with named
- // types are first in the list. This doesn't affect type inference if all types are taken
- // as is. But when we have inexact unification enabled (as is the case for function type
- // inference), when a named type is unified with an unnamed type, unification proceeds
- // with the underlying type of the named type because otherwise unification would fail
- // right away. This leads to an asymmetry in type inference: in cases where arguments of
- // named and unnamed types are passed to parameters with identical type, different types
- // (named vs underlying) may be inferred depending on the order of the arguments.
- // By ensuring that named types are seen first, order dependence is avoided and unification
- // succeeds where it can (go.dev/issue/43056).
- const enableArgSorting = true
- if m := len(args); m >= 2 && enableArgSorting {
- // Determine indices of arguments with named and unnamed types.
- var named, unnamed []int
- for i, arg := range args {
- if hasName(arg.typ) {
- named = append(named, i)
- } else {
- unnamed = append(unnamed, i)
- }
- }
-
- // If we have named and unnamed types, move the arguments with
- // named types first. Update the parameter list accordingly.
- // Make copies so as not to clobber the incoming slices.
- if len(named) != 0 && len(unnamed) != 0 {
- params2 := make([]*Var, m)
- args2 := make([]*operand, m)
- i := 0
- for _, j := range named {
- params2[i] = params.At(j)
- args2[i] = args[j]
- i++
- }
- for _, j := range unnamed {
- params2[i] = params.At(j)
- args2[i] = args[j]
- i++
- }
- params = NewTuple(params2...)
- args = args2
- }
- }
-
// Make sure we have a "full" list of type arguments, some of which may
// be nil (unknown). Make a copy so as to not clobber the incoming slice.
if len(targs) < n {
diff --git a/src/go/types/unify.go b/src/go/types/unify.go
index e104938..2e341b3 100644
--- a/src/go/types/unify.go
+++ b/src/go/types/unify.go
@@ -163,15 +163,13 @@
}
// set sets the type t for type parameter x;
-// t must not be nil and it must not have been set before.
+// t must not be nil.
func (u *unifier) set(x *TypeParam, t Type) {
assert(t != nil)
if traceInference {
u.tracef("%s ➞ %s", x, t)
}
- h := u.handles[x]
- assert(*h == nil)
- *h = t
+ *u.handles[x] = t
}
// unknowns returns the number of type parameters for which no type has been set yet.
@@ -261,7 +259,7 @@
}
// Cases where at least one of x or y is a type parameter recorded with u.
- // If we have ar least one type parameter, there is one in x.
+ // If we have at least one type parameter, there is one in x.
switch px, py := u.asTypeParam(x), u.asTypeParam(y); {
case px != nil && py != nil:
// both x and y are type parameters
@@ -273,8 +271,19 @@
case px != nil:
// x is a type parameter, y is not
- if tx := u.at(px); tx != nil {
- return u.nifyEq(tx, y, p)
+ if x := u.at(px); x != nil {
+ // x has an inferred type which must match y
+ if u.nifyEq(x, y, p) {
+ // If we have a match, possibly through underlying types,
+ // and y is a defined type, make sure we record that type
+ // for type parameter x, which may have until now only
+ // recorded an underlying type (go.dev/issue/43056).
+ if _, ok := y.(*Named); ok {
+ u.set(px, y)
+ }
+ return true
+ }
+ return false
}
// otherwise, infer type from y
u.set(px, y)
diff --git a/src/internal/types/testdata/examples/functions.go b/src/internal/types/testdata/examples/functions.go
index 4f58bb5..effb66c 100644
--- a/src/internal/types/testdata/examples/functions.go
+++ b/src/internal/types/testdata/examples/functions.go
@@ -174,15 +174,15 @@
func g3[T any](*T, ...T) {}
func _() {
- type intSlize []int
+ type intSlice []int
g1([]int{})
- g1(intSlize{})
+ g1(intSlice{})
g2(nil, 0)
type myString string
var s1 string
g3(nil, "1", myString("2"), "3")
- g3(& /* ERROR "does not match" */ s1, "1", myString("2"), "3")
+ g3(&s1, "1", myString /* ERROR `type myString of myString("2") does not match inferred type string for T` */ ("2"), "3")
_ = s1
type myStruct struct{x int}