}
buf.WriteByte(']')
arg = buf.String()
+ case []*TypeParam:
+ var buf bytes.Buffer
+ buf.WriteByte('[')
+ for i, x := range a {
+ if i > 0 {
+ buf.WriteString(", ")
+ }
+ buf.WriteString(typeString(x, qf, debug)) // use typeString so we get subscripts when debugging
+ }
+ buf.WriteByte(']')
+ arg = buf.String()
}
args[i] = arg
}
}()
}
+ if traceInference {
+ check.dump("-- inferA %s ➞ %s", tparams, targs)
+ defer func() {
+ check.dump("=> inferA %s ➞ %s", tparams, result)
+ }()
+ }
+
// There must be at least one type parameter, and no more type arguments than type parameters.
n := len(tparams)
assert(n > 0 && len(targs) <= n)
func (check *Checker) inferB(pos syntax.Pos, tparams []*TypeParam, targs []Type) (types []Type, index int) {
assert(len(tparams) >= len(targs) && len(targs) > 0)
+ if traceInference {
+ check.dump("-- inferB %s ➞ %s", tparams, targs)
+ defer func() {
+ check.dump("=> inferB %s ➞ %s", tparams, types)
+ }()
+ }
+
// Setup bidirectional unification between constraints
// and the corresponding type arguments (which may be nil!).
u := newUnifier(false)
// If a constraint has a core type, unify the corresponding type parameter with it.
for _, tpar := range tparams {
- sbound := coreType(tpar)
- if sbound != nil {
- // If the core type is the underlying type of a single
- // defined type in the constraint, use that defined type instead.
- if named, _ := tpar.singleType().(*Named); named != nil {
- sbound = named
- }
- if !u.unify(tpar, sbound) {
+ if ctype := adjCoreType(tpar); ctype != nil {
+ if !u.unify(tpar, ctype) {
// TODO(gri) improve error message by providing the type arguments
// which we know already
- check.errorf(pos, "%s does not match %s", tpar, sbound)
+ check.errorf(pos, "%s does not match %s", tpar, ctype)
return nil, 0
}
}
return
}
+func adjCoreType(tpar *TypeParam) Type {
+ // If the type parameter embeds a single, possibly named
+ // type, use that one instead of the core type (which is
+ // always the underlying type of that single type).
+ if single := tpar.singleType(); single != nil {
+ if debug {
+ assert(under(single) == coreType(tpar))
+ }
+ return single
+ }
+ return coreType(tpar)
+}
+
type cycleFinder struct {
tparams []*TypeParam
types []Type
--- /dev/null
+// Copyright 2022 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package p
+
+func f1[M1 map[K1]int, K1 comparable](m1 M1) {}
+
+func f2[M2 map[K2]int, K2 comparable](m2 M2) {
+ f1(m2)
+}
+
+// test case from issue
+
+func Copy[MC ~map[KC]VC, KC comparable, VC any](dst, src MC) {
+ for k, v := range src {
+ dst[k] = v
+ }
+}
+
+func Merge[MM ~map[KM]VM, KM comparable, VM any](ms ...MM) MM {
+ result := MM{}
+ for _, m := range ms {
+ Copy(result, m)
+ }
+ return result
+}
import (
"bytes"
"fmt"
+ "strings"
)
// The unifier maintains two separate sets of type parameters x and y
// Whether to panic when unificationDepthLimit is reached. Turn on when
// investigating infinite recursion.
panicAtUnificationDepthLimit = false
+
+ // If enableCoreTypeUnification is set, unification will consider
+ // the core types, if any, of non-local (unbound) type parameters.
+ enableCoreTypeUnification = true
+
+ // If traceInference is set, unification will print a trace of its operation.
+ // Interpretation of trace:
+ // x ≡ y attempt to unify types x and y
+ // p ➞ y type parameter p is set to type y (p is inferred to be y)
+ // p ⇄ q type parameters p and q match (p is inferred to be q and vice versa)
+ // x ≢ y types x and y cannot be unified
+ // [p, q, ...] ➞ [x, y, ...] mapping from type parameters to types
+ traceInference = false
)
// A unifier maintains the current type parameters for x and y
// exactly. If exact is not set, a named type's underlying type
// is considered if unification would fail otherwise, and the
// direction of channels is ignored.
+// TODO(gri) exact is not set anymore by a caller. Consider removing it.
func newUnifier(exact bool) *unifier {
u := &unifier{exact: exact}
u.x.unifier = u
return u.nify(x, y, nil)
}
+func (u *unifier) tracef(format string, args ...interface{}) {
+ fmt.Println(strings.Repeat(". ", u.depth) + sprintf(nil, true, format, args...))
+}
+
// A tparamsList describes a list of type parameters and the types inferred for them.
type tparamsList struct {
unifier *unifier
// If both type parameters already have a type associated with them and they are
// not joined, join fails and returns false.
func (u *unifier) join(i, j int) bool {
+ if traceInference {
+ u.tracef("%s ⇄ %s", u.x.tparams[i], u.y.tparams[j])
+ }
ti := u.x.indices[i]
tj := u.y.indices[j]
switch {
func (d *tparamsList) set(i int, typ Type) {
assert(typ != nil)
u := d.unifier
+ if traceInference {
+ u.tracef("%s ➞ %s", d.tparams[i], typ)
+ }
switch ti := d.indices[i]; {
case ti < 0:
u.types[-ti-1] = typ
// adapted version of Checker.identical. For changes to that
// code the corresponding changes should be made here.
// Must not be called directly from outside the unifier.
-func (u *unifier) nify(x, y Type, p *ifacePair) bool {
+func (u *unifier) nify(x, y Type, p *ifacePair) (result bool) {
+ if traceInference {
+ u.tracef("%s ≡ %s", x, y)
+ }
+
// Stop gap for cases where unification fails.
if u.depth >= unificationDepthLimit {
+ if traceInference {
+ u.tracef("depth %d >= %d", u.depth, unificationDepthLimit)
+ }
if panicAtUnificationDepthLimit {
panic("unification reached recursion depth limit")
}
u.depth++
defer func() {
u.depth--
+ if traceInference && !result {
+ u.tracef("%s ≢ %s", x, y)
+ }
}()
if !u.exact {
// (We use !hasName to exclude any type with a name, including
// basic types and type parameters; the rest are unamed types.)
if nx, _ := x.(*Named); nx != nil && !hasName(y) {
+ if traceInference {
+ u.tracef("under %s ≡ %s", nx, y)
+ }
return u.nify(nx.under(), y, p)
} else if ny, _ := y.(*Named); ny != nil && !hasName(x) {
+ if traceInference {
+ u.tracef("%s ≡ under %s", x, ny)
+ }
return u.nify(x, ny.under(), p)
}
}
return true
}
+ // If we get here and x or y is a type parameter, they are type parameters
+ // from outside our declaration list. Try to unify their core types, if any
+ // (see issue #50755 for a test case).
+ if enableCoreTypeUnification && !u.exact {
+ if isTypeParam(x) && !hasName(y) {
+ // When considering the type parameter for unification
+ // we look at the adjusted core type (adjCoreType).
+ // If the adjusted core type is a named type N; the
+ // corresponding core type is under(N). Since !u.exact
+ // and y doesn't have a name, unification will end up
+ // comparing under(N) to y, so we can just use the core
+ // type instead. Optimization.
+ if cx := coreType(x); cx != nil {
+ if traceInference {
+ u.tracef("core %s ≡ %s", x, y)
+ }
+ return u.nify(cx, y, p)
+ }
+ } else if isTypeParam(y) && !hasName(x) {
+ // see comment above
+ if cy := coreType(y); cy != nil {
+ if traceInference {
+ u.tracef("%s ≡ core %s", x, y)
+ }
+ return u.nify(x, cy, p)
+ }
+ }
+ }
+
// For type unification, do not shortcut (x == y) for identical
// types. Instead keep comparing them element-wise to unify the
// matching (and equal type parameter types). A simple test case
// avoid a crash in case of nil type
default:
- panic(fmt.Sprintf("### u.nify(%s, %s), u.x.tparams = %s", x, y, u.x.tparams))
+ panic(sprintf(nil, true, "u.nify(%s, %s), u.x.tparams = %s", x, y, u.x.tparams))
}
return false
}
buf.WriteByte(']')
arg = buf.String()
+ case []*TypeParam:
+ var buf bytes.Buffer
+ buf.WriteByte('[')
+ for i, x := range a {
+ if i > 0 {
+ buf.WriteString(", ")
+ }
+ buf.WriteString(typeString(x, qf, debug)) // use typeString so we get subscripts when debugging
+ }
+ buf.WriteByte(']')
+ arg = buf.String()
}
args[i] = arg
}
}()
}
+ if traceInference {
+ check.dump("-- inferA %s ➞ %s", tparams, targs)
+ defer func() {
+ check.dump("=> inferA %s ➞ %s", tparams, result)
+ }()
+ }
+
// There must be at least one type parameter, and no more type arguments than type parameters.
n := len(tparams)
assert(n > 0 && len(targs) <= n)
func (check *Checker) inferB(posn positioner, tparams []*TypeParam, targs []Type) (types []Type, index int) {
assert(len(tparams) >= len(targs) && len(targs) > 0)
+ if traceInference {
+ check.dump("-- inferB %s ➞ %s", tparams, targs)
+ defer func() {
+ check.dump("=> inferB %s ➞ %s", tparams, types)
+ }()
+ }
+
// Setup bidirectional unification between constraints
// and the corresponding type arguments (which may be nil!).
u := newUnifier(false)
// If a constraint has a core type, unify the corresponding type parameter with it.
for _, tpar := range tparams {
- sbound := coreType(tpar)
- if sbound != nil {
- // If the core type is the underlying type of a single
- // defined type in the constraint, use that defined type instead.
- if named, _ := tpar.singleType().(*Named); named != nil {
- sbound = named
- }
- if !u.unify(tpar, sbound) {
+ if ctype := adjCoreType(tpar); ctype != nil {
+ if !u.unify(tpar, ctype) {
// TODO(gri) improve error message by providing the type arguments
// which we know already
- check.errorf(posn, _InvalidTypeArg, "%s does not match %s", tpar, sbound)
+ check.errorf(posn, _InvalidTypeArg, "%s does not match %s", tpar, ctype)
return nil, 0
}
}
return
}
+func adjCoreType(tpar *TypeParam) Type {
+ // If the type parameter embeds a single, possibly named
+ // type, use that one instead of the core type (which is
+ // always the underlying type of that single type).
+ if single := tpar.singleType(); single != nil {
+ if debug {
+ assert(under(single) == coreType(tpar))
+ }
+ return single
+ }
+ return coreType(tpar)
+}
+
type cycleFinder struct {
tparams []*TypeParam
types []Type
--- /dev/null
+// Copyright 2022 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package p
+
+func f1[M1 map[K1]int, K1 comparable](m1 M1) {}
+
+func f2[M2 map[K2]int, K2 comparable](m2 M2) {
+ f1(m2)
+}
+
+// test case from issue
+
+func Copy[MC ~map[KC]VC, KC comparable, VC any](dst, src MC) {
+ for k, v := range src {
+ dst[k] = v
+ }
+}
+
+func Merge[MM ~map[KM]VM, KM comparable, VM any](ms ...MM) MM {
+ result := MM{}
+ for _, m := range ms {
+ Copy(result, m)
+ }
+ return result
+}
import (
"bytes"
"fmt"
+ "strings"
)
// The unifier maintains two separate sets of type parameters x and y
// Whether to panic when unificationDepthLimit is reached. Turn on when
// investigating infinite recursion.
panicAtUnificationDepthLimit = false
+
+ // If enableCoreTypeUnification is set, unification will consider
+ // the core types, if any, of non-local (unbound) type parameters.
+ enableCoreTypeUnification = true
+
+ // If traceInference is set, unification will print a trace of its operation.
+ // Interpretation of trace:
+ // x ≡ y attempt to unify types x and y
+ // p ➞ y type parameter p is set to type y (p is inferred to be y)
+ // p ⇄ q type parameters p and q match (p is inferred to be q and vice versa)
+ // x ≢ y types x and y cannot be unified
+ // [p, q, ...] ➞ [x, y, ...] mapping from type parameters to types
+ traceInference = false
)
// A unifier maintains the current type parameters for x and y
// exactly. If exact is not set, a named type's underlying type
// is considered if unification would fail otherwise, and the
// direction of channels is ignored.
+// TODO(gri) exact is not set anymore by a caller. Consider removing it.
func newUnifier(exact bool) *unifier {
u := &unifier{exact: exact}
u.x.unifier = u
return u.nify(x, y, nil)
}
+func (u *unifier) tracef(format string, args ...interface{}) {
+ fmt.Println(strings.Repeat(". ", u.depth) + sprintf(nil, nil, true, format, args...))
+}
+
// A tparamsList describes a list of type parameters and the types inferred for them.
type tparamsList struct {
unifier *unifier
// If both type parameters already have a type associated with them and they are
// not joined, join fails and returns false.
func (u *unifier) join(i, j int) bool {
+ if traceInference {
+ u.tracef("%s ⇄ %s", u.x.tparams[i], u.y.tparams[j])
+ }
ti := u.x.indices[i]
tj := u.y.indices[j]
switch {
func (d *tparamsList) set(i int, typ Type) {
assert(typ != nil)
u := d.unifier
+ if traceInference {
+ u.tracef("%s ➞ %s", d.tparams[i], typ)
+ }
switch ti := d.indices[i]; {
case ti < 0:
u.types[-ti-1] = typ
// adapted version of Checker.identical. For changes to that
// code the corresponding changes should be made here.
// Must not be called directly from outside the unifier.
-func (u *unifier) nify(x, y Type, p *ifacePair) bool {
+func (u *unifier) nify(x, y Type, p *ifacePair) (result bool) {
+ if traceInference {
+ u.tracef("%s ≡ %s", x, y)
+ }
+
// Stop gap for cases where unification fails.
if u.depth >= unificationDepthLimit {
+ if traceInference {
+ u.tracef("depth %d >= %d", u.depth, unificationDepthLimit)
+ }
if panicAtUnificationDepthLimit {
panic("unification reached recursion depth limit")
}
u.depth++
defer func() {
u.depth--
+ if traceInference && !result {
+ u.tracef("%s ≢ %s", x, y)
+ }
}()
if !u.exact {
// (We use !hasName to exclude any type with a name, including
// basic types and type parameters; the rest are unamed types.)
if nx, _ := x.(*Named); nx != nil && !hasName(y) {
+ if traceInference {
+ u.tracef("under %s ≡ %s", nx, y)
+ }
return u.nify(nx.under(), y, p)
} else if ny, _ := y.(*Named); ny != nil && !hasName(x) {
+ if traceInference {
+ u.tracef("%s ≡ under %s", x, ny)
+ }
return u.nify(x, ny.under(), p)
}
}
return true
}
+ // If we get here and x or y is a type parameter, they are type parameters
+ // from outside our declaration list. Try to unify their core types, if any
+ // (see issue #50755 for a test case).
+ if enableCoreTypeUnification && !u.exact {
+ if isTypeParam(x) && !hasName(y) {
+ // When considering the type parameter for unification
+ // we look at the adjusted core type (adjCoreType).
+ // If the adjusted core type is a named type N; the
+ // corresponding core type is under(N). Since !u.exact
+ // and y doesn't have a name, unification will end up
+ // comparing under(N) to y, so we can just use the core
+ // type instead. Optimization.
+ if cx := coreType(x); cx != nil {
+ if traceInference {
+ u.tracef("core %s ≡ %s", x, y)
+ }
+ return u.nify(cx, y, p)
+ }
+ } else if isTypeParam(y) && !hasName(x) {
+ // see comment above
+ if cy := coreType(y); cy != nil {
+ if traceInference {
+ u.tracef("%s ≡ core %s", x, y)
+ }
+ return u.nify(x, cy, p)
+ }
+ }
+ }
+
// For type unification, do not shortcut (x == y) for identical
// types. Instead keep comparing them element-wise to unify the
// matching (and equal type parameter types). A simple test case
// avoid a crash in case of nil type
default:
- panic(fmt.Sprintf("### u.nify(%s, %s), u.x.tparams = %s", x, y, u.x.tparams))
+ panic(sprintf(nil, nil, true, "u.nify(%s, %s), u.x.tparams = %s", x, y, u.x.tparams))
}
return false