-
Notifications
You must be signed in to change notification settings - Fork 23
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Redoing variable declaration macro pragmas #220
Comments
@timotheecour your feedback would be highly appreciated. |
Thanks for the writeup! I'm aware of those flaws and the current implementation can indeed be improved; I wrote it because it solved real use cases, and in a way that can be improved without facing backward compatibility issues. It's also so recent that I don't think anyone would mind if we replace it with an improved design, with or without a deprecation cycle. My original design intent was in fact very similar to the one you're describing (pretty much what I described in nim-lang/Nim#13830 where you replace type section by var section), but the reason I used a triplet (name,type,value) (deconstructed AST) instead of a single NimNode argument is to allow defining template pragmas, such as Your proposal (as written) also has issues
that'd be a non-starter IMO; but it's fixable, see below here's my proposal for the "best of both worlds", 0 compromise, designit's very similar to what you're describing and also to nim-lang/Nim#13830 where you replace type section with var section.
details: # can be used with var/let/const
var a {.foo(arg1, arg2), foo2.}: T = bar # type and value are optional, usual nim rules
=>
foo(arg1, arg2):
var a {.foo2. }: T = bar # gets expanded recursively if foo2 is also a var macro pragma
can use templates, not just macroswe can define var template pragmas (thus eliminating need for the triplet destructured AST in current implementation) by introducing a new nim feature template byaddr(ast) =
let tmp = addr(AstGet(ast[2]))
template AstGet(ast[0]): untyped = tmp[]
# instead of the destructed form used in current implementation:
template byaddr(lhs, typ, rhs) = # simplified from https://github.com/nim-lang/Nim/pull/13508
let tmp = addr(rhs)
template AstGet(lhs): untyped = tmp[] There are obviously many other use cases for We can keep the 1 and 3 argument forms (no clash) and deprecate the 3 argument form once minor points
not sure i understand this point; maybe post a full example? if you have |
I should have worded more clearly. What I meant by "type information" was overloading based on the type of the RHS or the type annotation of the var/let statement. template foo(varName, varType: untyped, varValue: int) =
echo "int"
template foo(varName, varType: untyped, varValue: string) =
echo "string"
let a {.foo.} = 4 # => int
let b {.foo.} = "abc" # => string This is the limitation of the design I was proposing. There's no way to overload macros based on the type of the RHS or the type annotation itself. Your example code block (the first one) is exactly the same functionality as the initial proposal. The "weird error message" is this: template foo(varName, varType: untyped, varValue: string) =
echo "works"
let a {.foo.} = 4 # => error: 'a' is undefined I know this has a simple fix, along with all the other problems I listed (const, sections), but I'm just putting it out there.
I understand your point here but I don't see a use case where Nim can't handle it without a compiler change (with either the limited cyclic import support it does have or by reorganizing |
ya that's totally fine and consistent with proc macro pragmas; semantic phase hasn't happened at that stage; so that's not a limitation.
ah ok, thanks for clarifying!
the cyclic import problem is real. system imports/includes a large number of modules and However, I've just found a clean solution to this:
# example user code / low-level library code that's usable in low-level modules
# all it needs is extract a few magics you need for your, typically those 2 cover a large number of use cases:
proc getAst(macroOrTemplate: untyped): NimNode {.magic: "ExpandToAst", noSideEffect.}
proc `[]`(n: NimNode, i: int): NimNode {.magic: "NChild", noSideEffect.}
macro byaddr2*(a): untyped =
template impl(lhs, typ, rhs) =
let tmp = addr(rhs)
template lhs: untyped = tmp[]
let a1 = a[0]
getAst(impl(a1[0], a1[1], a1[2]))
# (other useful examples would be a `dbg` macro that would be usable in low level modules) Given this works, I'm happy to drop support for the triplet (lhs,typ,rhs) and have it replaced it with the single AST node form. Now it's just a matter of implementing it... |
fix nim-lang#15920, close nim-lang#18212, close nim-lang#14781, close nim-lang#6696, close nim-lang/RFCs#220 Variable macro pragmas have been changed to only take a unary section node. They can now also be applied in sections with multiple variables, as well as `const` sections. They also accept arguments. Templates now support macro pragmas, mirroring other routine types. Type and variable macro pragmas have been made experimental. Symbols without parentheses instatiating nullary macros or templates has also been documented in the experimental manual. A check for a redefinition error based on the left hand side of variable definitions when using variable macro pragmas was disabled. This nerfs `byaddr` specifically, however this has been documented as a consequence of the experimental features `byaddr` uses. Given how simple these changes are I'm worried if I'm missing something.
* New/better macro pragmas, make some experimental fix #15920, close #18212, close #14781, close #6696, close nim-lang/RFCs#220 Variable macro pragmas have been changed to only take a unary section node. They can now also be applied in sections with multiple variables, as well as `const` sections. They also accept arguments. Templates now support macro pragmas, mirroring other routine types. Type and variable macro pragmas have been made experimental. Symbols without parentheses instatiating nullary macros or templates has also been documented in the experimental manual. A check for a redefinition error based on the left hand side of variable definitions when using variable macro pragmas was disabled. This nerfs `byaddr` specifically, however this has been documented as a consequence of the experimental features `byaddr` uses. Given how simple these changes are I'm worried if I'm missing something. * accomodate compiler boot * allow weird pragmas * add test for #10994 * remove some control flow, try remove some logic
* New/better macro pragmas, make some experimental fix nim-lang#15920, close nim-lang#18212, close nim-lang#14781, close nim-lang#6696, close nim-lang/RFCs#220 Variable macro pragmas have been changed to only take a unary section node. They can now also be applied in sections with multiple variables, as well as `const` sections. They also accept arguments. Templates now support macro pragmas, mirroring other routine types. Type and variable macro pragmas have been made experimental. Symbols without parentheses instatiating nullary macros or templates has also been documented in the experimental manual. A check for a redefinition error based on the left hand side of variable definitions when using variable macro pragmas was disabled. This nerfs `byaddr` specifically, however this has been documented as a consequence of the experimental features `byaddr` uses. Given how simple these changes are I'm worried if I'm missing something. * accomodate compiler boot * allow weird pragmas * add test for nim-lang#10994 * remove some control flow, try remove some logic
This supersedes nim-lang/Nim#6696, which didn't get moved to this repo for some reason.
nim-lang/Nim#13508 implemented the following macro syntax for variables:
This feature has a few shortcomings and seems unfinished. Before I get to the proposal I'll mention the few bugs that the current implementation has:
varValue: string
, it saysb is undefined
or something. This is fine because even proc pragma macros can't use types.Now to the design problems.
Problem 1: Additional arguments
How do you even define
foo
for this? Where is the argument supposed to go in its signature?Problem 2: var/let/const information
The current design gives no information if the variable being changed is
var
,let
orconst
. The only arguments you get are the variable name (ident or accent), variable type and the value. You could pass information through the type I guess, the parser allows having something likevar x: var T = 3
, but this still requires work on the user and bloats code.Problem 3: Incompatible with other pragmas
The current design does not allow using other pragmas. Not just other pragma macros, any pragmas.
This is disharmonious with proc pragma macros, they keep the other pragma regardless of the order, and if multiple macros are used the first one is evaluated.
I will add more problems if I find more, I think this many is enough.
Proposal
Instead of
foo(varName, varType, varValue)
, we use this:You can add arguments like how you would to proc pragma macros like:
We have the information of whether or not this is a let, var or const, so problem 2 is solved. It works slightly differently for sections:
This doesn't break any code since let/var/const sections are evaluated sequentially unlike type sections.
Here's an example of the usage of a user defined unpack macro in conjunction with this syntax:
Flaws and backwards compatibility
If you use the current variable declaration pragma macros your code will be broken, but since it's such a fragile feature in regards to bugs I don't think we have many problems.
One flaw is that there's no way to supply type information here, unless we had some kind of
VariableStmt
likeForLoopStmt
that had generic arguments that we could use here. I think that would be too complex though and no one really needs types here.The text was updated successfully, but these errors were encountered: