I'm using the RunningStrategies class in my Scala code to define a set of running strategies. To create an instance of RunningStrategies, I'm using the apply method along with a variable number of arguments representing the different strategies. To achieve this, I have also defined a macro called RunningStrategiesMacros.
However, when I try to use RunningStrategies with some strategies, I get the following error message: "macro implementation not found: apply". The error seems to be related to the fact that I'm trying to use the macro implementation in the same compilation run that defines it.
RunningStrategy:
package com.dv.phoenix.brandsafety.models
object RunningStrategy extends Enumeration {
type RunningStrategy = Value
val
MonitoringOnly,
BlockingOnly,
MonitoringAndBlockingInherent,
MonitoringAndBlockingRecalculate
= Value
}
RunningStrategies:
package com.dv.phoenix.brandsafety.models
import com.dv.phoenix.brandsafety.models.RunningStrategy.RunningStrategy
import com.dv.phoenix.brandsafety.utils.RunningStrategiesMacros
import scala.language.experimental.macros
case class RunningStrategies private (runningStrategies: Set[RunningStrategy])
object RunningStrategies {
def apply(strategies: RunningStrategy*): RunningStrategies = macro RunningStrategiesMacros.applyImp
}
RunningStrategiesMacro:
package com.dv.phoenix.brandsafety.utils
import com.dv.phoenix.brandsafety.models.RunningStrategies
import com.dv.phoenix.brandsafety.models.RunningStrategy.RunningStrategy
import scala.reflect.macros.blackbox
object RunningStrategiesMacros {
def applyImp(c: blackbox.Context)(runningStrategies: c.Expr[RunningStrategy]*): c.Expr[RunningStrategies] = {
import c.universe._
val hasMonitoringAndBlockingInherent = runningStrategies.map(_.tree.toString).contains(s"${RunningStrategy.getClass.getName.stripSuffix("$")}.${RunningStrategy.MonitoringAndBlockingInherent}")
val hasMonitoringAndBlockingRecalculate = runningStrategies.map(_.tree.toString).contains(s"${RunningStrategy.getClass.getName.stripSuffix("$")}.${RunningStrategy.MonitoringAndBlockingRecalculate}")
if (hasMonitoringAndBlockingInherent && hasMonitoringAndBlockingRecalculate) {
c.abort(c.enclosingPosition, "runningStrategies cannot include both RunningStrategy.MonitoringAndBlockingInherent and RunningStrategy.MonitoringAndBlockingRecalculate")
} else {
c.Expr(q"RunningStrategies(Set(..$runningStrategies))")
}
}
}
Usuage:
override protected val runningStrategies: RunningStrategies = RunningStrategies(MonitoringOnly, MonitoringAndBlockingInherent, MonitoringAndBlockingRecalculate)
I got the following error:
macro implementation not found: apply
(the most common reason for that is that you cannot use macro implementations in the same compilation run that defines them)
override protected val runningStrategies: RunningStrategies = RunningStrategies(MonitoringOnly, MonitoringAndBlockingInherent, MonitoringAndBlockingRecalculate)
I have noticed that the scala-logging library has managed to solve this problem with their LoggerImpl and LoggerMacro classes. Can someone explain how they were able to do this and how I can apply the same technique to my RunningStrategiesMacro?
In your question you described your package structure but not your project structure. Packages and subprojects are different concepts
Why can't sbt/assembly find the main classes in a project with multiple subprojects?
You should have different subprojects if you want to use macros in Scala 2
https://www.scala-sbt.org/1.x/docs/Macro-Projects.html
Firstly, let's refresh the terminology. This is a macro definition
import scala.language.experimental.macros
def foo(): Unit = macro fooImpl
This is a macro implementation
import scala.reflect.macros.blackbox
def fooImpl(c: blackbox.Context)(): c.Tree = {
import c.universe._
println("test")
q"""_root_.scala.Predef.println("foo")"""
}
This is a macro application (call site, macro expansion)
foo()
// at compile time: scalac: test
// at runtime: foo
In Scala 2 macro implementations must be compiled before macro applications (the compile time of macro applications i.e. macro expansion is the runtime of macros). So it's macro implementations and macro applications that must be in different compile units. For example in different subprojects of your project. Or in src/main/scala
and src/test/scala
of the same project (main code is compiled before tests). Where macro definitions are is not so important. They can be in the same compilation unit/subproject as macro implementations (maybe in the same file/class) or in the same as macro applications or in their own.
In scala-logging
, macro definitions
def error(message: String): Unit = macro LoggerMacro.errorMessage
and macro implementations
def errorMessage(c: LoggerContext)(message: c.Expr[String]): c.universe.Tree = ...
are in the same project (in different files but this is not necessary, they can be in the same file) but macro applications
logger.error(msg)
are in src/test/scala
https://github.com/lightbend-labs/scala-logging/blob/v3.9.5/src/test/scala/com/typesafe/scalalogging/LoggerSpec.scala . And other macro applications will be also in different compilation units i.e. client projects using scala-logging
as a dependency.
In your project
def apply(strategies: RunningStrategy*): RunningStrategies = macro RunningStrategiesMacros.applyImp
is a macro definition and
def applyImp(c: blackbox.Context)(strategies: c.Expr[RunningStrategy]*): c.Expr[RunningStrategies] = ...
is a macro implementation. But you should re-organize your project having at least two subprojects and place macro applications
override protected val runningStrategies: RunningStrategies = RunningStrategies.apply(MonitoringOnly, MonitoringAndBlockingInherent, MonitoringAndBlockingRecalculate)
to a different subproject
lazy val commonSettings = Seq(
scalaVersion := "2.13.10"
)
// where you override runningStrategies goes here
lazy val core = project
.dependsOn(macros)
.settings(
commonSettings,
scalacOptions += "-Ymacro-debug-lite" // convenient to debug macro expansions
)
// utils/RunningStrategiesMacros go here
lazy val macros = project
.settings(
commonSettings,
// necessary for macro implementations
libraryDependencies += scalaOrganization.value % "scala-reflect" % scalaVersion.value
)
Is there any trick to use macros in the same file they are defined?
Possible to identify/use Scala macros using reflection or similar?
Implicit materialization in the same module
Def macro - scala 2.13 - not found: value cond