-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Proposal: Decouple Dotty macros from inlining #5122
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
Comments
The whole point of having The current scheme also ensures the correct handling of the language call semantics. No need to have special handling of macros. Another important difference is that a macro in Dotty is just the top-level splice. The definition itself is not what should be referred as the macro, it is just another normal function. The arguments that the macro recives should always retain the semantics of by value or by name parameters. This ensures that when spliced the generated code will not break the semantics. If the parameter is by name and it is used only once the original tree should be available. There are still some bugs there. To inspect a lifted argument there is also the possibility of looking at the RHS of the lifted val. Which will be possible after #4968 is merged. We are also still missing an optimizer that will remove the useless binding after macro expansion. Previously we relied of the local optimizer that we do not have anymore. We can probably reuse the same optimizer that is used when inlininig after the expansion to cleanup the useless vals. |
As a macro author why would I care about this decoupling? It seems like an implementation detail of the compiler rather than a feature. Indeed, as a macro author, I always want my macros to produce new trees at call site, at compile time. |
In my proposition, there is no such thing as inlining. There is no reason you would need to inline anything before a call to a macro.
I propose to change the semantic for macros. So if the semantic for a macro is not the language call semantic, I don't think we can say it breaks the semantic.
Sure. Is there anything that justify this difference? I propose to change this. The macro is not a "normal" function in my proposal.
Same as above. It depends how you define the semantic of a macro.
This seems like a lot of added complexity for the macros writer for something that should be straightforward. You shouldn't have to lookup a synthetic definition generated by the inliner to recover the tree you want to inspect
You are still missing the ability to elide a tree that cannot be removed by the inliner (for example, if there is a side effect). Overall I think it is very fragile for macros to takes as input a tree produced by the inliner. Even more if this inliner start performing optimisations |
Having a special semantics for macros is an language overhead that is not required. Having the same semantics simplifies the language.
The idea is to not make the language more complex with no reason.
This could be provided by a function in the library.
Side effects should never be droped. The language would be quite inconsistent if we did that.
It seems that this fragility only comes from a lack of a spec for what happens with parameters to inline parameters. Which we will perform at some point. |
As a macro author, I have to say that getting full trees is also often annoying, because then you have to manually let-bind them to local values or risk duplication and unintended semantic changes. It's all too easy (and I've done it!) to forget that a receiver tree may actually be a complicated and possibly effectful expression, as opposed to a simple stable reference (which it is in the majority of the macro's use cases, so the bug may only be discovered very late). If the macro is intended to duplicate or suppress effects, then it seems totally reasonable to me that it should specify its parameter as by-name. It's also useful as some basic documentation for users who don't actually want to go and read the implementation of the macro.
I think the problem of easily inspecting a tree's definition to inform the behavior of the macro is an orthogonal one. It would be much better if we could make this seamless; for example, have the extractors used for inspection do the legwork for us behind the scenes, of finding definitions attached to each symbol. This is how many DSL compilers work (like those based on LMS). This approach would have the huge advantage (over the scalac way) that suddenly you can also see the definition of a value that the user actually let-bound themselves (i.e., when they did not syntactically pass the full tree as an argument). |
For reference here is the old idea of inline/meta which also aimed to make Scala 2 macros preserve the call semantics (https://docs.scala-lang.org/sips/inline-meta.html#inlinemeta). In particular:
|
Generally, I like the current approach based on inlining, which is more principled and can be reasoned by programers. However, maybe macro arguments should never be lifted to avoid the repetitive I think lifting of prefix is acceptable. The first argument is to preserve semantics, as mentioned by @LPTK. The second argument is that from my experience with macros, very few macros inspect trees of the receiver, the typical ones are interpolator macros. The reason is that class member macros are supposed to work with any prefix, thus most macros don't mess with the receiver. The |
I think few macros will need
Yes, exactly.
Again, I think the criterion for using |
Thanks for reminding this use case @LPTK , then I agree with you, it's better to keep |
After discussion, the conclusion was that we would stick to the current macro scheme. However some things need to be improved:
|
More concisely:
|
Closing in favor of #5132 |
@allanrenucci I don't know if that's on the table, but can it be made possible to inspect the trees associated with val-bound symbols in general? i.e., not just those the original argument trees of those val symbols that were inserted by the inliner. Conceptually, there should be no difference between the macro user writing |
@LPTK in general it will always be possible to inspect the right hand side of any definition. The trees for the arguments that @allanrenucci refers are some other functionality that allows you to as for the tree that was originally in the call. Effectively it will only remove sintactic overhead to figure to put yourself to place yourself back into the Scala 2 mindset. As you noted, we will be able to do much more by being able to inspect trees outside the macro expansion. |
I’d rather hide or specify those. Without Inline nodes, tree visitors cannot recover source positions: they say where the containing file changes, so that trees can keep storing only positions. However, not sure that tasty-reflect should expose them. |
Currently macros in Dotty rely on inlining. A call to a macro is first inlined at call site, then the macro is evaluated. However, inlining must preserve the semantic of the program and perform unpredictable tree transformations.
Macros authors now need to understand what happens during inlining, massage the inline definition to get the trees they expect and reverse some transformation performed by the inliner (e.g. deal with
Inline
trees).Let's look through an example that illustrate my point. We would like to implement a macro that can inspect its receiver and its arguments.
And a simple use case:
Here is what we get after inlining:
The inliner lifted out the receiver and the argument of the call to the macro and only give us a reference to them. To workaround this issue, one needs to massage the macro definition a lot in order to get the expected trees:
Note: This can possibly improve with extension methods
And here is what we get after inlining:
Our macro can now inspect inspect the trees of its receiver and its arguments. This is a lot of ceremony for something that is straightforward in
scalac
and I believe a common use case of macros.I propose to decouple inlining from macros and re-use the semantic we have in
scalac
:For a call
receiver.myMacro(args)
, ifmyMacro
is a macro, the compiler will expand that application by invoking the corresponding macro implementation method, with the abstract-syntax trees of the argument expressionsreceiver
andargs
as arguments.One could imagine the syntax being something like (this is not a proposal about the syntax):
@xeno-by, @liufengyun, @olafurpg, @nicolasstucki I would like to here your opinion and know if there are any concerns or drawbacks about going back to the Scala 2 semantic. I also discussed this with @sjrd and @OlivierBlanvillain.
The text was updated successfully, but these errors were encountered: