mirror of
https://github.com/nim-lang/Nim.git
synced 2026-04-21 14:55:24 +00:00
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:
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
37
tests/converter/tgenericconverterbindings1.nim
Normal file
37
tests/converter/tgenericconverterbindings1.nim
Normal 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
|
||||
10
tests/converter/tgenericconverterbindings2.nim
Normal file
10
tests/converter/tgenericconverterbindings2.nim
Normal 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]>
|
||||
38
tests/converter/tgenericconverterbindings3.nim
Normal file
38
tests/converter/tgenericconverterbindings3.nim
Normal 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])
|
||||
19
tests/converter/tgenericconverterbindings4.nim
Normal file
19
tests/converter/tgenericconverterbindings4.nim
Normal 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
|
||||
41
tests/converter/tgenericconverterbindings5.nim
Normal file
41
tests/converter/tgenericconverterbindings5.nim
Normal 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)
|
||||
36
tests/converter/tgenericconverterbindings6.nim
Normal file
36
tests/converter/tgenericconverterbindings6.nim
Normal 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,
|
||||
)
|
||||
Reference in New Issue
Block a user