-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Adding implicits to expression's scope #18705
-
I'm working on a problem (Scala 3 only), where my generated code has to call library code for typeclass derivation, but this derivation needs some implicits in scope. My inline method on top of this process first dynamically constructs these dependencies, and then I'd like to turn them into givens
, to achieve something like:
given Pickler[Field1Type] = ... given Pickler[Field2Type] = ... // more givens, depending on the previous step of the process External.derive[T] // macro call
In my macro, the givens are generated as a list of expressions. Then I create the result expression with Expr.block
, but my derive
call can't see the givens. I also tried to construct a tree using Symbols, Apply, etc., and convert it to Expr afterwards, but the final effect is the same. The result expression can be printed as a code and it looks like the example shown above, which works as expected when copied and pasted in isolation.
The problem can be showcased in this minimal example:
inline def deriveCodec: Int = ${ macros.deriveCodecMacro } def deriveCodecMacro(using Quotes): Expr[Int] = Expr.summon[Int].get def testMacro(using Quotes): Expr[Unit] = Expr.block( List('{ // this list of givens was generated dynamically implicit val i: Int = 123 }), '{ println(deriveCodec) } )
Generated code printed with TreeAnsiCode
is:
{ implicit val i: scala.Int = 123 scala.Predef.println(sttp.tapir.json.pickler.macros.deriveCodec) }
and works as expected when just copied and pasted (prints 123
). However, it doesn't work when testMacro
is called - summon returns None.
(BTW I used implicit
instead of given
to make sure generated code indeed creates an implicit. For some reason using given
and printing generated code gives final lazy val given_Int: scala.Int = 123
without the given
keyword.)
I suspect that there's some limitation which doesn't allow the implicit scopes of expressions to be dynamically generated or altered in a macro, like I'm trying to. Could someone confirm this?
Beta Was this translation helpful? Give feedback.
All reactions
Replies: 1 comment 3 replies
-
Maybe something like this could help
import scala.quoted.* def givenExpression[T: Type, U: Type](e: Expr[T])(f: Expr[T ?=> U])(using Quotes): Expr[U] = '{ implicit val x: T = $e; $f(using x) } def fun(using a: Int): Int = a def test(using Quotes) = givenExpression('{23})('{ fun }) // generates: { implicit val x = 123; fun(using x) }
Beta Was this translation helpful? Give feedback.
All reactions
-
Thanks! Sounds promising, although my problem is for an arbitrary number of implicits. That would require to generate def fun(...)
as well, since we don't know the list of using
params up front. Would that be possible?
Beta Was this translation helpful? Give feedback.
All reactions
-
It does not sound easy/possible if you want to have it statically checked.
Beta Was this translation helpful? Give feedback.
All reactions
-
Is there a way to do this in a non-statically checked way?
The deriveCodecMacro
from the example is fixed, and we need to call it so that it uses "our" dynamically-generated givens/implicits. The third-party macro looks up implicits using Expr.summon
.
Beta Was this translation helpful? Give feedback.