From 3ab6012c81a6007fc108950de6a0ae4bfc4252bd Mon Sep 17 00:00:00 2001 From: metagn Date: Fri, 25 Aug 2023 22:08:47 +0300 Subject: [PATCH] fix generic param substitution in templates (#22535) * fix generic param substitution in templates fixes #13527, fixes #17240, fixes #6340, fixes #20033, fixes #19576, fixes #19076 * fix bare except in test, test updated packages in CI (cherry picked from commit 1cc4d3f6220c5609e38258bd2c5a348e83106be4) --- compiler/sem.nim | 8 +++- compiler/semcall.nim | 7 ++- tests/template/tgenericparam.nim | 80 ++++++++++++++++++++++++++++++++ 3 files changed, 93 insertions(+), 2 deletions(-) create mode 100644 tests/template/tgenericparam.nim diff --git a/compiler/sem.nim b/compiler/sem.nim index 9fc13d6224994..8f9a88a672a78 100644 --- a/compiler/sem.nim +++ b/compiler/sem.nim @@ -511,7 +511,13 @@ proc semAfterMacroCall(c: PContext, call, macroResult: PNode, # we now know the supplied arguments var paramTypes = newIdTable() for param, value in genericParamsInMacroCall(s, call): - idTablePut(paramTypes, param.typ, value.typ) + var givenType = value.typ + # the sym nodes used for the supplied generic arguments for + # templates and macros leave type nil so regular sem can handle it + # in this case, get the type directly from the sym + if givenType == nil and value.kind == nkSym and value.sym.typ != nil: + givenType = value.sym.typ + idTablePut(paramTypes, param.typ, givenType) retType = generateTypeInstance(c, paramTypes, macroResult.info, retType) diff --git a/compiler/semcall.nim b/compiler/semcall.nim index e2844e4302c42..7bd2be8dc2606 100644 --- a/compiler/semcall.nim +++ b/compiler/semcall.nim @@ -595,7 +595,12 @@ proc semResolvedCall(c: PContext, x: TCandidate, else: x.call.add c.graph.emptyNode of skType: - x.call.add newSymNode(s, n.info) + var tn = newSymNode(s, n.info) + # this node will be used in template substitution, + # pretend this is an untyped node and let regular sem handle the type + # to prevent problems where a generic parameter is treated as a value + tn.typ = nil + x.call.add tn else: internalAssert c.config, false diff --git a/tests/template/tgenericparam.nim b/tests/template/tgenericparam.nim new file mode 100644 index 0000000000000..d33f55cf70e35 --- /dev/null +++ b/tests/template/tgenericparam.nim @@ -0,0 +1,80 @@ +block: # basic template generic parameter substitution + block: # issue #13527 + template typeNameTempl[T](a: T): string = $T + proc typeNameProc[T](a: T): string = $T + doAssert typeNameTempl(1) == typeNameProc(1) + doAssert typeNameTempl(true) == typeNameProc(true) + doAssert typeNameTempl(1.0) == typeNameProc(1.0) + doAssert typeNameTempl(1u8) == typeNameProc(1u8) + + template isDefault[T](a: T): bool = a == default(T) + doAssert isDefault(0.0) + + block: # issue #17240 + func to(c: int, t: typedesc[float]): t = discard + template converted[I, T](i: seq[I], t: typedesc[T]): seq[T] = + var result = newSeq[T](2) + result[0] = i[0].to(T) + result + doAssert newSeq[int](3).converted(float) == @[0.0, 0.0] + + block: # issue #6340 + type A[T] = object + v: T + proc foo(x: int): string = "int" + proc foo(x: typedesc[int]): string = "typedesc[int]" + template fooT(x: int): string = "int" + template fooT(x: typedesc[int]): string = "typedesc[int]" + proc foo[T](x: A[T]): (string, string) = + (foo(T), fooT(T)) + template fooT[T](x: A[T]): (string, string) = + (foo(T), fooT(T)) + var x: A[int] + doAssert foo(x) == fooT(x) + + block: # issue #20033 + template run[T](): T = default(T) + doAssert run[int]() == 0 + +import options, tables + +block: # complex cases of above with imports + block: # issue #19576, complex case + type RegistryKey = object + key, val: string + var regKey = @[RegistryKey(key: "abc", val: "def")] + template findFirst[T](s: seq[T], pred: proc(x: T): bool): Option[T] = + var res = none(T) # important line + for x in s: + if pred(x): + res = some(x) + break + res + proc getval(searchKey: string): Option[string] = + let found = regKey.findFirst(proc (rk: RegistryKey): bool = rk.key == searchKey) + if found.isNone: none(string) + else: some(found.get().val) + doAssert getval("strange") == none(string) + doAssert getval("abc") == some("def") + block: # issue #19076 + block: # case 1 + var tested: Table[string,int] + template `[]`[V](t:Table[string,V],key:string):untyped = + $V + doAssert tested["abc"] == "int" + template `{}`[V](t:Table[string,V],key:string):untyped = + ($V, tables.`[]`(t, key)) + doAssert (try: tested{"abc"} except KeyError: ("not there", 123)) == ("not there", 123) + tables.`[]=`(tested, "abc", 456) + doAssert tested["abc"] == "int" + doAssert tested{"abc"} == ("int", 456) + block: # case 2 + type Foo[A,T] = object + t:T + proc init[A,T](f:type Foo,a:typedesc[A],t:T):Foo[A,T] = Foo[A,T](t:t) + template fromOption[A](o:Option[A]):auto = + when o.isSome: + Foo.init(A,35) + else: + Foo.init(A,"hi") + let op = fromOption(some(5))