Description
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:
macro foo(varName, varType, varValue) = discard
let b {.foo.} = 4
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:
- This feature cannot be used in let/var sections. (since they're sequential you can just break them up for this)
- This feature cannot be used for const variables. (the PR just forgot to include const variables, it's in semVarOrLet but not in semConst)
- Attaching types to the value argument in the template/macro gives weird errors. Try the snippet I posted with
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
let b {.foo: 3.} = 4 # invalid pragma: foo: "abc"
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
or const
. 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 like var 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.
# order doesn't matter
let b {.global, foo.} = 4 # invalid pragma: foo
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:
macro foo(decl) = discard
foo:
let b = 3
# equivalent to
let b {.foo.} = 3
You can add arguments like how you would to proc pragma macros like:
macro foo(arg, decl) = discard
let b {.foo: 1.} = 3 # arg is 1, decl is `let b = 3`
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:
let
a = 1
b = 2
c {.foo.} = 3
d = 4
e = 5
# becomes
let
a = 1
b = 2
foo:
let
c = 3
let
d = 4
e = 5
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:
let _ {.unpack: (a, (b, c)).} = (1, (2, 3))
echo a, b, c # 123
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
like ForLoopStmt
that had generic arguments that we could use here. I think that would be too complex though and no one really needs types here.