Propagating macros is uber difficult. This took me nearly six hours of just staring at expressions to fix. (I hope you get some use out of this @matthias314!)
Basically this completely refactors the macro propagation functionality to fix some issues. Everything is immutable so there is less spooky action at a distance. And seems like no more issues because of that more robust approach.
How it works is for any macros
@stable @macro1 @macro2 option1 option2 @macro3 ... function f(x)
x
end
It will aggregate all macros into a stack, like
@macro1
@macro2 option1 option2
@macro3
If it keeps encountering macros, it will keep accumulating them in the stack.
Once it either:
Encounters an expression that is neither a macro or a function. Then it will discard the stack, restart a new stack of macros in a new call to _stabilize_all. Then once that exits, it will send the previous stack of macros back "upwards" in the recursion, at which point the original calls of _stabilize_all can each re-paste those macros back on the code. They know they can do this because the upwards pass is not empty.
Encounters a function. Then it will consume the stack, applying all macros to both the function simulator as well as the normal function.
Note that you can always customize behavior for a particular macro with register_macro! – including having it be pasted on each function (default), only be pasted over the entire begin...end block (DontPropagateMacro), or to prevent @stable from applying at all (IncompatibleMacro).
[!WARNING]
Macros which apply to a function and do not have that function in their final argument are incompatible with @stable and may result in it breaking their behavior. This is likely to be a loud breakage but in any case just note it might cause weird bugs.
To get around this, either register_macro! on any macros which apply to a function, or (2) declare such blocks @unstable, or (3) don't apply @stable to those parts of code containing those non-standard macros.
coverage: 96.679% (-0.4%) from 97.059%
when pulling 508845db53b034747577c81a76b3b833e7b88751 on fix-macro-stacking
into 1d497fdf68196a90a799682d21041931be91f347 on main.
Propagating macros is uber difficult. This took me nearly six hours of just staring at expressions to fix. (I hope you get some use out of this @matthias314!)
Basically this completely refactors the macro propagation functionality to fix some issues. Everything is immutable so there is less spooky action at a distance. And seems like no more issues because of that more robust approach.
How it works is for any macros
It will aggregate all macros into a stack, like
If it keeps encountering macros, it will keep accumulating them in the stack.
Once it either:
Encounters an expression that is neither a macro or a function. Then it will discard the stack, restart a new stack of macros in a new call to
_stabilize_all
. Then once that exits, it will send the previous stack of macros back "upwards" in the recursion, at which point the original calls of_stabilize_all
can each re-paste those macros back on the code. They know they can do this because the upwards pass is not empty.Encounters a function. Then it will consume the stack, applying all macros to both the function simulator as well as the normal function.
Note that you can always customize behavior for a particular macro with
register_macro!
– including having it be pasted on each function (default), only be pasted over the entirebegin...end
block (DontPropagateMacro
), or to prevent@stable
from applying at all (IncompatibleMacro
).