Changing generic weight of tyGenericParam (#22143)

This is in reference to a [feature
request](https://github.com/nim-lang/Nim/issues/22142) that I posted.

I'm making this PR to demonstrate the suggested change and expect that
this should be scrutinized

---------

Co-authored-by: Bung <crc32@qq.com>
Co-authored-by: Andreas Rumpf <rumpf_a@web.de>
(cherry picked from commit 74fa8ed59a)
This commit is contained in:
Ryan McConnell
2024-01-05 08:42:21 +00:00
committed by narimiran
parent ff46fcfd24
commit 3c66401dee
11 changed files with 241 additions and 88 deletions

View File

@@ -213,16 +213,17 @@ proc sumGeneric(t: PType): int =
case t.kind
of tyAlias, tySink, tyNot: t = t.lastSon
of tyArray, tyRef, tyPtr, tyDistinct, tyUncheckedArray,
tyOpenArray, tyVarargs, tySet, tyRange, tySequence, tyGenericBody,
tyOpenArray, tyVarargs, tySet, tyRange, tySequence,
tyLent, tyOwned, tyVar:
t = t.lastSon
inc result
of tyBool, tyChar, tyEnum, tyObject, tyPointer, tyVoid,
tyString, tyCstring, tyInt..tyInt64, tyFloat..tyFloat128,
tyUInt..tyUInt64, tyCompositeTypeClass, tyBuiltInTypeClass,
tyGenericParam:
tyUInt..tyUInt64, tyCompositeTypeClass, tyBuiltInTypeClass:
inc result
break
of tyGenericBody:
t = t.typeBodyImpl
of tyGenericInst, tyStatic:
t = t[0]
inc result
@@ -237,6 +238,12 @@ proc sumGeneric(t: PType): int =
t = t.lastSon
if t.kind == tyEmpty: break
inc result
of tyGenericParam:
if t.len > 0:
t = t.skipModifier
else:
inc result
break
of tyUntyped, tyTyped: break
of tyGenericInvocation, tyTuple, tyProc, tyAnd:
result += ord(t.kind == tyAnd)
@@ -466,34 +473,40 @@ proc handleFloatRange(f, a: PType): TTypeRelation =
else: result = isIntConv
else: result = isNone
proc getObjectType(f: PType): PType =
proc reduceToBase(f: PType): PType =
#[
Returns a type that is f's effective typeclass. This is usually just one level deeper
in the hierarchy of generality for a type. `object`, `ref object`, `enum` and user defined
tyObjects are common return values.
Returns the lowest order (most general) type that that is compatible with the input.
E.g.
A[T] = ptr object ... A -> ptr object
A[N: static[int]] = array[N, int] ... A -> array
]#
case f.kind:
of tyGenericParam:
if f.len <= 0 or f.skipModifier == nil:
result = f
else:
result = reduceToBase(f.skipModifier)
of tyGenericInvocation:
result = getObjectType(f[0])
result = reduceToBase(f.baseClass)
of tyCompositeTypeClass, tyAlias:
if f.len <= 0 or f[0] == nil:
result = f
else:
result = getObjectType(f[0])
of tyGenericBody, tyGenericInst:
result = getObjectType(f.lastSon)
result = reduceToBase(f.elementType)
of tyGenericInst:
result = reduceToBase(f.skipModifier)
of tyGenericBody:
result = reduceToBase(f.typeBodyImpl)
of tyUserTypeClass:
if f.isResolvedUserTypeClass:
result = f.base # ?? idk if this is right
else:
result = f.lastSon
of tyStatic, tyOwned, tyVar, tyLent, tySink:
result = getObjectType(f.base)
result = reduceToBase(f.base)
of tyInferred:
# This is not true "After a candidate type is selected"
result = getObjectType(f.base)
of tyTyped, tyUntyped, tyFromExpr:
result = f
result = reduceToBase(f.base)
of tyRange:
result = f.lastSon
else:
@@ -1269,7 +1282,7 @@ proc typeRel(c: var TCandidate, f, aOrig: PType,
result = typeRel(c, f.base, aOrig, flags + {trNoCovariance})
subtypeCheck()
of tyArray:
a = getObjectType(a)
a = reduceToBase(a)
case a.kind
of tyArray:
var fRange = f[0]
@@ -1395,7 +1408,7 @@ proc typeRel(c: var TCandidate, f, aOrig: PType,
let effectiveArgType = if useTypeLoweringRuleInTypeClass:
a
else:
getObjectType(a)
reduceToBase(a)
if effectiveArgType.kind == tyObject:
if sameObjectTypes(f, effectiveArgType):
c.inheritancePenalty = if tfFinal in f.flags: -1 else: 0
@@ -1426,7 +1439,7 @@ proc typeRel(c: var TCandidate, f, aOrig: PType,
result = isNone
of tyPtr, tyRef:
a = getObjectType(a)
a = reduceToBase(a)
if a.kind == f.kind:
# ptr[R, T] can be passed to ptr[T], but not the other way round:
if a.len < f.len: return isNone
@@ -1728,7 +1741,7 @@ proc typeRel(c: var TCandidate, f, aOrig: PType,
considerPreviousT:
let target = f[0]
let targetKind = target.kind
var effectiveArgType = getObjectType(a)
var effectiveArgType = reduceToBase(a)
effectiveArgType = effectiveArgType.skipTypes({tyBuiltInTypeClass})
if targetKind == effectiveArgType.kind:
if effectiveArgType.isEmptyContainer:
@@ -1846,10 +1859,11 @@ proc typeRel(c: var TCandidate, f, aOrig: PType,
a.sym.transitionGenericParamToType()
a.flags.excl tfWildcard
elif doBind:
# The mechanics of `doBind` being a flag that also denotes sig cmp via
# negation is potentially problematic. `IsNone` is appropriate for
# preventing illegal bindings, but it is not necessarily appropriate
# before the bindings have been finalized.
# careful: `trDontDont` (set by `checkGeneric`) is not always respected in this call graph.
# typRel having two different modes (binding and non-binding) can make things harder to
# reason about and maintain. Refactoring typeRel to not be responsible for setting, or
# at least validating, bindings can have multiple benefits. This is debatable. I'm not 100% sure.
# A design that allows a proper complexity analysis of types like `tyOr` would be ideal.
concrete = concreteType(c, a, f)
if concrete == nil:
return isNone
@@ -2401,9 +2415,9 @@ proc paramTypesMatch*(m: var TCandidate, f, a: PType,
# roll back the side effects of the unification algorithm.
let c = m.c
var
x = newCandidate(c, m.callee)
y = newCandidate(c, m.callee)
z = newCandidate(c, m.callee)
x = newCandidate(c, m.callee) # potential "best"
y = newCandidate(c, m.callee) # potential competitor with x
z = newCandidate(c, m.callee) # buffer for copies of m
x.calleeSym = m.calleeSym
y.calleeSym = m.calleeSym
z.calleeSym = m.calleeSym

View File

@@ -2634,7 +2634,9 @@ of the argument.
range.
3. Generic match: `f` is a generic type and `a` matches, for
instance `a` is `int` and `f` is a generic (constrained) parameter
type (like in `[T]` or `[T: int|char]`).
type (like in `[T]` or `[T: int|char]`). Constraints given an alias (as in `T`)
shall be used to define `f`, when `f` is composed of `T`, following simple variable
substitution.
4. Subrange or subtype match: `a` is a `range[T]` and `T`
matches `f` exactly. Or: `a` is a subtype of `f`.
5. Integral conversion match: `a` is convertible to `f` and `f` and `a`
@@ -2646,9 +2648,8 @@ of the argument.
There are two major methods of selecting the best matching candidate, namely
counting and disambiguation. Counting takes precedence to disambiguation. In counting,
each parameter is given a category and the number of parameters in each category is counted.
The categories are listed above and are in order of precedence. For example, if
a candidate with one exact match is compared to a candidate with multiple generic matches
and zero exact matches, the candidate with an exact match will win.
For example, if a candidate with one exact match is compared to a candidate with multiple
generic matches and zero exact matches, the candidate with an exact match will win.
In the following, `count(p, m)` counts the number of matches of the matching category `m`
for the routine `p`.
@@ -2668,10 +2669,12 @@ algorithm returns true:
return "ambiguous"
```
When counting is ambiguous, disambiguation begins. Parameters are iterated
by position and these parameter pairs are compared for their type relation. The general goal
of this comparison is to determine which parameter is more specific. The types considered are
not of the inputs from the callsite, but of the competing candidates' parameters.
When counting is ambiguous, disambiguation begins. Disambiguation also has two stages, first a
hierarchical type relation comparison, and if that is inconclusive, a complexity comparison.
Where counting relates the type of the operand to the formal parameter, disambiguation relates the
formal parameters with each other to find the most competitive choice.
Parameters are iterated by position and these parameter pairs are compared for their type
relation. The general goal of this comparison is to determine which parameter is least general.
Some examples:
@@ -2691,7 +2694,6 @@ Some examples:
```
If this algorithm returns "ambiguous" further disambiguation is performed:
If the argument `a` matches both the parameter type `f` of `p`
and `g` of `q` via a subtyping relation, the inheritance depth is taken
into account:
@@ -2733,6 +2735,23 @@ matches) is preferred:
gen(ri) # "ref T"
```
Type variables match
----------------------
When overload resolution is considering candidates, the type variable's definition
is not overlooked as it is used to define the formal parameter's type via variable substitution.
For example:
```nim
type A
proc p[T: A](param: T)
proc p[T: object](param: T)
```
These signatures are not ambiguous for an instance of `A` even though the formal parameters match ("T" == "T").
Instead `T` is treated as a variable in that (`T` ?= `T`) depending on the bound type of `T` at the time of
overload resolution.
Overloading based on 'var T'
--------------------------------------
@@ -5422,6 +5441,7 @@ Generics are Nim's means to parametrize procs, iterators or types with
`type parameters`:idx:. Depending on the context, the brackets are used either to
introduce type parameters or to instantiate a generic proc, iterator, or type.
The following example shows how a generic binary tree can be modeled:
```nim test = "nim c $1"

57
tests/concepts/t976.nim Normal file
View File

@@ -0,0 +1,57 @@
discard """
output: '''
Printable
'''
joinable: false
"""
#[
The converter is a proper example of a confounding variable
Moved to an isolated file
]#
type
Obj1[T] = object
v: T
converter toObj1[T](t: T): Obj1[T] =
return Obj1[T](v: t)
block t976:
type
int1 = distinct int
int2 = distinct int
int1g = concept x
x is int1
int2g = concept x
x is int2
proc take[T: int1g](value: int1) =
when T is int2:
static: error("killed in take(int1)")
proc take[T: int2g](vale: int2) =
when T is int1:
static: error("killed in take(int2)")
var i1: int1 = 1.int1
var i2: int2 = 2.int2
take[int1](i1)
take[int2](i2)
template reject(e) =
static: assert(not compiles(e))
reject take[string](i2)
reject take[int1](i2)
# bug #6249
type
Obj2 = ref object
PrintAble = concept x
$x is string
proc `$`[T](nt: Obj1[T]): string =
when T is PrintAble: result = "Printable"
else: result = "Non Printable"
echo Obj2()

View File

@@ -1,7 +1,6 @@
discard """
output: '''
20.0 USD
Printable
true
true
true
@@ -78,55 +77,6 @@ block t3414:
let s2 = s1.find(10)
type
Obj1[T] = object
v: T
converter toObj1[T](t: T): Obj1[T] =
return Obj1[T](v: t)
block t976:
type
int1 = distinct int
int2 = distinct int
int1g = concept x
x is int1
int2g = concept x
x is int2
proc take[T: int1g](value: int1) =
when T is int2:
static: error("killed in take(int1)")
proc take[T: int2g](vale: int2) =
when T is int1:
static: error("killed in take(int2)")
var i1: int1 = 1.int1
var i2: int2 = 2.int2
take[int1](i1)
take[int2](i2)
template reject(e) =
static: assert(not compiles(e))
reject take[string](i2)
reject take[int1](i2)
# bug #6249
type
Obj2 = ref object
PrintAble = concept x
$x is string
proc `$`[T](nt: Obj1[T]): string =
when T is PrintAble: result = "Printable"
else: result = "Non Printable"
echo Obj2()
block t1128:
type
TFooContainer[T] = object

View File

@@ -50,8 +50,6 @@ static:
doAssert isSameOwner(poo, dummyproc) == false
wrappedScope()
#---------------------------------------------------------------
macro check_gen_proc(ex: typed): (bool, bool) =
let lenChoice = bindsym"len"
var is_equal = false

View File

@@ -0,0 +1,10 @@
discard """
errormsg: "ambiguous call"
"""
type
A[T] = object
C = object
proc test[T: A](param: T): bool = false
proc test(param: A): bool = true
doAssert test(A[C]()) == true # previously would pass

View File

@@ -0,0 +1,12 @@
discard """
errormsg: "ambiguous call"
"""
type
A[T] = object
C = object
x:int
proc p[T: A[ptr]](x:ptr[T]):bool = false
proc p(x: ptr[A[ptr]]):bool = true
var a: A[ptr[C]]
doAssert p(a.addr) == true

View File

@@ -0,0 +1,16 @@
discard """
errormsg: "ambiguous call"
"""
#[
As of the time of writing `object` needs some special
treament in order to be considered "generic" in the right
context when used implicitly
]#
type
C = object
proc test[T: object](param: T): bool = false
proc test(param: object): bool = true
doAssert test(C()) == true # previously would pass

View File

@@ -0,0 +1,9 @@
discard """
errormsg: "ambiguous call"
"""
type C = object
proc test[T: ptr](param: var T): bool = false
proc test(param: var ptr): bool = true
var d: ptr[C]
doAssert test(d) == true # previously would pass

View File

@@ -0,0 +1,68 @@
type
A[T] = object of RootObj
B[T] = object
C = object
x:int
# change (previously true)
block:
proc test[J;H: A[J];T: B[H]](param: T): bool = false
proc test[T](param: B[T]): bool = true
doAssert test(B[A[int]]()) == false
block: # object is more specific then `T`
proc p[H:object;T:ptr[H]](param:T):bool = false
proc p[T](param:ptr[T]):bool= true
var l: ptr[C]
doAssert p(l) == false
block:
proc p[T:A[object]](param:T):bool = false
proc p[T](param: A[T]):bool= true
doAssert p(A[C]()) == false
block:
proc test[H;T: A[H]](param: T): bool = false
proc test(param: A): bool = true
doAssert test(A[C]()) == false
# change (previously ambiguous)
block:
proc p[T](a: A[T]): bool = false
proc p[T: object](a: T): bool = true
doAssert p(A[int]()) == false
block: # A is more specific than `object`
proc test[T: A](param: T): bool = false
proc test[T: object](param: T): bool = true
doAssert test(A[int]()) == false
block:
proc test[T: A](param: T): bool = false
proc test(param: object): bool = true
doAssert test(A[int]()) == false
block:
proc test[H;T: A[H]](param: T): bool = false
proc test(param: object): bool = true
doAssert test(A[C]()) == false
block:
proc test[H;T: A[B[H]]](param: T): bool = false
proc test[T: object](param: T): bool = true
doAssert test(A[B[int]]()) == false
block:
#[
This was referenced in the nim compiler source (`sumGeneric`) as a case
that was supposed to not be ambiguous, yet it was
]#
proc test[J;H:A[J]; T: A[H]](param: T): bool = false
proc test[H;T: A[H]](param: T): bool = true
doAssert test(A[A[C]]()) == false
block:
proc test[J;T:A[J]](param: A[T]): bool = false
proc test[T](param: A[T]): bool = true
doAssert test(A[A[C]]()) == false
block:
proc test[T](param: A[T]): bool = false
proc test[T: object](param: A[T]): bool = true
doAssert test(A[C]()) == true
block: #anti-regression (object is more specific then `T`)
proc test[J;T:A[J]](param: A[T]): bool = false
proc test(param: A[A[object]]): bool = true
doAssert test(A[A[C]]()) == true

View File

@@ -7,7 +7,6 @@ block: # PR #22261
proc d(x: int | D[SomeInteger]):bool= true
doAssert d(D[5]()) == false
block: # bug #8568
#[
Since PR #22261 and amendment has been made. Since D is a subset of D | E but