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

Compiler crash when using macro annotation with local class: "asTerm called on not-a-Term val <none>" #22908

Open
tschuchortdev opened this issue Apr 2, 2025 · 3 comments
Labels

Comments

@tschuchortdev
Copy link

tschuchortdev commented Apr 2, 2025

Compiler crashes when running a macro annotation on a local class. The same macro annotation works correctly with top-level classes.

Compiler version

3.6.2

Minimized code

import scala.annotation.{MacroAnnotation, experimental}
import scala.quoted.{Expr, Quotes, Type}

@experimental
class backedByLazyVal extends MacroAnnotation {

  override def transform(using q: Quotes)(definition: q.reflect.Definition,
                                          companion: Option[q.reflect.Definition]): List[q.reflect.Definition] = {
    import q.reflect.{*, given}

    val defdef: DefDef = definition.asInstanceOf[DefDef]
     
    val lazyValSym =
      Symbol.newVal(parent = Symbol.spliceOwner,
                    Symbol.freshName(defdef.name + "_lazyval"),
                    defdef.returnTpt.tpe,
                    Flags.Lazy | Flags.Private,
                    privateWithin = Symbol.noSymbol)

    val lazyValDef = ValDef(lazyValSym, rhs = defdef.rhs.map(_.changeOwner(lazyValSym)))

    val redirectedDefDef =
      DefDef.copy(defdef)(
        name = defdef.name,
        paramss = defdef.paramss,
        tpt = defdef.tpt,
        rhs = Some(
          // This seems to be the problem
          Ref(lazyValSym).changeOwner(defdef.symbol))
      )

    List(lazyValDef, redirectedDefDef)
  }
}

This test works:

import org.scalatest.flatspec.AnyFlatSpec
import org.scalatest.matchers.should.Matchers

var executedCount = 0

class MacroTestClass {
  @backedByLazyVal
  def foo: Int = {
    println("executed")
    executedCount += 1
    1234
  }
}

class LazyValMacroTest extends AnyFlatSpec with Matchers {
  "LazyValMacro" should "run body only once" in {
    val c = new MacroTestClass

    assert(c.foo == 1234)
    assert(executedCount == 1)
    assert(c.foo == 1234)
    assert(executedCount == 1)
  }
}

This test crashes the compiler:

import org.scalatest.flatspec.AnyFlatSpec
import org.scalatest.matchers.should.Matchers

class LazyValMacroTest extends AnyFlatSpec with Matchers {
  "LazyValMacro" should "run body only once" in {
    var executedCount = 0
    class MacroTestClass {
      @backedByLazyVal
      def foo: Int = {
        println("executed")
        executedCount += 1
        1234
      }
    }

    val c = new MacroTestClass

    assert(c.foo == 1234)
    assert(executedCount == 1)
    assert(c.foo == 1234)
    assert(executedCount == 1)
  }
}

Output

[error] java.lang.AssertionError: assertion failed: asTerm called on not-a-Term val <none>
[error] scala.runtime.Scala3RunTime$.assertFailed(Scala3RunTime.scala:8)
[error] dotty.tools.dotc.core.Symbols$Symbol.asTerm(Symbols.scala:186)
[error] dotty.tools.dotc.transform.ParamForwarding.transformIfParamAlias(ParamForwarding.scala:43)
[error] dotty.tools.dotc.transform.ParamForwarding.transformValDef(ParamForwarding.scala:66)
[error] dotty.tools.dotc.transform.MegaPhase.goValDef(MegaPhase.scala:1026)
[error] dotty.tools.dotc.transform.MegaPhase.goValDef(MegaPhase.scala:1027)
[error] dotty.tools.dotc.transform.MegaPhase.goValDef(MegaPhase.scala:1027)
[error] dotty.tools.dotc.transform.MegaPhase.goValDef(MegaPhase.scala:1027)
[error] dotty.tools.dotc.transform.MegaPhase.transformNamed$1(MegaPhase.scala:257)
[error] dotty.tools.dotc.transform.MegaPhase.transformTree(MegaPhase.scala:452)
[error] dotty.tools.dotc.transform.MegaPhase.loop$1(MegaPhase.scala:465)
[error] dotty.tools.dotc.transform.MegaPhase.transformStats(MegaPhase.scala:465)
[error] dotty.tools.dotc.transform.MegaPhase.transformUnnamed$1(MegaPhase.scala:376)
[error] dotty.tools.dotc.transform.MegaPhase.transformTree(MegaPhase.scala:454)
[error] dotty.tools.dotc.transform.MegaPhase.transformNamed$1(MegaPhase.scala:272)
[error] dotty.tools.dotc.transform.MegaPhase.transformTree(MegaPhase.scala:452)
[error] dotty.tools.dotc.transform.MegaPhase.loop$2(MegaPhase.scala:471)
[error] dotty.tools.dotc.transform.MegaPhase.transformBlock(MegaPhase.scala:476)
[error] dotty.tools.dotc.transform.MegaPhase.transformUnnamed$1(MegaPhase.scala:315)
[error] dotty.tools.dotc.transform.MegaPhase.transformTree(MegaPhase.scala:454)
[error] dotty.tools.dotc.transform.MegaPhase.mapDefDef$1(MegaPhase.scala:265)
[error] dotty.tools.dotc.transform.MegaPhase.transformNamed$1(MegaPhase.scala:268)
[error] dotty.tools.dotc.transform.MegaPhase.transformTree(MegaPhase.scala:452)
[error] dotty.tools.dotc.transform.MegaPhase.loop$3(MegaPhase.scala:486)
[error] dotty.tools.dotc.transform.MegaPhase.transformTrees(MegaPhase.scala:486)
[error] dotty.tools.dotc.transform.MegaPhase.transformSpecificTrees(MegaPhase.scala:489)
[error] dotty.tools.dotc.transform.MegaPhase.transformUnnamed$1(MegaPhase.scala:410)
[error] dotty.tools.dotc.transform.MegaPhase.transformTree(MegaPhase.scala:454)
[error] dotty.tools.dotc.transform.MegaPhase.loop$1(MegaPhase.scala:465)
[error] dotty.tools.dotc.transform.MegaPhase.transformStats(MegaPhase.scala:465)
[error] dotty.tools.dotc.transform.MegaPhase.transformUnnamed$1(MegaPhase.scala:376)
[error] dotty.tools.dotc.transform.MegaPhase.transformTree(MegaPhase.scala:454)
[error] dotty.tools.dotc.transform.MegaPhase.transformNamed$1(MegaPhase.scala:272)
[error] dotty.tools.dotc.transform.MegaPhase.transformTree(MegaPhase.scala:452)
[error] dotty.tools.dotc.transform.MegaPhase.loop$1(MegaPhase.scala:465)
[error] dotty.tools.dotc.transform.MegaPhase.transformStats(MegaPhase.scala:465)
[error] dotty.tools.dotc.transform.MegaPhase.mapPackage$1(MegaPhase.scala:396)
[error] dotty.tools.dotc.transform.MegaPhase.transformUnnamed$1(MegaPhase.scala:399)
[error] dotty.tools.dotc.transform.MegaPhase.transformTree(MegaPhase.scala:454)
[error] dotty.tools.dotc.transform.MegaPhase.transformUnit(MegaPhase.scala:481)
[error] dotty.tools.dotc.transform.MegaPhase.run(MegaPhase.scala:493)
[error] dotty.tools.dotc.core.Phases$Phase.runOn$$anonfun$1(Phases.scala:380)
[error] scala.runtime.function.JProcedure1.apply(JProcedure1.java:15)
[error] scala.runtime.function.JProcedure1.apply(JProcedure1.java:10)
[error] scala.collection.immutable.List.foreach(List.scala:334)
[error] dotty.tools.dotc.core.Phases$Phase.runOn(Phases.scala:373)
[error] dotty.tools.dotc.Run.runPhases$1$$anonfun$1(Run.scala:343)
[error] scala.runtime.function.JProcedure1.apply(JProcedure1.java:15)
[error] scala.runtime.function.JProcedure1.apply(JProcedure1.java:10)
[error] scala.collection.ArrayOps$.foreach$extension(ArrayOps.scala:1323)
[error] dotty.tools.dotc.Run.runPhases$1(Run.scala:336)
[error] dotty.tools.dotc.Run.compileUnits$$anonfun$1(Run.scala:384)
[error] dotty.tools.dotc.Run.compileUnits$$anonfun$adapted$1(Run.scala:396)
[error] dotty.tools.dotc.util.Stats$.maybeMonitored(Stats.scala:69)
[error] dotty.tools.dotc.Run.compileUnits(Run.scala:396)
[error] dotty.tools.dotc.Run.compileSources(Run.scala:282)
[error] dotty.tools.dotc.Run.compile(Run.scala:267)
[error] dotty.tools.dotc.Driver.doCompile(Driver.scala:37)
[error] dotty.tools.xsbt.CompilerBridgeDriver.run(CompilerBridgeDriver.java:141)
[error] dotty.tools.xsbt.CompilerBridge.run(CompilerBridge.java:22)
[error] sbt.internal.inc.AnalyzingCompiler.compile(AnalyzingCompiler.scala:91)
[error] sbt.internal.inc.MixedAnalyzingCompiler.$anonfun$compile$7(MixedAnalyzingCompiler.scala:196)
[error] scala.runtime.java8.JFunction0$mcV$sp.apply(JFunction0$mcV$sp.java:23)
[error] sbt.internal.inc.MixedAnalyzingCompiler.timed(MixedAnalyzingCompiler.scala:252)
[error] sbt.internal.inc.MixedAnalyzingCompiler.$anonfun$compile$4(MixedAnalyzingCompiler.scala:186)
[error] sbt.internal.inc.MixedAnalyzingCompiler.$anonfun$compile$4$adapted(MixedAnalyzingCompiler.scala:166)
[error] sbt.internal.inc.JarUtils$.withPreviousJar(JarUtils.scala:241)
[error] sbt.internal.inc.MixedAnalyzingCompiler.compileScala$1(MixedAnalyzingCompiler.scala:166)
[error] sbt.internal.inc.MixedAnalyzingCompiler.compile(MixedAnalyzingCompiler.scala:204)
[error] sbt.internal.inc.IncrementalCompilerImpl.$anonfun$compileInternal$1(IncrementalCompilerImpl.scala:542)
[error] sbt.internal.inc.IncrementalCompilerImpl.$anonfun$compileInternal$1$adapted(IncrementalCompilerImpl.scala:542)
[error] sbt.internal.inc.Incremental$.$anonfun$apply$3(Incremental.scala:178)
[error] sbt.internal.inc.Incremental$.$anonfun$apply$3$adapted(Incremental.scala:176)
[error] sbt.internal.inc.Incremental$$anon$2.run(Incremental.scala:454)
[error] sbt.internal.inc.IncrementalCommon$CycleState.next(IncrementalCommon.scala:117)
[error] sbt.internal.inc.IncrementalCommon$$anon$1.next(IncrementalCommon.scala:56)
[error] sbt.internal.inc.IncrementalCommon$$anon$1.next(IncrementalCommon.scala:52)
[error] sbt.internal.inc.IncrementalCommon.cycle(IncrementalCommon.scala:265)
[error] sbt.internal.inc.Incremental$.$anonfun$incrementalCompile$8(Incremental.scala:409)
[error] sbt.internal.inc.Incremental$.withClassfileManager(Incremental.scala:496)
[error] sbt.internal.inc.Incremental$.incrementalCompile(Incremental.scala:396)
[error] sbt.internal.inc.Incremental$.apply(Incremental.scala:204)
[error] sbt.internal.inc.IncrementalCompilerImpl.compileInternal(IncrementalCompilerImpl.scala:542)
[error] sbt.internal.inc.IncrementalCompilerImpl.$anonfun$compileIncrementally$1(IncrementalCompilerImpl.scala:496)
[error] sbt.internal.inc.IncrementalCompilerImpl.handleCompilationError(IncrementalCompilerImpl.scala:332)
[error] sbt.internal.inc.IncrementalCompilerImpl.compileIncrementally(IncrementalCompilerImpl.scala:433)
[error] sbt.internal.inc.IncrementalCompilerImpl.compile(IncrementalCompilerImpl.scala:137)
[error] sbt.Defaults$.compileIncrementalTaskImpl(Defaults.scala:2419)
[error] sbt.Defaults$.$anonfun$compileIncrementalTask$2(Defaults.scala:2369)
[error] sbt.internal.server.BspCompileTask$.$anonfun$compute$1(BspCompileTask.scala:41)
[error] sbt.internal.io.Retry$.apply(Retry.scala:47)
[error] sbt.internal.io.Retry$.apply(Retry.scala:29)
[error] sbt.internal.io.Retry$.apply(Retry.scala:24)
[error] sbt.internal.server.BspCompileTask$.compute(BspCompileTask.scala:41)
[error] sbt.Defaults$.$anonfun$compileIncrementalTask$1(Defaults.scala:2367)
[error] scala.Function1.$anonfun$compose$1(Function1.scala:49)
[error] sbt.internal.util.$tilde$greater.$anonfun$$u2219$1(TypeFunctions.scala:63)
[error] sbt.std.Transform$$anon$4.work(Transform.scala:69)
[error] sbt.Execute.$anonfun$submit$2(Execute.scala:283)
[error] sbt.internal.util.ErrorHandling$.wideConvert(ErrorHandling.scala:24)
[error] sbt.Execute.work(Execute.scala:292)
[error] sbt.Execute.$anonfun$submit$1(Execute.scala:283)
[error] sbt.ConcurrentRestrictions$$anon$4.$anonfun$submitValid$1(ConcurrentRestrictions.scala:265)
[error] sbt.CompletionService$$anon$2.call(CompletionService.scala:65)
[error] java.base/java.util.concurrent.FutureTask.run(FutureTask.java:317)
[error] java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:572)
[error] java.base/java.util.concurrent.FutureTask.run(FutureTask.java:317)
[error] java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1144)
[error] java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:642)

Expectation

Macro annotation should work correctly even for local classes.

@tschuchortdev tschuchortdev added itype:bug stat:needs triage Every issue needs to have an "area" and "itype" label labels Apr 2, 2025
@Gedochao
Copy link
Contributor

Gedochao commented Apr 3, 2025

[error] ./Macro.scala:13:38
[error] Not found: defdef - did you mean DefDef?
[error]                     Symbol.freshName(defdef.name + "_lazyval"),
[error]                                      ^^^^^^
[error] ./Macro.scala:14:21
[error] Not found: defdef - did you mean DefDef?
[error]                     defdef.returnTpt.tpe,
[error]                     ^^^^^^
[error] ./Macro.scala:18:47
[error] Not found: defdef - did you mean DefDef?
[error]     val lazyValDef = ValDef(lazyValSym, rhs = defdef.rhs.map(_.changeOwner(lazyValSym)))
[error]                                               ^^^^^^
[error] ./Macro.scala:21:19
[error] Not found: defdef - did you mean DefDef?
[error]       DefDef.copy(defdef)(
[error]                   ^^^^^^
[error] ./Macro.scala:22:16
[error] Not found: defdef - did you mean DefDef?
[error]         name = defdef.name,
[error]                ^^^^^^
[error] ./Macro.scala:23:19
[error] Not found: defdef - did you mean DefDef?
[error]         paramss = defdef.paramss,
[error]                   ^^^^^^
[error] ./Macro.scala:24:15
[error] Not found: defdef - did you mean DefDef?
[error]         tpt = defdef.tpt,
[error]               ^^^^^^
[error] ./Macro.scala:27:39
[error] Not found: defdef - did you mean DefDef?
[error]           Ref(lazyValSym).changeOwner(defdef.symbol))
[error]                                       ^^^^^^

@tschuchortdev are we missing a piece of the code? I think defdef hasn't been defined anywhere.

@Gedochao Gedochao added itype:crash stat:needs info area:metaprogramming:quotes Issues related to quotes and splices stat:cannot reproduce and removed stat:needs triage Every issue needs to have an "area" and "itype" label labels Apr 3, 2025
@tschuchortdev
Copy link
Author

Oops, you are right. I removed some error handling code which also assigned this variable. I have updated the code sample.

@Gedochao
Copy link
Contributor

Gedochao commented Apr 4, 2025

Ah ok.
I managed to reproduce it now. 👍

Scala CLI directives necessary to reproduce:

//> using test.dep org.scalatest::scalatest:3.2.19
//> using options -experimental

no need to run the tests, just compile them with --test:

scala-cli compile --test .

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants