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?

YuliaSp
  • 53
  • 3

2 Answers2

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)

Dmytro Mitin
  • 48,194
  • 3
  • 28
  • 66
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.

Mateusz Kubuszok
  • 24,995
  • 4
  • 42
  • 64
  • 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 – YuliaSp Nov 26 '21 at 10:11
  • 1
    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. – Mateusz Kubuszok Nov 26 '21 at 12:10
  • 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`. – Mateusz Kubuszok Nov 26 '21 at 12:20
  • 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. – YuliaSp Nov 26 '21 at 16:59
  • Yes, it was a conscious decision otherwise you could run into cyclical dependencies: you could have to resolve a macro to find a type which relies on a macro etc. And in non-cyclical cases you'd have to resolve the order in which types would be deduced - it is already complex and macros additionally increase that complexity. So some cut was needed and language authors decided that in cases like yours not tracing external identifiers' trees is a reasonable tradeoff. – Mateusz Kubuszok Nov 26 '21 at 17:17
  • I see. Thanks for your explanations and your time :) – YuliaSp Nov 26 '21 at 18:33
  • 1
    @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](https://stackoverflow.com/questions/63132189) [2](https://stackoverflow.com/questions/13767582) [3](https://stackoverflow.com/questions/60806283) [4](https://stackoverflow.com/questions/59387459)) 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. – Dmytro Mitin Sep 07 '22 at 07:47