Skip to content
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

IllegalArgumentException: Could not find proxy for Lambda/ValDef combination in quotes #13922

Closed
cchantep opened this issue Nov 11, 2021 · 4 comments
Assignees
Labels
area:documentation area:metaprogramming:reflection Issues related to the quotes reflection API

Comments

@cchantep
Copy link
Contributor

Compiler version

3.1.2-RC1-bin-20211022-f7abd32-NIGHTLY-git-f7abd32

Minimized code

import scala.quoted.*

  inline def optPrettyPrinter[T]: Option[T] => Option[T] =
    ${ optPrettyPrinterImpl[T] }

  private def optPrettyPrinterImpl[T: Type](
      using
      q: Quotes
    ): Expr[Option[T] => Option[T]] = {
    import q.reflect.*

    val tpe = TypeRepr.of[T]

    val fn = Lambda(
      Symbol.spliceOwner,
      MethodType(List("macroVal"))(
        _ => List(tpe),
        _ => tpe
      ),
      {
        case (_, List(arg: Term)) =>
          ValDef.let(Symbol.spliceOwner, "v", arg) { v =>
            '{
              val vv = ${ v.asExprOf[T] }
              println("v=" + vv.toString())
              vv
            }.asTerm
          }

        case _ =>
          report.errorAndAbort("Fails compile")
      }
    ).asExprOf[T => T]

    '{ (_: Option[T]).map(${ fn }) }
  }
optPrettyPrinter(Some("foo"))

Output

(run-main-2) java.lang.IllegalArgumentException: Could not find proxy for val vv: String in List(val vv, method res0, module class rs$line$1$, module class repl$, module class ), encl = package repl, owners = package repl, package ; enclosures = package repl, package java.lang.IllegalArgumentException: Could not find proxy for val vv: String in List(val vv, method res0, module class rs$line$1$, module class repl$, module class ), encl = package repl, owners = package repl, package ; enclosures = package repl, package
	at dotty.tools.dotc.transform.LambdaLift$Lifter.searchIn$1(LambdaLift.scala:148)
	at dotty.tools.dotc.transform.LambdaLift$Lifter.proxy(LambdaLift.scala:161)
	at dotty.tools.dotc.transform.LambdaLift$Lifter.proxyRef(LambdaLift.scala:179)
	at dotty.tools.dotc.transform.LambdaLift$Lifter.addFreeArgs$$anonfun$1(LambdaLift.scala:185)
	at scala.collection.immutable.List.map(List.scala:246)
	at dotty.tools.dotc.transform.LambdaLift$Lifter.addFreeArgs(LambdaLift.scala:185)
	at dotty.tools.dotc.transform.LambdaLift.transformApply(LambdaLift.scala:322)
	at dotty.tools.dotc.transform.LambdaLift.transformApply(LambdaLift.scala:321)
	at dotty.tools.dotc.transform.MegaPhase.goApply(MegaPhase.scala:644)
	at dotty.tools.dotc.transform.MegaPhase.transformUnnamed$1(MegaPhase.scala:281)
	at dotty.tools.dotc.transform.MegaPhase.transformTree(MegaPhase.scala:429)
	at dotty.tools.dotc.transform.MegaPhase.mapValDef$1(MegaPhase.scala:235)
	at dotty.tools.dotc.transform.MegaPhase.transformNamed$1(MegaPhase.scala:240)
	at dotty.tools.dotc.transform.MegaPhase.transformTree(MegaPhase.scala:427)
	at dotty.tools.dotc.transform.MegaPhase.transformStat$1(MegaPhase.scala:437)
	at dotty.tools.dotc.transform.MegaPhase.recur$1(MegaPhase.scala:442)
	at dotty.tools.dotc.transform.MegaPhase.transformStats(MegaPhase.scala:442)
	at dotty.tools.dotc.transform.MegaPhase.mapPackage$1(MegaPhase.scala:382)
	at dotty.tools.dotc.transform.MegaPhase.transformUnnamed$1(MegaPhase.scala:385)
	at dotty.tools.dotc.transform.MegaPhase.transformTree(MegaPhase.scala:429)
	at dotty.tools.dotc.transform.MegaPhase.transformUnit(MegaPhase.scala:448)
	at dotty.tools.dotc.transform.MegaPhase.run(MegaPhase.scala:460)
	at dotty.tools.dotc.core.Phases$Phase.runOn$$anonfun$1(Phases.scala:308)
	at scala.collection.immutable.List.map(List.scala:246)
	at dotty.tools.dotc.core.Phases$Phase.runOn(Phases.scala:309)
	at dotty.tools.dotc.Run.runPhases$1$$anonfun$1(Run.scala:261)
	at scala.runtime.function.JProcedure1.apply(JProcedure1.java:15)
	at scala.runtime.function.JProcedure1.apply(JProcedure1.java:10)
	at scala.collection.ArrayOps$.foreach$extension(ArrayOps.scala:1323)
	at dotty.tools.dotc.Run.runPhases$1(Run.scala:272)
	at dotty.tools.dotc.Run.compileUnits$$anonfun$1(Run.scala:280)
	at scala.runtime.java8.JFunction0$mcV$sp.apply(JFunction0$mcV$sp.scala:18)
	at dotty.tools.dotc.util.Stats$.maybeMonitored(Stats.scala:68)
	at dotty.tools.dotc.Run.compileUnits(Run.scala:289)
	at dotty.tools.dotc.Run.compileUnits(Run.scala:228)
	at dotty.tools.repl.ReplCompiler.runCompilationUnit(ReplCompiler.scala:167)
	at dotty.tools.repl.ReplCompiler.compile(ReplCompiler.scala:178)
	at dotty.tools.repl.ReplDriver.compile(ReplDriver.scala:251)
	at dotty.tools.repl.ReplDriver.interpret(ReplDriver.scala:219)
	at dotty.tools.repl.ReplDriver.loop$1(ReplDriver.scala:153)
	at dotty.tools.repl.ReplDriver.runUntilQuit$$anonfun$1(ReplDriver.scala:156)
	at dotty.tools.repl.ReplDriver.withRedirectedOutput(ReplDriver.scala:175)
	at dotty.tools.repl.ReplDriver.runUntilQuit(ReplDriver.scala:156)
	at xsbt.ConsoleInterface.run(ConsoleInterface.java:52)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.base/java.lang.reflect.Method.invoke(Method.java:564)
	at sbt.internal.inc.AnalyzingCompiler.invoke(AnalyzingCompiler.scala:329)
	at sbt.internal.inc.AnalyzingCompiler.console(AnalyzingCompiler.scala:233)
	at sbt.Console.console0$1(Console.scala:64)
	at sbt.Console.$anonfun$apply$5(Console.scala:74)
	at scala.runtime.java8.JFunction0$mcV$sp.apply(JFunction0$mcV$sp.java:23)
	at sbt.util.InterfaceUtil$$anon$1.get(InterfaceUtil.scala:17)
	at sbt.TrapExit$App.run(TrapExit.scala:258)
	at java.base/java.lang.Thread.run(Thread.java:844)

Expectation

val res0: Option[String] = Some(foo)

Details

The following is working fine, not mixing '{ ... } with tree constructed using Lambda/ValDef.

  inline def prettyPrinter[T]: T => T = ${ prettyPrinterImpl[T] }

  private def prettyPrinterImpl[T: Type](using q: Quotes): Expr[T => T] = {
    import q.reflect.*

    val tpe = TypeRepr.of[T]

    Lambda(
      Symbol.spliceOwner,
      MethodType(List("macroVal"))(
        _ => List(tpe),
        _ => tpe
      ),
      {
        case (_, List(arg: Term)) =>
          ValDef.let(Symbol.spliceOwner, "v", arg) { v =>
            '{
              val vv = ${ v.asExprOf[T] }
              println("v=" + vv.toString())
              vv
            }.asTerm
          }

        case _ =>
          report.errorAndAbort("Fails compile")
      }
    ).asExprOf[T => T]
  }

Also coming back to macro optPrettyPrinter it can be fixed using changeOwner.

  inline def optPrettyPrinter[T]: Option[T] => Option[T] =
    ${ optPrettyPrinterImpl[T] }

  private def optPrettyPrinterImpl[T: Type](
      using
      q: Quotes
    ): Expr[Option[T] => Option[T]] = {
    import q.reflect.*

    val tpe = TypeRepr.of[T]

    val fn = Lambda(
      Symbol.spliceOwner,
      MethodType(List("macroVal"))(
        _ => List(tpe),
        _ => tpe
      ),
      {
        case (m, List(arg: Term)) =>
          ValDef
            .let(Symbol.spliceOwner, "v", arg) { v =>
              '{
                val vv = ${ v.asExprOf[T] }
                println("v=" + vv.toString())
                vv
              }.asTerm
            }
            .changeOwner(m) // <---- HERE

        case _ =>
          report.errorAndAbort("Fails compile")
      }
    ).asExprOf[T => T]

    '{ (_: Option[T]).map(${ fn }) }
  }

The importance of changeOwner should at least be documented on the Lambda factories, maybe always enforced.

@nicolasstucki
Copy link
Contributor

In the original example it seems fishy to ignore the lambda symbol in

        case (_, List(arg: Term)) =>

This is supposed to be the owner of any definition within the lambda.

The code should probably be

        case (meth, List(arg: Term)) =>
          ValDef.let(meth, "v", arg) { v =>

@nicolasstucki
Copy link
Contributor

Running the code with -Xcheck-macros shows the place where this bug in the implementation of the macro tries to add the val with the wrong owner to the lambda.

-- Error: tests/pos-macros/i13922/Test_2.scala:1:11 ----------------------------
1 |def test = optPrettyPrinter(Some("foo"))
  |           ^^^^^^^^^^^^^^^^
  |Exception occurred while executing macro expansion.
  |java.lang.AssertionError: assertion failed: Tree had an unexpected owner for val v
  |Expected: method $anonfun (Test_2$package$._$_$$anonfun)
  |But was: val macro (Test_2$package$._$macro)
  |
  |
  |The code of the definition of val v is
  |val v: java.lang.String = macroVal
  |
  |which was found in the code
  |{
  |  val v: java.lang.String = macroVal
  |  val vv: java.lang.String = v
  |  scala.Predef.println("v=".+(vv.toString()))
  |
  |  (vv: java.lang.String)
  |}
  |
  |which has the AST representation
  |Block(List(ValDef("v", Inferred(), Some(Ident("macroVal")))), Inlined(Some(TypeIdent("Macro_1$package$")), Nil, Block(List(ValDef("vv", Inferred(), Some(Inlined(None, Nil, Ident("v")))), Apply(Ident("println"), List(Apply(Select(Literal(StringConstant("v=")), "+"), List(Apply(Select(Ident("vv"), "toString"), Nil)))))), Typed(Ident("vv"), Inferred()))))
  |
  |
  |     at scala.runtime.Scala3RunTime$.assertFailed(Scala3RunTime.scala:8)
  |     at scala.quoted.runtime.impl.QuotesImpl$$anon$9.traverse(QuotesImpl.scala:2910)
  |     at dotty.tools.dotc.ast.Trees$Instance$TreeTraverser.apply(Trees.scala:1640)
  |     at dotty.tools.dotc.ast.Trees$Instance$TreeTraverser.apply(Trees.scala:1640)
  |     at dotty.tools.dotc.ast.Trees$Instance$TreeAccumulator.fold$1(Trees.scala:1515)
  |     at dotty.tools.dotc.ast.Trees$Instance$TreeAccumulator.apply(Trees.scala:1517)
  |     at dotty.tools.dotc.ast.Trees$Instance$TreeAccumulator.foldOver(Trees.scala:1548)
  |     at dotty.tools.dotc.ast.Trees$Instance$TreeTraverser.traverseChildren(Trees.scala:1641)
  |     at scala.quoted.runtime.impl.QuotesImpl$$anon$9.traverse(QuotesImpl.scala:2913)
  |     at scala.quoted.runtime.impl.QuotesImpl$reflect$.xCheckMacroOwners(QuotesImpl.scala:2914)
  |     at scala.quoted.runtime.impl.QuotesImpl$reflect$.scala$quoted$runtime$impl$QuotesImpl$reflect$$$xCheckMacroedOwners(QuotesImpl.scala:2885)
  |     at scala.quoted.runtime.impl.QuotesImpl$reflect$Lambda$.apply$$anonfun$22$$anonfun$1(QuotesImpl.scala:797)
  |     at dotty.tools.dotc.ast.tpd$.DefDef(tpd.scala:285)
  |     at dotty.tools.dotc.ast.tpd$.Closure(tpd.scala:120)
  |     at scala.quoted.runtime.impl.QuotesImpl$reflect$Lambda$.apply$$anonfun$22(QuotesImpl.scala:797)
  |     at scala.quoted.runtime.impl.QuotesImpl$reflect$.scala$quoted$runtime$impl$QuotesImpl$reflect$$$withDefaultPos(QuotesImpl.scala:2867)
  |     at scala.quoted.runtime.impl.QuotesImpl$reflect$Lambda$.apply(QuotesImpl.scala:797)
  |     at scala.quoted.runtime.impl.QuotesImpl$reflect$Lambda$.apply(QuotesImpl.scala:795)
  |     at Macro_1$package$.optPrettyPrinterImpl(Macro_1.scala:32)
  |     at Macro_1$package$.inline$optPrettyPrinterImpl(Macro_1.scala:6)
  |
  | This location contains code that was inlined from Test_2.scala:1

@nicolasstucki
Copy link
Contributor

nicolasstucki commented Nov 11, 2021

The following is the correct fix

        case (meth, List(arg: Term)) =>
          ValDef.let(meth, "v", arg) { v =>

but then we hit another issue with the owner of the quote in the ValDef.let as we also need to change its owner

        '{
            val vv = ${ v.asExprOf[T] }
            println("v=" + vv.toString())
            vv
-          }.asTerm
+          }.asTerm.changeOwner(owner)

To set the owner of the quote correctly from the start we need #13652 and would need to do

        given Quotes = owner.asQuotes
        ValDef.let(m, "v", arg) { v =>
          '{
            val vv = ${ v.asExprOf[T] }
            println("v=" + vv.toString())
            vv
          }.asTerm
        }

#13652 should add an example of this usecase added examples in documentation of Lambda and let in #13652.

nicolasstucki added a commit to dotty-staging/dotty that referenced this issue Nov 11, 2021
This makes it possible to create `Expr`s with different owners to avoid
a call to `changeOwner`.

Closes scala#13922
@nicolasstucki nicolasstucki self-assigned this Nov 11, 2021
@cchantep
Copy link
Contributor Author

For now .changeOwner at top-level is the soundest workaround.

nicolasstucki added a commit to dotty-staging/dotty that referenced this issue Nov 18, 2021
This makes it possible to create `Expr`s with different owners to avoid
a call to `changeOwner`.

Closes scala#13922
nicolasstucki added a commit to dotty-staging/dotty that referenced this issue Nov 19, 2021
This makes it possible to create `Expr`s with different owners to avoid
a call to `changeOwner`.

Closes scala#13922
nicolasstucki added a commit to dotty-staging/dotty that referenced this issue Dec 15, 2021
This makes it possible to create `Expr`s with different owners to avoid
a call to `changeOwner`.

Closes scala#13922
nicolasstucki added a commit to dotty-staging/dotty that referenced this issue Jan 3, 2022
This makes it possible to create `Expr`s with different owners to avoid
a call to `changeOwner`.

Closes scala#13922
nicolasstucki added a commit to dotty-staging/dotty that referenced this issue Jan 4, 2022
This makes it possible to create `Expr`s with different owners to avoid
a call to `changeOwner`.

Closes scala#13922
nicolasstucki added a commit to dotty-staging/dotty that referenced this issue Jan 13, 2022
This makes it possible to create `Expr`s with different owners to avoid
a call to `changeOwner`.

Closes scala#13922
@nicolasstucki nicolasstucki added area:metaprogramming:reflection Issues related to the quotes reflection API and removed area:metaprogramming labels Mar 4, 2022
nicolasstucki added a commit to dotty-staging/dotty that referenced this issue Mar 15, 2022
This makes it possible to create `Expr`s with different owners to avoid
a call to `changeOwner`.

Closes scala#13922
nicolasstucki added a commit to dotty-staging/dotty that referenced this issue Mar 16, 2022
This makes it possible to create `Expr`s with different owners to avoid
a call to `changeOwner`.

Closes scala#13922
nicolasstucki added a commit to dotty-staging/dotty that referenced this issue Mar 18, 2022
This makes it possible to create `Expr`s with different owners to avoid
a call to `changeOwner`.

Closes scala#13922
nicolasstucki added a commit to dotty-staging/dotty that referenced this issue Mar 21, 2022
This makes it possible to create `Expr`s with different owners to avoid
a call to `changeOwner`.

Closes scala#13922
nicolasstucki added a commit to dotty-staging/dotty that referenced this issue Mar 21, 2022
This makes it possible to create `Expr`s with different owners to avoid
a call to `changeOwner`.

Closes scala#13922
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area:documentation area:metaprogramming:reflection Issues related to the quotes reflection API
Projects
None yet
Development

No branches or pull requests

3 participants