Open mratsim opened 4 years ago
I may have misunderstood but I thing it works with no argument but doesn't as soon as we pass an argument due to semOverloadedCall trying to resolve overloading. It manage to with no arguments but for pragma the templates are not instantiated or maybe it doesn't work that well with the procAST as argument:
I am not much familiar with custom pragmas, but if m
takes an argument, then don't you need to pass an argument to m
in push
for it to work?
for e.g., this compiles -
template m(s: string) {.pragma.}
{.push m("s").}
proc p() = discard
{.pop.}
(I am on mobile, so sorry if i missed something)
Yes you're right, my second example doesn't make sense and your fix actually compiles.
Template as pragma or macro as pragma which manipulates the proc body does the following transformation
template myPragma(a, b, c: int, procBody:untyped): untyped =
discard
proc foo(){.myPragma(1,2,3).} =
discard
is rewritten into
template myPragma(a, b, c: int, procBody:untyped): untyped =
discard
myPragma(1, 2, 3):
proc foo() =
discard
This allows a macro to manipulate the proc AST, it just needs to have "untyped" as the last parameter type. The last 2 compiles. But if I want to apply this pragma to a whole file with push/pop the compiler refuses unless I missed some hidden rules.
This would be very useful to write inspection libraries for:
where you could just add hooks at the beginning of a proc and at all the exit points in an unintrusive and maintainable way, instead of tagging everything manually and remember to tag the new procs when they are introduced, lest they don't show up in your system.
If you manually apply the custom pragma to a proc, the proc is added as an argument to the pragma automatically. so the template/macro needs to have an untyped
as the last argument. If it has zero arguments, then compiler says that the pragma is not found.
For push, we do not currently add the proc as the last argument. So instead the reverse case works.
macros/templates without any extra untyped
args work.
macros/templates with extra untyped
args do not work.
So the solution is to add the proc
as the last argument here as well and document (if not already) / give better error message that custom pragmas need to have last parameter as untyped
.
Solution: in semCustomPragma, to the nkCall we add the last node as the proc (without the pragma) itself for manually applied pragma, it is already done here - https://github.com/nim-lang/Nim/blob/56cf3403b48943fe1fbc26377669d7d69fde4878/compiler/semstmts.nim#L1426-L1430
alternative fix could be to change to order so as to apply push pragma before sem runs on proc, so that in the end both the above cases follow the same code path (which is not the case now).) but that would be a more difficult approach, and maybe not even possible
{.pragma: myPragma, inline.}
{.push myPragma.}
proc foo(x: int) =
echo "Hello there"
{.pop.}
No it doesn't. the error is now Error: recursive dependency: myPragma
I ran into this same issue, expecting push to work.
faced same problem while trying to {.push cov.} from https://github.com/yglukhov/coverage
Currently I use this hack
macro scan*(tag: int) =
let file = staticRead(tag.lineinfoObj.filename)
for node in parseStmt(file):
...
as a workaround to scan any module its called in to add code based on the module's contents. Would be great to be able to implement scan
properly if this issue gets fixed.
Like others, I expected {.push.}
to work with macro pragmas, and encountered the same problem. Also like others, I read the manual and it seems to imply pushing custom macros is possible, so I wasted a fair amount of time trying to figure out what I was missing before finding this issue and realising the manual is kind of misleading.
One of my uses is logging all parameters and return values with a proc-transforming macro. Another is making a wrapper for each function to map its parameter types for a C interface, that it also callable from Nim using Nim native types. A third is converting procs to pairs of procs that are both run and compared to do differential testing.
All these uses need to be switched on or off for different builds, so it would have been very convenient to use push under a when
guard at the start of the file. The current situation requires something like {.current_build_options.}
after every proc, where current_build_options
is a macro pragma which applies the real selection of pragmas.
I would also love to see this fixed. I'm currently using a clunky workaround with a custom push
macro.
Would like to see this fixed/implemented as well. Useful for all the reasons mentioned above - logging, benchmarks, code contracts, instrumentation, code generation, DSL, etc.
I just run into this bug as well, was going to create a new bug, so I have a simple repro case:
import macros
macro mz*(fn: untyped) =
return fn
proc foo1() {.mz.} = discard
proc bar2() {.mz.} = discard
{.push mz.}
proc foo2() = discard
proc bar2() = discard
{.pop.}
The push/pop should work, it just does not.
custompragma.nim(9, 8) Error: invalid pragma: mz
Nim Compiler Version 1.6.0 [MacOSX: amd64]
The thing is if this was implemented you would be passing a random macro to push
and the compiler would only be assuming that it's for routines, which is kind of unintuitive. But I disagree with the idea of annotating the original macro so it can be used as a pragma, so I don't know a good solution. For now a weak version of this can be:
import macros
macro mz*(fn: untyped) =
return fn
proc foo1() {.mz.} = discard
proc bar2() {.mz.} = discard
macro applyPragma(prag, st) =
if st.kind == nnkStmtList:
for s in st:
s.addPragma(prag)
else:
s.addPragma(prag)
applyPragma mz:
proc foo2() = discard
proc bar2() = discard
You can also make this work recursively if you wanted, but you would have to use typed
for more complex code which wouldn't work for some macros that require untyped
like async
.
We can annotate the original macro with smething like
macro mz*(fn: untyped{nkProcDef, nkFuncDef}) =
return fn
using the syntax for parameter constraints https://nim-lang.github.io/Nim/manual_experimental.html#term-rewriting-macros-parameter-constraints
To add to this issue,
template hey {.pragma.}
{.push hey.}
proc hello = discard # works
iterator noo: int = discard # fails
/tmp/test.nim(4, 1) template/generic instantiation from here
/tmp/test.nim(2, 8) Error: cannot attach a custom pragma to 'noo'
This is a pain point when using for instance async, since the inner iterator will get the custom pragma inferred, and will then fail to compile
The manual does an artistic dodge about the push/pop rules for custom pragma:
https://nim-lang.org/docs/manual.html#pragmas-push-and-pop-pragmas
I have found one way where it works:
[Bad example see comments below] ~but change it to:~
~and you get invalid pragma~
Change it to
from https://nim-lang.org/docs/manual.html#macros-macros-as-pragmas
And it still doesn't work. And it doesn't work with macro either.
Alternative approach
Let's thing out-of-the-box and maybe it's because templates are not well registered as pragma, does a well known pragma work?
Yes it does.
Does aliasing it work
Yes it does
Does pushing my aliased pragma work?
No it doesn't. the error is now
Error: recursive dependency: myPragma
Examples from within Nim
Looking for custom push/pop yields only 2 tests from nimpretty with a pretty sketchy syntax so I supposed it's not supposed to work: https://github.com/nim-lang/Nim/blob/5929c3da218fdecf70e11ff970e7b7cda50c9144/nimpretty/tests/exhaustive.nim#L51-L60
Trace
https://github.com/nim-lang/Nim/blob/5929c3da218fdecf70e11ff970e7b7cda50c9144/compiler/pragmas.nim#L710-L728