2

Suppose I have this code for extracting the code initialising a variable:

def extractBodyImpl[T: Type](expr: Expr[T])(using Quotes) =
    import quotes.reflect._
    expr.asTerm.underlyingArgument match
        case ident @ Ident(_) =>
            ident.symbol.tree match
                case ValDef(_,_,rhs) => println(rhs)
                case DefDef(_,_,_,rhs) => println(rhs)
    '{ () }

inline def extractBody[T](inline expr: T) = ${ extractBodyImpl('expr) }

When called on a variable declared in the same scope it works as desired:

@main def hello() =
  val x = 1
  extractBody(x)

prints Some(Literal(Constant(1))).

However, on a variable from outer scope, it prints None:

val x = 1
@main def hello() =
  extractBody(x)

How can I make it work in the second case?

2 Answers 2

2

In Scala 3 you just need to switch on

scalacOptions += "-Yretain-trees"

Then

val x = 1
@main def hello() =
  extractBody(x)

will print Some(Literal(Constant(1))) too.

In Scala 2 we had to use Traverser technique in order to get RHS of definition

Get an scala.MatchError: f (of class scala.reflect.internal.Trees$Ident) when providing a lambda assigned to a val

Def Macro, pass parameter from a value

Creating a method definition tree from a method symbol and a body

Scala macro how to convert a MethodSymbol to DefDef with parameter default values?

How to get the runtime value of parameter passed to a Scala macro?

Can you implement dsinfo in Scala 3? (Can Scala 3 macros get info about their context?) (Scala 3)

Sign up to request clarification or add additional context in comments.

Comments

0

You cannot do it in macro. A function which received argument might have been called from everywhere. How would static analysis would access the information only available in runtime? The only reliable solution would be to force user to expand this extractBody macro right after defining the value and passing the result in some wrapper combining both value and its origin.

7 Comments

Thanks for your reply. What information in my example is not available at compiletime? To be clear, I'm not asking for a way to extract any possible symbol, that of course would not be possible (e.g. a symbol defined outside of the codebase). Just a symbol from one scope up in my codebase
Macro (simplifying) accesses information from AST, including types and scopes. The start of a function is a horizon of what it knows: where this function is called is unknown in compile time, therefore unavailable to macro. If you do val x = 1; foo(x), foo only knows that it received some value of a known type as a parameter. Where this value come from and how it came to be - it's not known inside a function and in general cannot be known.
I believe that here, when you have x function sees it as something passed from external scope, where x could be treated e.g. as alias to _root_.x - in such case macro would only see its origin as a alias of sort, part of the context, rather than something defined locally with a Tree.
Sure, function knows nothing about the parameter, here x is part of context. It seems to me that stopping the search through whole program AST for the variable declaration is a limitation of the macros system implementation, not a limitation in principle. The macro already does a search through AST outside the directly passed AST - otherwise only extractBody({val x = 1}) would work.
@MateuszKubuszok I'm afraid your answer in not completely correct. "If you do val x = 1; foo(x), foo only knows that it received some value of a known type as a parameter" This was true in Scala 2 so we had to use the technique with Traverser (1 2 3 4) to find right hand side of definition of x. Now in Scala 3 we can not only get from a tree to its symbol but also from a symbol to its tree.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.