I'm trying to associate some data to a function symbol inside said function, so that later I can retrieve this data given a function call. Here's an example:
import macros, tables, hashes
proc hash(n: NimNode): Hash = hash($n) # Make Table compile
var registry {.compiletime.}: Table[NimNode, NimNode] # Map function symbol => internal function symbol
macro register(functionSymbol, internalFunctionSymbol: typed): untyped =
registry[functionSymbol] = internalFunctionSymbol
macro inst(someCall: typed): untyped =
let functionSymbol = someCall[0]
let internalFunctionSymbol = registry[functionSymbol]
result = newCall(internalFunctionSymbol)
# So it can be used like this
proc foo(a: int) =
proc bar() =
echo "foo int"
register(foo, bar)
foo(5) # Make sure `foo` is instantiated
inst foo(5) # I expect this to be replaced with corresponding `bar()`
The example above works as expected, however it quickly gets flaky with foo overloads:
proc foo(a: string) =
proc bar() =
echo "foo string"
register(foo, bar)
foo("hi") # Make sure `foo` is instantiated
inst foo("hi") # I expect this to be replaced with corresponding `bar()`
The snippet above will fail with KeyError on registry lookup, even though there's 2 keys in the registry by this time.
It gets even worse with generics:
proc baz[T](a: T) =
proc bar() =
when T is string:
echo "baz string"
else:
echo "baz not string"
register(baz, bar)
baz("hi") # Make sure `baz[string]` is instantiated
inst baz("hi") # I expect this to be replaced with corresponding `bar()`
baz(5) # Make sure `baz[int]` is instantiated
inst baz(5) # I expect this to be replaced with corresponding `bar()`
In the snippet above the registry is populated only with one symbol, even though register is called once per every instance of baz, thus twice in total. And both inst fail on lookup, as neither baz[string] nor baz[int] match that single key in the registry.
So the question is am I wrong in my expectations and why? Or is it a bug?
OMG I think I got it now.
The problem is that register is called with a symbol that is not guaranteed to be resolved, and in case of foo(string) ends up as nnkClosedSymChoice. Thats why overloaded foo example doesn't work.
I have tweaked register to take a call instead of symbol to guarantee that the symbol is fully resolved, and suddenly it worked, even the generic example. Here's the working version:
import macros, tables, hashes
proc hash(n: NimNode): Hash =
hash($n)
var r {.compiletime.}: Table[NimNode, NimNode]
macro register(k, v: typed): untyped =
r[k[0]] = v
macro inst(c: typed): untyped =
result = newCall(r[c[0]])
proc foo(a: int) =
proc bar() =
echo "foo int"
register(foo(5), bar)
proc foo(a: string) =
proc bar() =
echo "foo string"
register(foo("hi"), bar)
proc genericfoo[T](a: T) =
proc bar() =
when T is int:
echo "generic foo int"
else:
echo "generic foo not int"
register(genericfoo(a), bar)
foo(5)
inst foo(5)
foo("i")
inst foo("i")
genericfoo(5)
inst genericfoo(5)
genericfoo("i")
inst genericfoo("i")
Now it all kinda makes sense :)