]> Cypherpunks repositories - gostls13.git/commitdiff
go/types, types2: eliminate need to sort arguments for type inference
authorRobert Griesemer <gri@golang.org>
Thu, 2 Feb 2023 04:03:17 +0000 (20:03 -0800)
committerGopher Robot <gobot@golang.org>
Mon, 6 Feb 2023 19:34:10 +0000 (19:34 +0000)
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>

src/cmd/compile/internal/types2/infer.go
src/cmd/compile/internal/types2/infer2.go
src/cmd/compile/internal/types2/unify.go
src/go/types/infer.go
src/go/types/infer2.go
src/go/types/unify.go
src/internal/types/testdata/examples/functions.go

index 671ce6a64099c360a5de282d4bc1a530459f5497..6bf7c55434c91feab05b37a38b34d82b95c46380 100644 (file)
@@ -56,51 +56,6 @@ func (check *Checker) infer1(pos syntax.Pos, tparams []*TypeParam, targs []Type,
        // 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:
index 6f0c1ddff513f0b34a8f250812eb316f724b5c47..f8a96c9cd807e5451d4c2bd6b65a20fded41cc7f 100644 (file)
@@ -68,51 +68,6 @@ func (check *Checker) infer2(pos syntax.Pos, tparams []*TypeParam, targs []Type,
        // 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 {
index 48be5aeaefb45a2c36ee2327698a4ad395bf13d3..e73fd8045b8350245c644cb23c25086f587bce09 100644 (file)
@@ -161,15 +161,13 @@ func (u *unifier) at(x *TypeParam) Type {
 }
 
 // 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 @@ func (u *unifier) nify(x, y Type, p *ifacePair) (result bool) {
        }
 
        // 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 @@ func (u *unifier) nify(x, y Type, p *ifacePair) (result bool) {
 
        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)
index 93a43d39eab88c4ee4dbbcf5157fc8cce698c990..a65cdce840c917479f87b468da6fc88f73707d52 100644 (file)
@@ -58,51 +58,6 @@ func (check *Checker) infer1(posn positioner, tparams []*TypeParam, targs []Type
        // 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:
index a0c2ac1c6995222ec39624dab871f959a48734d3..d763e3b7ae82274d4de5aaa3d61369aa780b1097 100644 (file)
@@ -70,51 +70,6 @@ func (check *Checker) infer2(posn positioner, tparams []*TypeParam, targs []Type
        // 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 {
index e10493897cb8dc69e21fd1456ef6850acb1e2080..2e341b3807d8eac8b66c4c464704e29300925043 100644 (file)
@@ -163,15 +163,13 @@ func (u *unifier) at(x *TypeParam) Type {
 }
 
 // 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 @@ func (u *unifier) nify(x, y Type, p *ifacePair) (result bool) {
        }
 
        // 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 @@ func (u *unifier) nify(x, y Type, p *ifacePair) (result bool) {
 
        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)
index 4f58bb5599842f5abddf9efade2761cf8646b839..effb66c616797903c3945c29febe99c6698eae20 100644 (file)
@@ -174,15 +174,15 @@ func g2[T any]([]T, T) {}
 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}