isolate and rematch generic converters to get bindings (#24867)

fixes #4554, fixes #10900, fixes #13843, fixes #19471, fixes #19517

Instead of matching generic converters to their arguments using the full
call match bindings, a new match is created for them (from which the
bindings are used to instantiate the converter return type). Then when
instantiating generic converters, they are matched to their argument
again to get their bindings again instead of using the call bindings.
This prevents generic converters which match more than once from
interfering with each other's bindings.
This commit is contained in:
metagn
2025-04-12 18:53:18 +03:00
committed by GitHub
parent 0cba752c8a
commit 334f96c05a
8 changed files with 190 additions and 3 deletions

View File

@@ -686,7 +686,12 @@ proc instGenericConvertersArg*(c: PContext, a: PNode, x: TCandidate) =
if a.kind == nkHiddenCallConv and a[0].kind == nkSym:
let s = a[0].sym
if s.isGenericRoutineStrict:
let finalCallee = generateInstance(c, s, x.bindings, a.info)
var src = s.typ.firstParamType
var convMatch = newCandidate(c, src)
let srca = typeRel(convMatch, src, a[1].typ)
if srca notin {isEqual, isGeneric, isSubtype}:
internalError(c.config, a.info, "generic converter failed rematch")
let finalCallee = generateInstance(c, s, convMatch.bindings, a.info)
a[0].sym = finalCallee
a[0].typ() = finalCallee.typ
#a.typ = finalCallee.typ.returnType

View File

@@ -2296,7 +2296,8 @@ proc userConvMatch(c: PContext, m: var TCandidate, f, a: PType,
# for generic type converters we need to check 'src <- a' before
# 'f <- dest' in order to not break the unification:
# see tests/tgenericconverter:
let srca = typeRel(m, src, a)
var convMatch = newCandidate(c, src)
let srca = typeRel(convMatch, src, a)
if srca notin {isEqual, isGeneric, isSubtype}: continue
# What's done below matches the logic in ``matchesAux``
@@ -2308,7 +2309,7 @@ proc userConvMatch(c: PContext, m: var TCandidate, f, a: PType,
let destIsGeneric = containsGenericType(dest)
if destIsGeneric:
dest = generateTypeInstance(c, m.bindings, arg, dest)
dest = generateTypeInstance(c, convMatch.bindings, arg, dest)
let fdest = typeRel(m, f, dest)
if fdest in {isEqual, isGeneric} and not (dest.kind == tyLent and f.kind in {tyVar}):
# can't fully mark used yet, may not be used in final call

View File

@@ -0,0 +1,37 @@
discard """
output: '''
p 1 20
p 1000 200
p 1 1
p 1000 1000
p 1 1000
p 1000 1
p 1 200
p 1000 20
'''
"""
# issue #4554
type
G[N:static[int]] = object
v: int
F[N:static[int]] = object
v: int
converter G2int[N:static[int]](x:G[N]):int = x.v
converter F2int[N:static[int]](x:F[N]):int = x.v
proc p(x,y:int) = echo "p ",x," ",y
var
g1 = G[1](v:1)
g2 = G[2](v:20)
f1 = F[1](v:1000)
f2 = F[2](v:200)
p(g1,g2) # Error: type mismatch: got (G[1], G[2])
p(f1,f2) # Error: type mismatch: got (F[1], F[2])
p(g1,g1) # compiles
p(f1,f1) # compiles
p(g1,f1) # compiles
p(f1,g1) # compiles
p(g1,f2) # compiles
p(f1,g2) # compiles

View File

@@ -0,0 +1,10 @@
# issue #4554 comment
type Obj[T] = object
b: T
converter test1[T](a: Obj[T]): T = a.b
proc doStuff(a: int, b: float) = discard
doStuff(Obj[int](b: 1), Obj[float](b: 1.2)) # Error: type mismatch: got <Obj[system.int], Obj[system.float]>

View File

@@ -0,0 +1,38 @@
# issue #10900
import std/options
type
AllTypesInModule =
bool | string | seq[int]
converter toOptional[T: AllTypesInModule](x: T): Option[T] =
some(x)
proc foo(
a: Option[bool] = none[bool](),
b: Option[string] = none[string](),
c: Option[seq[int]] = none[seq[int]]()) =
discard
# works:
foo(a = true)
foo(true)
foo(b = "asdf")
foo(c = @[1, 2, 3])
# fails:
foo(
a = true,
b = "asdf")
foo(true, "asdf")
foo(
a = true,
c = @[1, 2, 3])
foo(
b = "asdf",
c = @[1, 2, 3])
foo(
a = true,
b = "asdf",
c = @[1, 2, 3])

View File

@@ -0,0 +1,19 @@
# issue #13843
type
IdLayer {.pure, size: int.sizeof.} = enum
Core
Ui
IdScene {.pure, size: int.sizeof.} = enum
Game
Shop
SomeIds = IdLayer|IdScene
converter toint*(x: SomeIds): int = x.int
var IdGame : int = IdScene.Game #works
proc bind_scene(a, b: int) = discard
bind_scene(Core,Game) # doesn't work, type mismatch for Game, doesnt convert to int
bind_scene(Core,IdScene.Game) # doesn't work, type mismatch for Game, doesnt convert to int
bind_scene(Core,IdGame) # works

View File

@@ -0,0 +1,41 @@
discard """
output: '''
Converting (int, int) to A
Converting (int, int) to A
Checked: A
Checked: A
Checked: A
Converting (A, A) to A
Converting (int, int) to A
Checked: A
Checked: A
Checked: A
Converting (A, A) to A
Converting (A, A) to A
Checked: A
Checked: A
Checked: A
'''
"""
# issue #19471
type A = ref object
converter toA(x: tuple): A =
echo "Converting ", x.type, " to A"
A()
proc check(a: A) =
echo "Checked: ", a.type
proc mux(a: A, b: A, c: A) =
check(a)
check(b)
check(c)
let a = A()
mux(a, (0, 0), (1, 1)) # both tuples are (int, int)
mux(a, (a, a), (1, 1)) # one tuple is (A, A), another (int, int)
mux(a, (a, a), (a, a)) # both tuples are (A, A)

View File

@@ -0,0 +1,36 @@
discard """
output: '''
int | int
int | string
int | string
'''
"""
# issue #19517
type thing [T] = object
value: T
converter asValue[T](o: thing[T]): T =
o.value
proc mycall(num, num2: int) =
echo ($(num.type) & " | " & $(num2.type))
proc mycall(num: int, str: string) =
echo ($(num.type) & " | " & $(str.type))
mycall( # This call uses asValue[int] converter automatically fine
thing[int](value: 1),
thing[int](value: 42),
)
mycall( # This gives a type error as if the converter was not defined and I tried to pass in a thing directly
thing[int](value: 2),
thing[string](value: "foo"),
)
mycall( # This can be fixed by calling the converter explicitly for everything but the first use
thing[int](value: 2),
thing[string](value: "foo").asValue,
)