-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Scala 3 Macros: Possibly missing information in the AST for inlined lambda functions #22165
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
Maybe it's just a "printer" problem, as it looks more reasonable under
or wait it only looks reasonable. There is a dangling
I can't see the Scala 3 forest for the trees. Or is it a thicket? |
Interesting! I didn't know you could do that. 💪 What's cool about that is that you do actually get the definition of However, fun as that is, if I understand what's happening here(?), we're getting the entire AST tree by running that command, but in my example I only get to see the sub-section relevant to my macro in which the definition of |
Running the example with DefDef($anonfun,
List(List(
ValDef(env, TypeTree( | scala.this.Float),
Thicket(List() | <notype>) | (env : scala.this.Float))
)),
TypeTree( | <empty>.this.D),
Block(
List(
ValDef(y, TypeTree( | scala.this.Float),
Literal(2.0f | (2.0f : scala.this.Float)) |
(y : scala.this.Float))
),
Apply(
Select(Ident(D | <empty>.this.D.type), apply |
(<empty>.this.D.apply :
(x: <root>.this.scala.Float, c: <empty>.this.C):
<empty>.this.D
)
),
List(Ident(env | (env : scala.this.Float)),
Inlined(
Ident(f |
(<empty>.this.bbb$package.f :
=>
Function1[<root>.this.scala.Float,
<empty>.this.C]
)
),
List(),
Block(List(),
Apply(
Select(Ident(C | <empty>.this.C.type), apply |
(<empty>.this.C.apply :
(x: <root>.this.scala.Float, y:
<root>.this.scala.Float): <empty>.this.C
)
),
List(Literal(0.0f | (0.0f : scala.this.Float)),
Ident(foo | (y : scala.this.Float))) |
<empty>.this.C)
| <empty>.this.C)
| <empty>.this.C)
) | <empty>.this.D)
| <empty>.this.D)
| ($anonfun : (env: scala.this.Float): <empty>.this.D)) In the |
Thank you for looking into it @jchyb. I shall check out the I've mostly just worked out how to do - erm... whatever I can do with scala 3 macros - by trial and error. So I'm confident there are lots of branches I've missed, don't know about, or just don't understand yet. For example, I didn't know about the scala-cli command you've both used there. That seems to work nicely on standalone code, but my next question would be: Is there a way to run that one a piece of code in a larger code base? Or is the pretty printer I'm using the next best thing, or is there something better? Thanks again! |
It does feel like this (well, this sort of thing) is where most of my problems stem from. I'm trying to use inline macros to write a transpiler, and instead of being able to work with the AST that represent the code that is there, I'm dealing with an AST that has already been partially inlined/optimised, and I've lost information. Do Scala 3 macro annotations give you a non-inlined tree, do you happen to know? Or is it all hanging off the same underlying mechanism? Is it worth the hassle of moving from inline macros to annotation based macros, or even a compiler plugin? |
Answering my own quesiton: It looks to me like macro annotations are run before inlining. So when attempting to write a transpiler, this seems to leave me with 3 options with their pros and cons:
Macro annotations would be the best solution, but being able to reuse code is kinda the whole point of the library (because code reuse in WebGL / GLSL 300 is difficult), and if we can't share code that I think I'm left with the current option, because I don't want to transform whole programs. For the avoidance of doubt, here's what you get if you print the tree for this: case class C(x: Float, y: Float)
case class D(x: Float, c: C)
inline def f: Float => C =
foo => C(0f, foo)
@DebugAST2
class Test:
def g =
List(3.14f).map: env =>
val y = 2.0f
D(env, f(y)) Notice that all is good but there is no trace of the implementation of ClassDef(
"Test",
DefDef("<init>", List(TermParamClause(Nil)), Inferred(), None),
List(Apply(Select(New(Inferred()), "<init>"), Nil)),
None,
List(
DefDef(
"g",
Nil,
Inferred(),
Some(
Apply(
TypeApply(
Select(
Apply(
TypeApply(Select(Ident("List"), "apply"), List(Inferred())),
List(Typed(Repeated(List(Literal(FloatConstant(3.14f))), Inferred()), Inferred()))
),
"map"
),
List(Inferred())
),
List(
Block(
List(
DefDef(
"$anonfun",
List(TermParamClause(List(ValDef("env", Inferred(), None)))),
Inferred(),
Some(
Block(
List(ValDef("y", Inferred(), Some(Literal(FloatConstant(2.0f))))),
Apply(
Select(Ident("D"), "apply"),
List(Ident("env"), Apply(Select(Ident("f"), "apply"), List(Ident("y"))))
)
)
)
)
),
Closure(Ident("$anonfun"), None)
)
)
)
)
)
)
) If we move That being the case, I don't see how I could write something like this (made up, no idea if it compiles): case class C(x: Float, y: Float)
case class D(x: Float, c: C)
inline def f: Float => C =
foo => C(0f, foo)
@DebugAST2
class Test:
def g =
List(3.14f).map: env =>
val y = 2.0f
D(env, f(y))
@DebugAST2
class Test2:
def h =
List(6f).map: env =>
D(env, f(1.0f)) Where The second best - or maybe even best of all? - would be inline macros... with less aggressive inlining! ...which I think means no val inlining because of the way that function arguments end up getting handled / obliterated. I wonder if there's some workaround there where args are annotated or declared as something that doesn't end up as / being treated like a |
Possibly related: inline val one: 1.0f = 1.0f
inline def foobar(value: Float): vec2 =
vec2(value, one)
DebugAST.anyToAST(foobar(2.0f)) Now looking at that... I'm expecting a tree that sooner or later says "vec2 was applied with (2.0, 1.0)", but alas no... I've got 2.0 and Ident("one"). Any idea why Inlined(
None,
Nil,
Inlined(
Some(Apply(Ident("foobar"), List(Literal(FloatConstant(2.0f))))),
Nil,
Typed(
Apply(Select(Ident("vec2"), "apply"), List(Inlined(None, Nil, Literal(FloatConstant(2.0f))), Ident("one"))),
TypeIdent("vec2")
)
)
) |
Right, so with thanks to some nice people on Discord... inline vals are dealt with in a later phase so the macro doesn't get the benefit of them:
And the desired effect can be achieved with the final keyword:
|
Hi! I don't know if this is really an issue or just my lack of understanding. Any insights welcome! Thank you.
Compiler version
3.5.0, also tested on 3.5.2.
Minimized example
Kindly provided by someone on Discord, the original version is at the bottom of the issue.
Output
Expectation
In the original code,
y
is passed as an argument tof(..)
.f
is a lambda with a named argumentfoo
.f
gets inlined, and continues to refer tofoo
in the AST, but there is no clue as to wherefoo
came from, or thaty
(which is correctly defined) should be substituted in it's place.Does that seem right? Perhaps it is use-case dependant. In my case I'm writing a transpiler, and I need to know where values come from.
More information
DebugAST
is just doingPrinter.TreeStructure.show(expr.asTerm)
behind the scenes to give me the tree. Perhaps there's a better tool?Original question posted on Discord
I have this code:
And it produces this AST (just the relevant bit, starting with
env =>
):fn1
is fine. It inlines the value and ignores the argument, but that isn't a problem.fn2
...talks about alpha, but the argument and type have vanished, and it's being called with y, but it hasn't inlined the value. So it appears broken / not wired up correctly in the AST.The text was updated successfully, but these errors were encountered: