Closed japgolly closed 2 years ago
Oh and FYI, inspecting c.macroApplication
in a Scala 2, I correctly see Ident(n)
in all 3 cases.
There's too much macro operation for me to see whether something is wrong here. What should have been the expected code in the 2nd and 3rd case? And what would be the expectation why and where that code would be generated?
I'll try to clarify
There's too much macro operation for me to see whether something is wrong here.
The macro only does this following one line, and then prints out the output.
val callSite = self.asTerm.underlying
What should have been the expected code in the 2nd and 3rd case?
Instead of
new BUG.ApiWithMacros[scala.AnyRef]().remember(new scala.AnyRef()).remember(new scala.AnyRef())
which contains
new scala.AnyRef()
twice, it should be like the first example, like this:
new BUG.ApiWithMacros[scala.AnyRef]().remember(n).remember(n)
I've replaced new scala.AnyRef()
with n
.
And what would be the expectation why and where that code would be generated?
Sorry I don't understand this question.
I guess it's the underlying
that goes from n
to new AnyRef()
. But I think that's as specced?
In any case you are dealing here with a low-level API for term plumbing. It seems that any expectations of call-by-name vs call-by-value are up to the macro. They are not guaranteed by the API.
@nicolasstucki What is your opinion here?
Ah that's interesting. For reference, using .underlying
is the only way I could figure out how to get close to macroApplication
from Scala 2. If there's something else I should be using please let me know and I'll play around with it and see if it's still a problem
From a quick read though the issue it seems that underlying
is doing what it is supposed to do.
As I understand, the code should recover the api.remember(n).remember(n)
prefix of the test
method. If that is the case, the test method should be an extension method with an inline prefix.
extension [A](inline self: ApiWithMacros[A]) inline def test: Unit =
${ macroDefinition('self) }
I tried that out like this:
- final inline def test: Unit =
- ${ macroDefinition('this) }
}
+ extension [A](inline self: ApiWithMacros[A]) inline def test: Unit =
+ ${ macroDefinition('self) }
+
def macroDefinition[A](self: Expr[ApiWithMacros[A]])(using Quotes): Expr[Unit] = {
import quotes.reflect.*
- val callSite = self.asTerm.underlying
+ val callSite = self.asTerm
and now it works as expected
================================================================================
Demo.api.remember(Demo.n).remember(Demo.n)
Inlined(EmptyTree,List(),Apply(Select(Apply(Select(Ident(api),remember),List(Ident(n))),remember),List(Ident(n))))
================================================================================
api.remember(n).remember(n)
Inlined(EmptyTree,List(),Apply(Select(Apply(Select(Ident(api),remember),List(Ident(n))),remember),List(Ident(n))))
================================================================================
api.remember(n).remember(n)
Inlined(EmptyTree,List(),Apply(Select(Apply(Select(Ident(api),remember),List(Ident(n))),remember),List(Ident(n))))
so thank you! :tada: It's a great workaround. Now the question is should the use of an extension method be required? Shouldn't there be away to accomplish the same thing directly from the method in the trait? WDYT?
The extension method solution is by design and not a workaround.
The limitation we have is that we do not have a way to state that the this
is inline
for a particular method. Maybe we could have something like
class A:
// mimic extension method syntax to annotate the `this`
inline (inline this) def foo: Int = ...
or
class A:
@inlineThis inline def foo: Int = ...
I think steering people towards extension methods in this case is fine.
Should we move this to feature requests?
Yes, we should move it to feature requests
Understood. Thank you both for the help
Compiler version
3.1.2
Minimized code
BUG.scala
:demo.scala
:Output
Each 3-line block above prints the following at compile-time:
Expectation
The 1st result is correct; we see two
Ident(n)
clauses which is correct becausen
is being referenced by-value.The 2nd and 3rd results are incorrect. You can see that
n
has been inlined to be effectively by-name, resulting in two separatenew AnyRef
calls. Imagine thatn
is something obviously-mutable likeList.newBuilder
and you can see how disastrous changing the semantics to by-name is.