-
Notifications
You must be signed in to change notification settings - Fork 1k
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
Inline summon generated by macro doesn't consider expanded macro scope #12359
Comments
In attempt 1, if it is
If it is Then I quit while I was ahead. |
@som-snytt If I had your consent, I would kiss you!! ❤️ Thank you so much! import scala.quoted.*
case class F[A](i: Int)
object F:
implicit def option[A](implicit f: F[A]): F[Option[A]] = F(f.i * 10)
inline def test[A]: F[Option[A]] =
${ _test[A] }
def _test[A](using Quotes, Type[A]): Expr[F[Option[A]]] =
import quotes.reflect.*
transparent inline def summonInline = '{ scala.compiletime.summonInline[F[Option[A]]] }
val result: Expr[F[Option[A]]] = '{
implicit val fa: F[A] = F(3)
$summonInline
}
// println(s"\n${result.show}\n")
result @main def run =
println("Expecting to see F(30)")
println(F.test[Int]) which does indeed print:
|
Sorry @som-snytt but I need to take that potential kiss back. It doesn't actually work. It only appeared to work because it was statically able to derive an instance before macro-expansion time and only because this is a static example for minification purposes. I'll clarify with a new snippet.... |
I've updated the examples in the main issue description. I minimised a little too much, in reality I'm working with dynamically-created implicits in scope. |
Sorry to break your heart like that 💔 but I just started reading the docs. I'm pretty sure this was the tag line for the movie "Highlander 4: The Inlining":
|
The correct way to summon this value is using - val summonInline = '{ scala.compiletime.summonInline[F[Option[A]]] }.asTerm
+ val summonInline = Expr.summon[F[Option[A]]].get.asTerm |
Oh, there is no implicit object Test:
+ implicit val fa: F[scala.Int] = F.apply[scala.Int]()
val ko = F.test[Int] |
The here is if the following inline def f() =
given Int = 3
compiletime.summonInline[Int]
def test() =
f()
// given given_Int: Int = 3
// compiletime.summonInline[Int](using ???) The real question is if this would count as a re-elaboration of the code or not. We designed the inline summoning assuming that we would find the implicit at call site. But this shows that the call site might also contain inlined |
Note that even if we can see the previously given definition we would not be able to see this one as inline def f(inline thunk: Unit) =
given Int = 3
thunk
def g = summonInline[Int]
def test() = f { g } |
Also, |
It seems that the current semantics are correct and we cannot change them without breaking something.
|
Note that we can inject a given definition if we combine transparent and non-transparent definitions transparent inline def f(inline thunk: Unit) =
given Int = 3
thunk
inline def g = compiletime.summonInline[Int]
def test() = f { g } |
This means that we can, with some ugly hacks get some kind of import scala.quoted.*
case class F[A]()
object F:
implicit def option[A: F]: F[Option[A]] = F()
transparent inline def test[A]: F[Option[A]] =
${ _test[A] }
+ inline def delayedSummonInline[T] = scala.compiletime.summonInline[T]
def _test[A](using Quotes, Type[A]): Expr[F[Option[A]]] =
import quotes.reflect.*
// Dynamically created `implicit val`
val faSym = Symbol.newVal(Symbol.spliceOwner, "fa", TypeRepr.of[F[A]], Flags.Implicit, Symbol.noSymbol)
val faBody = '{ F[Int]() }.asTerm
val faDef = ValDef(faSym, Some(faBody))
- val summonInline = '{ scala.compiletime.summonInline[F[Option[A]]] }.asTerm
+ val summonInline = '{ delayedSummonInline[F[Option[A]]] }.asTerm
val result = Block(List(faDef), summonInline).asExprOf[F[Option[A]]]
println(s"\n${result.show}\n")
result But this is a quite bad pattern as it is just forcing a new implicit search for something that we already know how to build. |
Oh wow! Thanks so much @nicolasstucki ! You accidentally missed another small change that was required in that import scala.quoted.*
case class F[A]()
object F:
implicit def option[A: F]: F[Option[A]] = F()
- inline def test[A]: F[Option[A]] =
+ transparent inline def test[A]: F[Option[A]] =
${ _test[A] }
+ inline def delayedSummonInline[T] = scala.compiletime.summonInline[T]
def _test[A](using Quotes, Type[A]): Expr[F[Option[A]]] =
import quotes.reflect.*
// Dynamically created `implicit val`
val faSym = Symbol.newVal(Symbol.spliceOwner, "fa", TypeRepr.of[F[A]], Flags.Implicit, Symbol.noSymbol)
val faBody = '{ F[Int]() }.asTerm
val faDef = ValDef(faSym, Some(faBody))
- val summonInline = '{ scala.compiletime.summonInline[F[Option[A]]] }.asTerm
+ val summonInline = '{ delayedSummonInline[F[Option[A]]] }.asTerm
val result = Block(List(faDef), summonInline).asExprOf[F[Option[A]]]
println(s"\n${result.show}\n")
result |
If you know exactly what you need, then yes 100% this is a bad pattern, I agree with you. The problem is that there are cases where you not only do you know what what's required, but the effort to work out if something is required and then to try to recursively populate it with it's required implicits, especially when you're generating implicits that can (according to user-space rules and not the rules of the macro) have mutual/recursive implicit dependencies, trying to calculate all of that effort 100% statically is equivalent to just implementing the implicit search algorithm. Using this pattern I can just generate all of the expected implicits and if the user has done everything right on their side, then everything should work out at expansion time. If they haven't, then they'll get a nice implicit-not-found error after my macro expands which is perfect for both users, and me as the macro author. |
Compiler version
3.0.0-RC3
Attempt 1:
summonInline
Minimized code
a:scala
:b:scala
:Output
Attempt 2:
inline
call toImplicits.search
Minimized code
a:scala
:b:scala
:Output
Expectation
Neither macro compiles, however in both cases if I copy-paste the output of
.show
, the code compiles ok.Implicit search should consider sibling implicits in local scope, just like normal code.
The text was updated successfully, but these errors were encountered: