// Function parameters are always typed. Arguments may be untyped.
// Collect the indices of untyped arguments and handle them later.
if isTyped(arg.typ) {
- if !u.unify(par.typ, arg.typ, 0) {
+ if !u.unify(par.typ, arg.typ, assign) {
errorf("type", par.typ, arg.typ, arg)
return nil
}
arg := args[i]
typ := Default(arg.typ)
assert(isTyped(typ))
- if !u.unify(tpar, typ, 0) {
+ if !u.unify(tpar, typ, assign) {
errorf("default type", tpar, typ, arg)
return nil
}
// Whether to panic when unificationDepthLimit is reached.
// If disabled, a recursion depth overflow results in a (quiet)
// unification failure.
- panicAtUnificationDepthLimit = false // go.dev/issue/59740
+ panicAtUnificationDepthLimit = true
// If enableCoreTypeUnification is set, unification will consider
// the core types, if any, of non-local (unbound) type parameters.
// unifyMode controls the behavior of the unifier.
type unifyMode uint
+const (
+ // If assign is set, we are unifying types involved in an assignment:
+ // they may match inexactly at the top, but element types must match
+ // exactly.
+ assign unifyMode = 1 << iota
+
+ // If exact is set, types unify if they are identical (or can be
+ // made identical with suitable arguments for type parameters).
+ // Otherwise, a named type and a type literal unify if their
+ // underlying types unify, channel directions are ignored, and
+ // if there is an interface, the other type must implement the
+ // interface.
+ exact
+)
+
// unify attempts to unify x and y and reports whether it succeeded.
// As a side-effect, types may be inferred for type parameters.
+// The mode parameter controls how types are compared.
func (u *unifier) unify(x, y Type, mode unifyMode) bool {
return u.nify(x, y, mode, nil)
}
}
// Unification will fail if we match a defined type against a type literal.
- // Per the (spec) assignment rules, assignments of values to variables with
+ // If we are matching types in an assignment, at the top-level, types with
// the same type structure are permitted as long as at least one of them
// is not a defined type. To accommodate for that possibility, we continue
// unification with the underlying type of a defined type if the other type
- // is a type literal.
+ // is a type literal. This is controlled by the exact unification mode.
// We also continue if the other type is a basic type because basic types
// are valid underlying types and may appear as core types of type constraints.
// If we exclude them, inferred defined types for type parameters may not
// we will fail at function instantiation or argument assignment time.
//
// If we have at least one defined type, there is one in y.
- if ny, _ := y.(*Named); ny != nil && isTypeLit(x) && !(enableInterfaceInference && IsInterface(x)) {
+ if ny, _ := y.(*Named); mode&exact == 0 && ny != nil && isTypeLit(x) && !(enableInterfaceInference && IsInterface(x)) {
if traceInference {
u.tracef("%s ≡ under %s", x, ny)
}
}
// Type elements (array, slice, etc. elements) use emode for unification.
+ // Element types must match exactly if the types are used in an assignment.
emode := mode
+ if mode&assign != 0 {
+ emode |= exact
+ }
// If EnableInterfaceInference is set and both types are interfaces, one
// interface must have a subset of the methods of the other and corresponding
}
case *Chan:
- // Two channel types unify if their value types unify.
+ // Two channel types unify if their value types unify
+ // and if they have the same direction.
+ // The channel direction is ignored for inexact unification.
if y, ok := y.(*Chan); ok {
- return u.nify(x.elem, y.elem, emode, p)
+ return (mode&exact == 0 || x.dir == y.dir) && u.nify(x.elem, y.elem, emode, p)
}
case *Named:
// If one or both named types are interfaces, the types unify if the
// respective methods unify (per the rules for interface unification).
if y, ok := y.(*Named); ok {
- if enableInterfaceInference {
+ if enableInterfaceInference && mode&exact == 0 {
xi, _ := x.under().(*Interface)
yi, _ := y.under().(*Interface)
// If one or both of x and y are interfaces, use interface unification.
// Function parameters are always typed. Arguments may be untyped.
// Collect the indices of untyped arguments and handle them later.
if isTyped(arg.typ) {
- if !u.unify(par.typ, arg.typ, 0) {
+ if !u.unify(par.typ, arg.typ, assign) {
errorf("type", par.typ, arg.typ, arg)
return nil
}
arg := args[i]
typ := Default(arg.typ)
assert(isTyped(typ))
- if !u.unify(tpar, typ, 0) {
+ if !u.unify(tpar, typ, assign) {
errorf("default type", tpar, typ, arg)
return nil
}
// Whether to panic when unificationDepthLimit is reached.
// If disabled, a recursion depth overflow results in a (quiet)
// unification failure.
- panicAtUnificationDepthLimit = false // go.dev/issue/59740
+ panicAtUnificationDepthLimit = true
// If enableCoreTypeUnification is set, unification will consider
// the core types, if any, of non-local (unbound) type parameters.
// unifyMode controls the behavior of the unifier.
type unifyMode uint
+const (
+ // If assign is set, we are unifying types involved in an assignment:
+ // they may match inexactly at the top, but element types must match
+ // exactly.
+ assign unifyMode = 1 << iota
+
+ // If exact is set, types unify if they are identical (or can be
+ // made identical with suitable arguments for type parameters).
+ // Otherwise, a named type and a type literal unify if their
+ // underlying types unify, channel directions are ignored, and
+ // if there is an interface, the other type must implement the
+ // interface.
+ exact
+)
+
// unify attempts to unify x and y and reports whether it succeeded.
// As a side-effect, types may be inferred for type parameters.
+// The mode parameter controls how types are compared.
func (u *unifier) unify(x, y Type, mode unifyMode) bool {
return u.nify(x, y, mode, nil)
}
}
// Unification will fail if we match a defined type against a type literal.
- // Per the (spec) assignment rules, assignments of values to variables with
+ // If we are matching types in an assignment, at the top-level, types with
// the same type structure are permitted as long as at least one of them
// is not a defined type. To accommodate for that possibility, we continue
// unification with the underlying type of a defined type if the other type
- // is a type literal.
+ // is a type literal. This is controlled by the exact unification mode.
// We also continue if the other type is a basic type because basic types
// are valid underlying types and may appear as core types of type constraints.
// If we exclude them, inferred defined types for type parameters may not
// we will fail at function instantiation or argument assignment time.
//
// If we have at least one defined type, there is one in y.
- if ny, _ := y.(*Named); ny != nil && isTypeLit(x) && !(enableInterfaceInference && IsInterface(x)) {
+ if ny, _ := y.(*Named); mode&exact == 0 && ny != nil && isTypeLit(x) && !(enableInterfaceInference && IsInterface(x)) {
if traceInference {
u.tracef("%s ≡ under %s", x, ny)
}
}
// Type elements (array, slice, etc. elements) use emode for unification.
+ // Element types must match exactly if the types are used in an assignment.
emode := mode
+ if mode&assign != 0 {
+ emode |= exact
+ }
// If EnableInterfaceInference is set and both types are interfaces, one
// interface must have a subset of the methods of the other and corresponding
}
case *Chan:
- // Two channel types unify if their value types unify.
+ // Two channel types unify if their value types unify
+ // and if they have the same direction.
+ // The channel direction is ignored for inexact unification.
if y, ok := y.(*Chan); ok {
- return u.nify(x.elem, y.elem, emode, p)
+ return (mode&exact == 0 || x.dir == y.dir) && u.nify(x.elem, y.elem, emode, p)
}
case *Named:
// If one or both named types are interfaces, the types unify if the
// respective methods unify (per the rules for interface unification).
if y, ok := y.(*Named); ok {
- if enableInterfaceInference {
+ if enableInterfaceInference && mode&exact == 0 {
xi, _ := x.under().(*Interface)
yi, _ := y.under().(*Interface)
// If one or both of x and y are interfaces, use interface unification.
var send func(chan<- int)
ffboth(both)
- ffboth(recv /* ERROR "cannot use" */ )
- ffboth(send /* ERROR "cannot use" */ )
+ ffboth(recv /* ERROR "does not match" */ )
+ ffboth(send /* ERROR "does not match" */ )
- ffrecv(both /* ERROR "cannot use" */ )
+ ffrecv(both /* ERROR "does not match" */ )
ffrecv(recv)
- ffrecv(send /* ERROR "cannot use" */ )
+ ffrecv(send /* ERROR "does not match" */ )
- ffsend(both /* ERROR "cannot use" */ )
- ffsend(recv /* ERROR "cannot use" */ )
+ ffsend(both /* ERROR "does not match" */ )
+ ffsend(recv /* ERROR "does not match" */ )
ffsend(send)
}
}
// This is similar to the first example but here T1 is a component
-// of a func type. In this case we should be able to infer a type
-// argument for P because component types must be identical even
-// in the case of interfaces.
-// This is a short-coming of type inference at the moment, but it
-// is better to not be able to infer a type here (we can always
-// supply one), than to infer the wrong type in other cases (see
-// below). Finally, if we decide to accept go.dev/issues/8082,
-// the behavior here is correct.
+// of a func type. In this case types must match exactly: P must
+// match int.
func g5[P any](func(T1[P])) {}
func _() {
var f func(T1[int])
- g5 /* ERROR "cannot infer P" */ (f)
+ g5(f)
g5[int](f)
g5[string](f /* ERROR "cannot use f (variable of type func(T1[int])) as func(T1[string]) value in argument to g5[string]" */)
}
--- /dev/null
+// Copyright 2023 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
+
+// Simplified (representative) test case.
+
+func _() {
+ f(R1{})
+}
+
+func f[T any](R[T]) {}
+
+type R[T any] interface {
+ m(R[T])
+}
+
+type R1 struct{}
+
+func (R1) m(R[int]) {}
+
+// Test case from issue.
+
+func _() {
+ r := newTestRules()
+ NewSet(r)
+ r2 := newTestRules2()
+ NewSet(r2)
+}
+
+type Set[T any] struct {
+ rules Rules[T]
+}
+
+func NewSet[T any](rules Rules[T]) Set[T] {
+ return Set[T]{
+ rules: rules,
+ }
+}
+
+func (s Set[T]) Copy() Set[T] {
+ return NewSet(s.rules)
+}
+
+type Rules[T any] interface {
+ Hash(T) int
+ Equivalent(T, T) bool
+ SameRules(Rules[T]) bool
+}
+
+type testRules struct{}
+
+func newTestRules() Rules[int] {
+ return testRules{}
+}
+
+func (r testRules) Hash(val int) int {
+ return val % 16
+}
+
+func (r testRules) Equivalent(val1 int, val2 int) bool {
+ return val1 == val2
+}
+
+func (r testRules) SameRules(other Rules[int]) bool {
+ _, ok := other.(testRules)
+ return ok
+}
+
+type testRules2 struct{}
+
+func newTestRules2() Rules[string] {
+ return testRules2{}
+}
+
+func (r testRules2) Hash(val string) int {
+ return 16
+}
+
+func (r testRules2) Equivalent(val1 string, val2 string) bool {
+ return val1 == val2
+}
+
+func (r testRules2) SameRules(other Rules[string]) bool {
+ _, ok := other.(testRules2)
+ return ok
+}