x.nim(9, 1) template/generic instantiation of `t` from here x.nim(7, 9) Error: undeclared identifier: 'x' The error is for bod(x) If I change the for loop variable to anything other than x, it works. If this is indeed a bug, what title should I file it under? .. code-block:: nim template t(body: untyped) = proc bod(a: int) = let x {.inject.} = a body for x in [1]: bod(x) t: echo x
Here's another variant that also fails with the same message (note that there's no error for the let statement, just the bod call):
template t(body: untyped) =
proc bod(a: int) =
let x {.inject.} = a
body
let x = 1
bod(x)
t:
echo x
If I move the let out of the template, it works:
template t(body: untyped) =
proc bod(a: int) =
let x {.inject.} = a
body
bod(x)
let x = 1
t:
echo x
You need another {.inject.} at the site of the 2nd let for the call site, i.e., inside the template:
let x {.inject.} = 1
bod(x)
Or you could mark the whole template {.dirty.}.@jibal The error you're receiving is because you've named the loop variable the same as the injected variable, which overrides the injected variable. This works:
template t(body: untyped) =
proc bod(a: int) =
let x {.inject.} = a
body
for i in [1, 2, 3, 4]:
bod(i)
t:
echo x
The error you're receiving is because you've named the loop variable the same as the injected variable, which overrides the injected variable.
Sigh. I know what I've done and what is needed to overcome it, but the message makes no sense and the error makes no sense in terms of scope. The x in the loop is simply the name of an argument that is the value of the parameter to bod() -- it has nothing at all to do with the x inside of bod(), which is in a totally different context. There's some sort of symbol table mishandling going on here.
You need another {.inject.} at the site of the 2nd let for the call site, i.e., inside the template
Why would I need that? I don't care what the variable name is.
I'm not asking how to get the code to work ... I already have a trivial fix for that, vastly superior to adding another {.inject.} or marking the template {.dirty.} -- simply change the variable name. I'm pointing out that the error doesn't make sense -- certainly the error message doesn't.
Here's another example that works, showing yet again that the error is due to mishandling of the symbol table. I'm going to go ahead and file a bug.
template z(body: untyped) =
proc bod(a: int) =
let x {.inject.} = a
body
template t(body: untyped) =
z(body)
for x in [1]:
bod(x)
t:
echo x
Sorry - I read more your 2nd whole comment than your 2nd sentence and replied too quickly. Oops.
It does indeed seem weird that for y in [1]: bod(y) works while for x in [1]: bod(x) fails.
I'd file a bug called something like "template-local symbol mishandled when shadowing a sub-proc-scope inject" or something similar.
Sigh. I know what I've done and what is needed to overcome it (I already said that it works if I change the name, so you don't need to tell me that I can change the name to make it work)
My bad, I accidentally skipped over that part of your question.
"template-local symbol mishandled when shadowing a sub-proc-scope inject" or something similar.
That's a lot more concise than my title for the bug ... but the thing is, the local symbol doesn't shadow the one in the proc (or v.v. -- symbols in inner scopes shadow symbols in outer scopes) -- the proc is declared first! (And changing the order, moving the let above the proc, doesn't change anything.) That's why I think it has something to do with popping the symbol table entry for the x in the proc somehow pops it for the x in the template. Anyway, I'll leave it up to the compiler people to figure out ... if they ever do; it's certainly lo pri, since the workaround is so simple. But it's a weird one.
Hmm ... following the above assumption, I got rid of the bod call (among other simplifications) and it still fails ... any use of a locally declared x apparently fails:
template t =
proc p =
let x {.inject.} = 1
let x = 1
x
t
Injecting both x symbols works.
template t(body: untyped) =
proc bod(a: int) =
let x {.inject.} = a
body
for x {.inject.} in [1]:
bod(x)
t:
echo x
Re: "shadow"...Eh, there are a few very specific usages of it like a local shadowing a parameter or nested scope shadowing outer scope and so on, but the word/idea generally captures the ' must both be "x" ' idea fundamental to this bug, at least to my ears/eyeballs.
Anyway your reproductions are very small/self-contained. I expect Nim core can fix it or at least produce a better error message or at the very least document the limitation (unless it already is documented).
@cblake As I've explained, this has nothing to do with shadowing, which only has to do with scope (https://en.wikipedia.org/wiki/Variable_shadowing).
What's actually going on is that, once an inject is done in a proc in a template, any (textually) later uses of that symbol in that template are taken to be injected, even when in the scope of a gensym declaration. So in for x in [1]: echo x, the first x is declared as gensym but the second one is marked as injected so it produces an "undeclared" error since it has a different name.
The bug is filed as https://github.com/nim-lang/Nim/issues/14620, which is a dup of https://github.com/nim-lang/Nim/issues/10609 (thanks, Danil Yarantsev, for discovering that.)
@Araq This bug is rather inconsequential and trivial to work around ... I'm certainly not waiting on a fix.