Skip to content

Commit 68b3d36

Browse files
committed
go/types, types2: make function type inference argument-order independent
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. This CL implements the respectice code but keeps it disabled for now, pending decision whether we want to address this issue in the first place. For #43056. Change-Id: Ibe3b08ec2afe90a24a8c30cd1875d504bcc2ef39 Reviewed-on: https://go-review.googlesource.com/c/go/+/377894 Trust: Robert Griesemer <[email protected]> Run-TryBot: Robert Griesemer <[email protected]> Reviewed-by: Robert Findley <[email protected]>
1 parent 83bfdb6 commit 68b3d36

File tree

4 files changed

+160
-2
lines changed

4 files changed

+160
-2
lines changed

src/cmd/compile/internal/types2/infer.go

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,54 @@ func (check *Checker) infer(pos syntax.Pos, tparams []*TypeParam, targs []Type,
5454
}
5555
// len(targs) < n
5656

57+
// If we have more than 2 arguments, we may have arguments with named and unnamed types.
58+
// If that is the case, permutate params and args such that the arguments with named
59+
// types are first in the list. This doesn't affect type inference if all types are taken
60+
// as is. But when we have inexact unification enabled (as is the case for function type
61+
// inference), when a named type is unified with an unnamed type, unification proceeds
62+
// with the underlying type of the named type because otherwise unification would fail
63+
// right away. This leads to an asymmetry in type inference: in cases where arguments of
64+
// named and unnamed types are passed to parameters with identical type, different types
65+
// (named vs underlying) may be inferred depending on the order of the arguments.
66+
// By ensuring that named types are seen first, order dependence is avoided and unification
67+
// succeeds where it can.
68+
//
69+
// This code is disabled for now pending decision whether we want to address cases like
70+
// these and make the spec on type inference more complicated (see issue #43056).
71+
const enableArgSorting = false
72+
if m := len(args); m >= 2 && enableArgSorting {
73+
// Determine indices of arguments with named and unnamed types.
74+
var named, unnamed []int
75+
for i, arg := range args {
76+
if hasName(arg.typ) {
77+
named = append(named, i)
78+
} else {
79+
unnamed = append(unnamed, i)
80+
}
81+
}
82+
83+
// If we have named and unnamed types, move the arguments with
84+
// named types first. Update the parameter list accordingly.
85+
// Make copies so as not to clobber the incoming slices.
86+
if len(named) != 0 && len(unnamed) != 0 {
87+
params2 := make([]*Var, m)
88+
args2 := make([]*operand, m)
89+
i := 0
90+
for _, j := range named {
91+
params2[i] = params.At(j)
92+
args2[i] = args[j]
93+
i++
94+
}
95+
for _, j := range unnamed {
96+
params2[i] = params.At(j)
97+
args2[i] = args[j]
98+
i++
99+
}
100+
params = NewTuple(params2...)
101+
args = args2
102+
}
103+
}
104+
57105
// --- 1 ---
58106
// Continue with the type arguments we have. Avoid matching generic
59107
// parameters that already have type arguments against function arguments:
@@ -62,7 +110,7 @@ func (check *Checker) infer(pos syntax.Pos, tparams []*TypeParam, targs []Type,
62110
// arguments we have, and continue with that parameter list.
63111

64112
// First, make sure we have a "full" list of type arguments, some of which
65-
// may be nil (unknown).
113+
// may be nil (unknown). Make a copy so as to not clobber the incoming slice.
66114
if len(targs) < n {
67115
targs2 := make([]Type, n)
68116
copy(targs2, targs)
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
// Copyright 2022 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package p
6+
7+
// simplified example
8+
func f[T ~func(T)](a, b T) {}
9+
10+
type F func(F)
11+
12+
func _() {
13+
var i F
14+
var j func(F)
15+
16+
f(i, j)
17+
// f(j, i) // disabled for now
18+
}
19+
20+
// example from issue
21+
func g[T interface{ Equal(T) bool }](a, b T) {}
22+
23+
type I interface{ Equal(I) bool }
24+
25+
func _() {
26+
var i I
27+
var j interface{ Equal(I) bool }
28+
29+
g(i, j)
30+
// g(j, i) // disabled for now
31+
}

src/go/types/infer.go

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,54 @@ func (check *Checker) infer(posn positioner, tparams []*TypeParam, targs []Type,
5353
}
5454
// len(targs) < n
5555

56+
// If we have more than 2 arguments, we may have arguments with named and unnamed types.
57+
// If that is the case, permutate params and args such that the arguments with named
58+
// types are first in the list. This doesn't affect type inference if all types are taken
59+
// as is. But when we have inexact unification enabled (as is the case for function type
60+
// inference), when a named type is unified with an unnamed type, unification proceeds
61+
// with the underlying type of the named type because otherwise unification would fail
62+
// right away. This leads to an asymmetry in type inference: in cases where arguments of
63+
// named and unnamed types are passed to parameters with identical type, different types
64+
// (named vs underlying) may be inferred depending on the order of the arguments.
65+
// By ensuring that named types are seen first, order dependence is avoided and unification
66+
// succeeds where it can.
67+
//
68+
// This code is disabled for now pending decision whether we want to address cases like
69+
// these and make the spec on type inference more complicated (see issue #43056).
70+
const enableArgSorting = false
71+
if m := len(args); m >= 2 && enableArgSorting {
72+
// Determine indices of arguments with named and unnamed types.
73+
var named, unnamed []int
74+
for i, arg := range args {
75+
if hasName(arg.typ) {
76+
named = append(named, i)
77+
} else {
78+
unnamed = append(unnamed, i)
79+
}
80+
}
81+
82+
// If we have named and unnamed types, move the arguments with
83+
// named types first. Update the parameter list accordingly.
84+
// Make copies so as not to clobber the incoming slices.
85+
if len(named) != 0 && len(unnamed) != 0 {
86+
params2 := make([]*Var, m)
87+
args2 := make([]*operand, m)
88+
i := 0
89+
for _, j := range named {
90+
params2[i] = params.At(j)
91+
args2[i] = args[j]
92+
i++
93+
}
94+
for _, j := range unnamed {
95+
params2[i] = params.At(j)
96+
args2[i] = args[j]
97+
i++
98+
}
99+
params = NewTuple(params2...)
100+
args = args2
101+
}
102+
}
103+
56104
// --- 1 ---
57105
// Continue with the type arguments we have. Avoid matching generic
58106
// parameters that already have type arguments against function arguments:
@@ -61,7 +109,7 @@ func (check *Checker) infer(posn positioner, tparams []*TypeParam, targs []Type,
61109
// arguments we have, and continue with that parameter list.
62110

63111
// First, make sure we have a "full" list of type arguments, some of which
64-
// may be nil (unknown).
112+
// may be nil (unknown). Make a copy so as to not clobber the incoming slice.
65113
if len(targs) < n {
66114
targs2 := make([]Type, n)
67115
copy(targs2, targs)
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
// Copyright 2022 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package p
6+
7+
// simplified example
8+
func f[T ~func(T)](a, b T) {}
9+
10+
type F func(F)
11+
12+
func _() {
13+
var i F
14+
var j func(F)
15+
16+
f(i, j)
17+
// f(j, i) // disabled for now
18+
}
19+
20+
// example from issue
21+
func g[T interface{ Equal(T) bool }](a, b T) {}
22+
23+
type I interface{ Equal(I) bool }
24+
25+
func _() {
26+
var i I
27+
var j interface{ Equal(I) bool }
28+
29+
g(i, j)
30+
// g(j, i) // disabled for now
31+
}

0 commit comments

Comments
 (0)