Skip to content

Commit f03ab0e

Browse files
committed
go/types, types2: unify core types for unbound type parameters
NOTE: Should this change cause problems, the new functionality can be disabled by setting the flag enableCoreTypeUnification in unify.go to false. In the code func f1[M1 map[K1]int, K1 comparable](m1 M1) {} func f2[M2 map[K2]int, K2 comparable](m2 M2) { f1(m2) } type inference attempts to unify the types of m1 and m2. This leads to the unification attempt of M1 and M2. The result is that the type argument for M1 is inferred to be M2. Since there is no furter function argument to use, constraint type inference attempts to infer the type for K1 which is still missing. Constraint type inference (inferB in the trace below) compares the inferred type for M1 (i.e., M2) against map[K1]int. M2 is bound to f2, not f1; with the existing algorithm that means M2 is simply a named type without further information. Unification fails and with that type inference, and the type checker reports an error. -- inferA [M1₁, K1₂] ➞ [] M1₁ ≡ M2₃ . M1₁ ➞ M2₃ -- inferB [M1₁, K1₂] ➞ [M2₃, <nil>] M1₁ ➞ M2₃ M1₁ ≡ map[K1₂]int . M2₃ ≡ map[K1₂]int . M2₃ ≢ map[K1₂]int M1₁ ≢ map[K1₂]int => inferB [M1₁, K1₂] ➞ [] => inferA [M1₁, K1₂] ➞ [] With this change, when attempting to unify M2 with map[K1]int, rather than failing, the unifier now considers the core type of M2 which is map[K2]int. This leads to the unification of K1 and K2; so type inference successfully infers M2 for M1 and K2 for K1. -- inferA [M1₁, K1₂] ➞ [] M1₁ ≡ M2₃ . M1₁ ➞ M2₃ -- inferB [M1₁, K1₂] ➞ [M2₃, <nil>] M1₁ ➞ M2₃ M1₁ ≡ map[K1₂]int . M2₃ ≡ map[K1₂]int . . core M2₃ ≡ map[K1₂]int . . map[K2₄]int ≡ map[K1₂]int . . . K2₄ ≡ K1₂ . . . . K1₂ ➞ K2₄ . . . int ≡ int => inferB [M1₁, K1₂] ➞ [M2₃, K2₄] => inferA [M1₁, K1₂] ➞ [M2₃, K2₄] The fix for this issue was provided by Rob Findley in CL 380375; this change is a copy of that fix with some additional changes: - Constraint type inference doesn't simply use a type parameter's core type. Instead, if the type parameter type set consists of a single, possibly named type, it uses that type. Factor out the existing code into a new function adjCoreType. This change is not strictly needed but makes it easier to think about the code. - Tracing code is added for debugging type inference. All tracing code is guarded with the flag traceEnabled which is set to false by default. - The change to the unification algorithm is guarded with the flag enableCoreTypeUnification. - The sprintf function has a new type switch case for lists of type parameters. This is used for tracing output (and was also missing for a panic that was printing type parameter lists). Fixes #50755. Change-Id: Ie50c8f4540fcd446a71b07e2b451a95339b530ce Reviewed-on: https://go-review.googlesource.com/c/go/+/385354 Trust: Robert Griesemer <gri@golang.org> Run-TryBot: Robert Griesemer <gri@golang.org> Reviewed-by: Robert Findley <rfindley@google.com> TryBot-Result: Gopher Robot <gobot@golang.org>
1 parent f14ad78 commit f03ab0e

File tree

8 files changed

+280
-22
lines changed

8 files changed

+280
-22
lines changed

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

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,17 @@ func sprintf(qf Qualifier, debug bool, format string, args ...interface{}) strin
124124
}
125125
buf.WriteByte(']')
126126
arg = buf.String()
127+
case []*TypeParam:
128+
var buf bytes.Buffer
129+
buf.WriteByte('[')
130+
for i, x := range a {
131+
if i > 0 {
132+
buf.WriteString(", ")
133+
}
134+
buf.WriteString(typeString(x, qf, debug)) // use typeString so we get subscripts when debugging
135+
}
136+
buf.WriteByte(']')
137+
arg = buf.String()
127138
}
128139
args[i] = arg
129140
}

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

Lines changed: 30 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,13 @@ func (check *Checker) infer(pos syntax.Pos, tparams []*TypeParam, targs []Type,
4141
}()
4242
}
4343

44+
if traceInference {
45+
check.dump("-- inferA %s ➞ %s", tparams, targs)
46+
defer func() {
47+
check.dump("=> inferA %s ➞ %s", tparams, result)
48+
}()
49+
}
50+
4451
// There must be at least one type parameter, and no more type arguments than type parameters.
4552
n := len(tparams)
4653
assert(n > 0 && len(targs) <= n)
@@ -403,6 +410,13 @@ func (w *tpWalker) isParameterizedTypeList(list []Type) bool {
403410
func (check *Checker) inferB(pos syntax.Pos, tparams []*TypeParam, targs []Type) (types []Type, index int) {
404411
assert(len(tparams) >= len(targs) && len(targs) > 0)
405412

413+
if traceInference {
414+
check.dump("-- inferB %s ➞ %s", tparams, targs)
415+
defer func() {
416+
check.dump("=> inferB %s ➞ %s", tparams, types)
417+
}()
418+
}
419+
406420
// Setup bidirectional unification between constraints
407421
// and the corresponding type arguments (which may be nil!).
408422
u := newUnifier(false)
@@ -418,17 +432,11 @@ func (check *Checker) inferB(pos syntax.Pos, tparams []*TypeParam, targs []Type)
418432

419433
// If a constraint has a core type, unify the corresponding type parameter with it.
420434
for _, tpar := range tparams {
421-
sbound := coreType(tpar)
422-
if sbound != nil {
423-
// If the core type is the underlying type of a single
424-
// defined type in the constraint, use that defined type instead.
425-
if named, _ := tpar.singleType().(*Named); named != nil {
426-
sbound = named
427-
}
428-
if !u.unify(tpar, sbound) {
435+
if ctype := adjCoreType(tpar); ctype != nil {
436+
if !u.unify(tpar, ctype) {
429437
// TODO(gri) improve error message by providing the type arguments
430438
// which we know already
431-
check.errorf(pos, "%s does not match %s", tpar, sbound)
439+
check.errorf(pos, "%s does not match %s", tpar, ctype)
432440
return nil, 0
433441
}
434442
}
@@ -525,6 +533,19 @@ func (check *Checker) inferB(pos syntax.Pos, tparams []*TypeParam, targs []Type)
525533
return
526534
}
527535

536+
func adjCoreType(tpar *TypeParam) Type {
537+
// If the type parameter embeds a single, possibly named
538+
// type, use that one instead of the core type (which is
539+
// always the underlying type of that single type).
540+
if single := tpar.singleType(); single != nil {
541+
if debug {
542+
assert(under(single) == coreType(tpar))
543+
}
544+
return single
545+
}
546+
return coreType(tpar)
547+
}
548+
528549
type cycleFinder struct {
529550
tparams []*TypeParam
530551
types []Type
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
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+
func f1[M1 map[K1]int, K1 comparable](m1 M1) {}
8+
9+
func f2[M2 map[K2]int, K2 comparable](m2 M2) {
10+
f1(m2)
11+
}
12+
13+
// test case from issue
14+
15+
func Copy[MC ~map[KC]VC, KC comparable, VC any](dst, src MC) {
16+
for k, v := range src {
17+
dst[k] = v
18+
}
19+
}
20+
21+
func Merge[MM ~map[KM]VM, KM comparable, VM any](ms ...MM) MM {
22+
result := MM{}
23+
for _, m := range ms {
24+
Copy(result, m)
25+
}
26+
return result
27+
}

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

Lines changed: 72 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ package types2
99
import (
1010
"bytes"
1111
"fmt"
12+
"strings"
1213
)
1314

1415
// The unifier maintains two separate sets of type parameters x and y
@@ -41,6 +42,19 @@ const (
4142
// Whether to panic when unificationDepthLimit is reached. Turn on when
4243
// investigating infinite recursion.
4344
panicAtUnificationDepthLimit = false
45+
46+
// If enableCoreTypeUnification is set, unification will consider
47+
// the core types, if any, of non-local (unbound) type parameters.
48+
enableCoreTypeUnification = true
49+
50+
// If traceInference is set, unification will print a trace of its operation.
51+
// Interpretation of trace:
52+
// x ≡ y attempt to unify types x and y
53+
// p ➞ y type parameter p is set to type y (p is inferred to be y)
54+
// p ⇄ q type parameters p and q match (p is inferred to be q and vice versa)
55+
// x ≢ y types x and y cannot be unified
56+
// [p, q, ...] ➞ [x, y, ...] mapping from type parameters to types
57+
traceInference = false
4458
)
4559

4660
// A unifier maintains the current type parameters for x and y
@@ -58,6 +72,7 @@ type unifier struct {
5872
// exactly. If exact is not set, a named type's underlying type
5973
// is considered if unification would fail otherwise, and the
6074
// direction of channels is ignored.
75+
// TODO(gri) exact is not set anymore by a caller. Consider removing it.
6176
func newUnifier(exact bool) *unifier {
6277
u := &unifier{exact: exact}
6378
u.x.unifier = u
@@ -70,6 +85,10 @@ func (u *unifier) unify(x, y Type) bool {
7085
return u.nify(x, y, nil)
7186
}
7287

88+
func (u *unifier) tracef(format string, args ...interface{}) {
89+
fmt.Println(strings.Repeat(". ", u.depth) + sprintf(nil, true, format, args...))
90+
}
91+
7392
// A tparamsList describes a list of type parameters and the types inferred for them.
7493
type tparamsList struct {
7594
unifier *unifier
@@ -121,6 +140,9 @@ func (d *tparamsList) init(tparams []*TypeParam) {
121140
// If both type parameters already have a type associated with them and they are
122141
// not joined, join fails and returns false.
123142
func (u *unifier) join(i, j int) bool {
143+
if traceInference {
144+
u.tracef("%s ⇄ %s", u.x.tparams[i], u.y.tparams[j])
145+
}
124146
ti := u.x.indices[i]
125147
tj := u.y.indices[j]
126148
switch {
@@ -210,6 +232,9 @@ func (d *tparamsList) at(i int) Type {
210232
func (d *tparamsList) set(i int, typ Type) {
211233
assert(typ != nil)
212234
u := d.unifier
235+
if traceInference {
236+
u.tracef("%s ➞ %s", d.tparams[i], typ)
237+
}
213238
switch ti := d.indices[i]; {
214239
case ti < 0:
215240
u.types[-ti-1] = typ
@@ -247,9 +272,16 @@ func (u *unifier) nifyEq(x, y Type, p *ifacePair) bool {
247272
// adapted version of Checker.identical. For changes to that
248273
// code the corresponding changes should be made here.
249274
// Must not be called directly from outside the unifier.
250-
func (u *unifier) nify(x, y Type, p *ifacePair) bool {
275+
func (u *unifier) nify(x, y Type, p *ifacePair) (result bool) {
276+
if traceInference {
277+
u.tracef("%s ≡ %s", x, y)
278+
}
279+
251280
// Stop gap for cases where unification fails.
252281
if u.depth >= unificationDepthLimit {
282+
if traceInference {
283+
u.tracef("depth %d >= %d", u.depth, unificationDepthLimit)
284+
}
253285
if panicAtUnificationDepthLimit {
254286
panic("unification reached recursion depth limit")
255287
}
@@ -258,6 +290,9 @@ func (u *unifier) nify(x, y Type, p *ifacePair) bool {
258290
u.depth++
259291
defer func() {
260292
u.depth--
293+
if traceInference && !result {
294+
u.tracef("%s ≢ %s", x, y)
295+
}
261296
}()
262297

263298
if !u.exact {
@@ -267,8 +302,14 @@ func (u *unifier) nify(x, y Type, p *ifacePair) bool {
267302
// (We use !hasName to exclude any type with a name, including
268303
// basic types and type parameters; the rest are unamed types.)
269304
if nx, _ := x.(*Named); nx != nil && !hasName(y) {
305+
if traceInference {
306+
u.tracef("under %s ≡ %s", nx, y)
307+
}
270308
return u.nify(nx.under(), y, p)
271309
} else if ny, _ := y.(*Named); ny != nil && !hasName(x) {
310+
if traceInference {
311+
u.tracef("%s ≡ under %s", x, ny)
312+
}
272313
return u.nify(x, ny.under(), p)
273314
}
274315
}
@@ -302,6 +343,35 @@ func (u *unifier) nify(x, y Type, p *ifacePair) bool {
302343
return true
303344
}
304345

346+
// If we get here and x or y is a type parameter, they are type parameters
347+
// from outside our declaration list. Try to unify their core types, if any
348+
// (see issue #50755 for a test case).
349+
if enableCoreTypeUnification && !u.exact {
350+
if isTypeParam(x) && !hasName(y) {
351+
// When considering the type parameter for unification
352+
// we look at the adjusted core type (adjCoreType).
353+
// If the adjusted core type is a named type N; the
354+
// corresponding core type is under(N). Since !u.exact
355+
// and y doesn't have a name, unification will end up
356+
// comparing under(N) to y, so we can just use the core
357+
// type instead. Optimization.
358+
if cx := coreType(x); cx != nil {
359+
if traceInference {
360+
u.tracef("core %s ≡ %s", x, y)
361+
}
362+
return u.nify(cx, y, p)
363+
}
364+
} else if isTypeParam(y) && !hasName(x) {
365+
// see comment above
366+
if cy := coreType(y); cy != nil {
367+
if traceInference {
368+
u.tracef("%s ≡ core %s", x, y)
369+
}
370+
return u.nify(x, cy, p)
371+
}
372+
}
373+
}
374+
305375
// For type unification, do not shortcut (x == y) for identical
306376
// types. Instead keep comparing them element-wise to unify the
307377
// matching (and equal type parameter types). A simple test case
@@ -490,7 +560,7 @@ func (u *unifier) nify(x, y Type, p *ifacePair) bool {
490560
// avoid a crash in case of nil type
491561

492562
default:
493-
panic(fmt.Sprintf("### u.nify(%s, %s), u.x.tparams = %s", x, y, u.x.tparams))
563+
panic(sprintf(nil, true, "u.nify(%s, %s), u.x.tparams = %s", x, y, u.x.tparams))
494564
}
495565

496566
return false

src/go/types/errors.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,17 @@ func sprintf(fset *token.FileSet, qf Qualifier, debug bool, format string, args
110110
}
111111
buf.WriteByte(']')
112112
arg = buf.String()
113+
case []*TypeParam:
114+
var buf bytes.Buffer
115+
buf.WriteByte('[')
116+
for i, x := range a {
117+
if i > 0 {
118+
buf.WriteString(", ")
119+
}
120+
buf.WriteString(typeString(x, qf, debug)) // use typeString so we get subscripts when debugging
121+
}
122+
buf.WriteByte(']')
123+
arg = buf.String()
113124
}
114125
args[i] = arg
115126
}

src/go/types/infer.go

Lines changed: 30 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,13 @@ func (check *Checker) infer(posn positioner, tparams []*TypeParam, targs []Type,
4040
}()
4141
}
4242

43+
if traceInference {
44+
check.dump("-- inferA %s ➞ %s", tparams, targs)
45+
defer func() {
46+
check.dump("=> inferA %s ➞ %s", tparams, result)
47+
}()
48+
}
49+
4350
// There must be at least one type parameter, and no more type arguments than type parameters.
4451
n := len(tparams)
4552
assert(n > 0 && len(targs) <= n)
@@ -402,6 +409,13 @@ func (w *tpWalker) isParameterizedTypeList(list []Type) bool {
402409
func (check *Checker) inferB(posn positioner, tparams []*TypeParam, targs []Type) (types []Type, index int) {
403410
assert(len(tparams) >= len(targs) && len(targs) > 0)
404411

412+
if traceInference {
413+
check.dump("-- inferB %s ➞ %s", tparams, targs)
414+
defer func() {
415+
check.dump("=> inferB %s ➞ %s", tparams, types)
416+
}()
417+
}
418+
405419
// Setup bidirectional unification between constraints
406420
// and the corresponding type arguments (which may be nil!).
407421
u := newUnifier(false)
@@ -417,17 +431,11 @@ func (check *Checker) inferB(posn positioner, tparams []*TypeParam, targs []Type
417431

418432
// If a constraint has a core type, unify the corresponding type parameter with it.
419433
for _, tpar := range tparams {
420-
sbound := coreType(tpar)
421-
if sbound != nil {
422-
// If the core type is the underlying type of a single
423-
// defined type in the constraint, use that defined type instead.
424-
if named, _ := tpar.singleType().(*Named); named != nil {
425-
sbound = named
426-
}
427-
if !u.unify(tpar, sbound) {
434+
if ctype := adjCoreType(tpar); ctype != nil {
435+
if !u.unify(tpar, ctype) {
428436
// TODO(gri) improve error message by providing the type arguments
429437
// which we know already
430-
check.errorf(posn, _InvalidTypeArg, "%s does not match %s", tpar, sbound)
438+
check.errorf(posn, _InvalidTypeArg, "%s does not match %s", tpar, ctype)
431439
return nil, 0
432440
}
433441
}
@@ -524,6 +532,19 @@ func (check *Checker) inferB(posn positioner, tparams []*TypeParam, targs []Type
524532
return
525533
}
526534

535+
func adjCoreType(tpar *TypeParam) Type {
536+
// If the type parameter embeds a single, possibly named
537+
// type, use that one instead of the core type (which is
538+
// always the underlying type of that single type).
539+
if single := tpar.singleType(); single != nil {
540+
if debug {
541+
assert(under(single) == coreType(tpar))
542+
}
543+
return single
544+
}
545+
return coreType(tpar)
546+
}
547+
527548
type cycleFinder struct {
528549
tparams []*TypeParam
529550
types []Type
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
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+
func f1[M1 map[K1]int, K1 comparable](m1 M1) {}
8+
9+
func f2[M2 map[K2]int, K2 comparable](m2 M2) {
10+
f1(m2)
11+
}
12+
13+
// test case from issue
14+
15+
func Copy[MC ~map[KC]VC, KC comparable, VC any](dst, src MC) {
16+
for k, v := range src {
17+
dst[k] = v
18+
}
19+
}
20+
21+
func Merge[MM ~map[KM]VM, KM comparable, VM any](ms ...MM) MM {
22+
result := MM{}
23+
for _, m := range ms {
24+
Copy(result, m)
25+
}
26+
return result
27+
}

0 commit comments

Comments
 (0)