Macro untypecheck required - scala

I'm running into problems in my open-source project using Macros to generate some code. Everything works fine if I use c.untypecheck, but ideally I'd prefer not to have to do that.
This is the relevant code: https://github.com/outr/reactify/blob/master/shared/src/main/scala/com/outr/reactify/Macros.scala#L46
If I remove the c.untypecheck I get the following compile-time error:
[error] (reactifyJVM/test:compileIncremental) java.lang.AssertionError: assertion failed:
[error] transformCaseApply: name = previousVal tree = previousVal / class scala.reflect.internal.Trees$Ident
[error] while compiling: /home/mhicks/projects/open-source/reactify/shared/src/test/scala/specs/BasicSpec.scala
[error] during phase: refchecks
[error] library version: version 2.12.1
[error] compiler version: version 2.12.1
[error] reconstructed args: -classpath /home/mhicks/projects/open-source/reactify/jvm/target/scala-2.12/test-classes:/home/mhicks/projects/open-source/reactify/jvm/target/scala-2.12/classes:/home/mhicks/.ivy2/cache/org.scala-lang/scala-reflect/jars/scala-reflect-2.12.1.jar:/home/mhicks/.ivy2/cache/org.scalatest/scalatest_2.12/bundles/scalatest_2.12-3.0.1.jar:/home/mhicks/.ivy2/cache/org.scalactic/scalactic_2.12/bundles/scalactic_2.12-3.0.1.jar:/home/mhicks/.ivy2/cache/org.scala-lang.modules/scala-xml_2.12/bundles/scala-xml_2.12-1.0.5.jar:/home/mhicks/.ivy2/cache/org.scala-lang.modules/scala-parser-combinators_2.12/bundles/scala-parser-combinators_2.12-1.0.4.jar -bootclasspath /usr/java/jdk1.8.0_92/jre/lib/resources.jar:/usr/java/jdk1.8.0_92/jre/lib/rt.jar:/usr/java/jdk1.8.0_92/jre/lib/sunrsasign.jar:/usr/java/jdk1.8.0_92/jre/lib/jsse.jar:/usr/java/jdk1.8.0_92/jre/lib/jce.jar:/usr/java/jdk1.8.0_92/jre/lib/charsets.jar:/usr/java/jdk1.8.0_92/jre/lib/jfr.jar:/usr/java/jdk1.8.0_92/jre/classes:/home/mhicks/.ivy2/cache/org.scala-lang/scala-library/jars/scala-library-2.12.1.jar
[error]
[error] last tree to typer: TypeTree(class Position)
[error] tree position: line 148 of /home/mhicks/projects/open-source/reactify/shared/src/test/scala/specs/BasicSpec.scala
[error] tree tpe: org.scalactic.source.Position
[error] symbol: case class Position in package source
[error] symbol definition: case class Position extends Product with Serializable (a ClassSymbol)
[error] symbol package: org.scalactic.source
[error] symbol owners: class Position
[error] call site: <$anon: com.outr.reactify.ChangeListener[Int]> in package specs
[error]
[error] == Source file context for tree position ==
[error]
[error] 145 current should be(15)
[error] 146 }
[error] 147 "observe a complex change" in {
[error] 148 val v1 = Var(5)
[error] 149 val v2 = Var(10)
[error] 150 val v3 = Var(v1 + v2)
[error] 151 var changed = 0
[error] Total time: 1 s, completed Jan 31, 2017 4:43:03 PM
If I add it back everything compiles and works just fine. In more complex use-cases I've been encountering some issues at compile-time Could not find proxy for ... and I think this might be the reason.
Any suggestions would be greatly appreciated.

You're introducing an untyped tree into a typed tree.
The incoming tree is typechecked, and then the outgoing tree (that your macro emits) is typechecked again, but the typer does not descend into a tree that is already typechecked (i.e., that has a type already assigned to it).
Because you're introducing new symbols, you can't just use the incoming context to typecheck your reference.
So, the simplest solution is what you arrived at, to untypecheck the outgoing tree. It's also sufficient to untypecheck the transformed tree, to allow typer to descend to your new, untyped tree.
I had to reduce the exploding test by commenting out code. It's unfortunate that it's not immediately obvious what source line causes the error. Maybe it's more obvious if you're familiar with the macro involved.
class Sample {
def sample(): Unit = {
val v = Var(5)
v := v + 5
}
}
The tree in question, from -Xprint:typer -Yshow-trees:
Apply( // def +(x: Int): Int in class Int, tree.tpe=Int
com.outr.reactify.`package`.state2Value[Int](previousVal)."$plus" // def +(x: Int): Int in class Int, tree.tpe=(x: Int)Int
5
)
Also worth mentioning that it was easier to write a quick compile script with the "reconstructed args" in the error message, to eliminate sbt incremental compilation, ScalaTest macros and other mysteries.
Edit, the API for setting by hand:
def setStateChannel(value: c.Tree): c.Tree = {
val observables = retrieveObservables(c)(value)
val channel = c.prefix.tree
val selfReference = observables.exists(_.equalsStructure(channel))
val untyped =
q"""
val previousValue = com.outr.reactify.State.internalFunction($channel)
val previousVal = com.outr.reactify.Val(previousValue())
"""
val retyped = c.typecheck(untyped)
val transformed = if (selfReference) {
val transformer = new Transformer {
override def transform(tree: c.universe.Tree): c.universe.Tree = if (tree.equalsStructure(channel)) {
val t = q"previousVal"
val Block(_ :: v :: Nil, _) = retyped
c.internal.setSymbol(t, v.symbol)
c.internal.setType(t, v.tpe)
} else {
super.transform(tree)
}
}
transformer.transform(value)
} else {
value
}
val res = q"$channel.update(List(..$observables), $transformed)"
q"$retyped ; $res"
}

Related

sbt throws AssertionError at compilation with indecipherable error message

I've been getting these weird error messages when trying to recompile my sbt project, after adding some small changes to the code. Sometimes, depending on the edited code, the error disappears after cleaning the sbt project, but other times the error persists.
One example of how the error appears (but doesn't persist)
Adding this method to the Terrain class and compiling gives an error message that disappears after cleaning the project and recompiling:
def genChunkInputData(p: Vector3i): FloatBuffer = {
val inputData = MemoryUtil.memAllocFloat(4 * (Chunk.SIZE+ 1) * (Chunk.SIZE+ 1) * (Chunk.SIZE+ 1))
val r = (0 to Chunk.SIZE + 1)
for (i <- r; j <- r; k <- r) {
inputData.put(0.1F + 0.5F*min(i, Chunk.SIZE - i).toFloat/ Chunk.SIZE). //RED
put(0.1F + 0.5F*min(j, Chunk.SIZE - j).toFloat/ Chunk.SIZE). //GREEN
put(0.1F + 0.5F*min(k, Chunk.SIZE - k).toFloat/ Chunk.SIZE).//BLUE
put(isovalue((i + p(2)*Chunk.SIZE).toDouble, (j + p(1)*Chunk.SIZE).toDouble, (k + p(0)*Chunk.SIZE).toDouble)) //ISOVALUE
}
inputData.flip()
inputData
}
Here are some snippets from the error message:
[error] ## Exception when compiling 21 sources to D:\Computer science\Scala\Meandering Depths\target\scala-2.13\classes
[error] java.lang.AssertionError: assertion failed:
[error] List(method apply$mcI$sp, method apply$mcI$sp)
[error] while compiling: D:\Computer science\Scala\Meandering Depths\src\main\scala\game\Terrain.scala
[error] during phase: globalPhase=specialize, enteringPhase=explicitouter
[error] library version: version 2.13.3
[error] compiler version: version 2.13.3
[error] reconstructed args:
and
[error]
[error] last tree to typer: Select(Ident(r), foreach$mVc$sp)
[error] tree position: line 77 of D:\Computer science\Scala\Meandering Depths\src\main\scala\game\Terrain.scala
[error] tree tpe: (f: Int => Unit): Unit
[error] symbol: (final override) method foreach$mVc$sp in class Range
[error] symbol definition: final override def foreach$mVc$sp(f: Int => Unit): Unit (a MethodSymbol)
[error] symbol package: scala.collection.immutable
[error] symbol owners: method foreach$mVc$sp -> class Range
[error] call site: method $anonfun$genChunkInputData in package game
[error]
[error] == Source file context for tree position ==
[error]
[error] 74 def genChunkInputData(p: Vector3i): FloatBuffer = {
[error] 75 val inputData = MemoryUtil.memAllocFloat(4 * (Chunk.SIZE+ 1) * (Chunk.SIZE+ 1) * (Chunk.SIZE+ 1))
[error] 76 val r = (0 to Chunk.SIZE)
[error] 77 for (i <- r; j <- r; k <- r) {
[error] 78 inputData.put(0.1F + 0.5F*min(i, Chunk.SIZE - i).toFloat/ Chunk.SIZE). //RED
[error] 79 put(0.1F + 0.5F*min(j, Chunk.SIZE - j).toFloat/ Chunk.SIZE). //GREEN
[error] 80 put(0.1F + 0.5F*min(k, Chunk.SIZE - k).toFloat/ Chunk.SIZE).//BLUE
[error] scala.reflect.internal.SymbolTable.throwAssertionError(SymbolTable.scala:170)
[error] scala.reflect.internal.Symbols$Symbol.suchThat(Symbols.scala:2024)[error] scala.tools.nsc.transform.SpecializeTypes$SpecializationTransformer.matchingSymbolInPrefix$1(SpecializeTypes.scala:1573)
However, after following these steps, if you choose to remove that method from the Terrain class, it will again throw a similar error, but this time the source seems to have changed (to a different method, which had had no issue before and worked just fine). Again, by cleaning the build and recompiling, the error disappears.
Here is the change to the error message:
[error] last tree to typer: Function(value $anonfun)
[error] tree position: line 150 of D:\Computer science\Scala\Meandering Depths\src\main\scala\game\Terrain.scala
[error] tree tpe: Int => Unit
[error] symbol: value $anonfun
[error] == Source file context for tree position ==
[error]
[error] 147
[error] 148 private def isovalue(x: Double, y: Double, z: Double): Float = {
[error] 149 var res = -0.1F
[error] 150 for (i <- 0 until 4)
[error] 151 res += amp(i) * noise(i).noise3_XYBeforeZ(freq(i) * x, freqY(i) * y,freq(i) * z).toFloat
[error] 152 res
[error] 153 }
What is weird is, sometimes after following these steps of adding some code, compiling and getting an error, cleaning the build and recompiling with no error and then removing said code and compiling to get a new error message, the apparent source of the new error might come from a completely different class, which was behaving just fine prior to that.
What is more, and this is actually my real issue, is that sometimes the change in code causes the error to persist despite cleaning the build.
It also doesn't help that the error message doesn't tell me much, and confusingly enough it seems to be pointing to the wrong source at times. I must confess however that I don't really understand how sbt works and I'm just using it to import some libraries, but I had no issues with it before.
EDIT: Apparently it's caused by the scala compiler, not sbt itself. Error message seems to be similar to this one: https://github.com/scala/bug/issues/9578
I've tested the code from that link and it does give the same type of error I was getting with my code (and the source presented in the error message is again irrelevant to the error). I am using breeze in my project, so that seems to be the source of the problem. I'll attempt to remove it from the project and see if the error still occurs.
I have managed to find the source of the problem, which as expected has nothing to do with sbt, but with Breeze. The problem is exactly the one in this open issue: https://github.com/scala/bug/issues/9578
My workaround was to stop using DenseVector[Int] (right now I am using DenseVector[Float] but will probably switch to a different linear algebra library soon).

How do Scala macro typechecks resolve identifiers to types?

I'm trying to create an annotation macro which can only be applied to a certain type. When I run my tests I see a type not found error when the annotation is applied to top level objects only.
My macro code:
trait Labelled[T] {
def label: T
}
#compileTimeOnly("DoSomethingToLabelled requires the macro paradise plugin")
class DoSomethingToLabelled extends StaticAnnotation {
def macroTransform(annottees: Any*): Any = macro DoSomethingToLabelled.impl
}
object DoSomethingToLabelled {
def impl(c: whitebox.Context)(annottees: c.Expr[Any]*): c.Expr[Any] = {
import c.universe._
annottees.map(_.tree).head match {
case expr # ModuleDef(mods: Modifiers, name: TermName, impl: Template) =>
println(showRaw(impl.parents))
val parentTypes = impl.parents.map(c.typecheck(_, c.TYPEmode))
if (parentTypes.exists(_.tpe <:< typeOf[Labelled[_]])) {
c.Expr[Any](expr)
} else {
c.abort(c.enclosingPosition, s"DoSomethingToLabelled can only be applied to a Labelled. Received types: $parentTypes")
}
}
}
}
My test code:
class DoSomethingToLabelledSpec extends Specification {
private def classPathUrls(cl: ClassLoader): List[String] = cl match {
case null => Nil
case u: java.net.URLClassLoader => u.getURLs.toList.map(systemPath) ++ classPathUrls(cl.getParent)
case _ => classPathUrls(cl.getParent)
}
private def systemPath(url: URL): String = {
Paths.get(url.toURI).toString
}
private def paradiseJarLocation: String = {
val classPath = classPathUrls(getClass.getClassLoader)
classPath.find(_.contains("paradise")).getOrElse {
throw new RuntimeException(s"Could not find macro paradise on the classpath: ${classPath.mkString(";")}")
}
}
lazy val toolbox = runtimeMirror(getClass.getClassLoader)
.mkToolBox(options = s"-Xplugin:$paradiseJarLocation -Xplugin-require:macroparadise")
"The DoSomethingToLabelled annotation macro" should {
"be applicable for nested object definitions extending Labelled" in {
toolbox.compile {
toolbox.parse {
"""
|import macrotests.Labelled
|import macrotests.DoSomethingToLabelled
|
|object Stuff {
| #DoSomethingToLabelled
| object LabelledWithHmm extends Labelled[String] {
| override val label = "hmm"
| }
|}
|""".stripMargin
}
} should not (throwAn[Exception])
}
"be applicable for top level object definitions extending Labelled" in {
toolbox.compile {
toolbox.parse {
"""
|import macrotests.Labelled
|import macrotests.DoSomethingToLabelled
|
|#DoSomethingToLabelled
|object LabelledWithHmm extends Labelled[String] {
| override val label = "hmm"
|}
|""".stripMargin
}
} should not (throwAn[Exception])
}
}
}
And my test log is:
sbt:macro-type-extraction> test
[info] Compiling 1 Scala source to C:\Users\WilliamCarter\workspace\macro-type-extraction\target\scala-2.11\classes ...
[info] Done compiling.
List(AppliedTypeTree(Ident(TypeName("Labelled")), List(Ident(TypeName("String")))))
List(AppliedTypeTree(Ident(TypeName("Labelled")), List(Ident(TypeName("String")))))
[info] DoSomethingToLabelledSpec
[info] The DoSomethingToLabelled annotation macro should
[info] + be applicable for nested object definitions extending Labelled
[error] scala.tools.reflect.ToolBoxError: reflective compilation has failed:
[error]
[error] exception during macro expansion:
[error] scala.reflect.macros.TypecheckException: not found: type Labelled
[error] at scala.reflect.macros.contexts.Typers$$anonfun$typecheck$2$$anonfun$apply$1.apply(Typers.scala:34)
[error] at scala.reflect.macros.contexts.Typers$$anonfun$typecheck$2$$anonfun$apply$1.apply(Typers.scala:28)
[error] at scala.reflect.macros.contexts.Typers$$anonfun$3.apply(Typers.scala:24)
[error] at scala.reflect.macros.contexts.Typers$$anonfun$3.apply(Typers.scala:24)
[error] at scala.reflect.macros.contexts.Typers$$anonfun$withContext$1$1.apply(Typers.scala:25)
[error] at scala.reflect.macros.contexts.Typers$$anonfun$withContext$1$1.apply(Typers.scala:25)
[error] at scala.reflect.macros.contexts.Typers$$anonfun$1.apply(Typers.scala:23)
[error] at scala.reflect.macros.contexts.Typers$$anonfun$1.apply(Typers.scala:23)
[error] at scala.reflect.macros.contexts.Typers$class.withContext$1(Typers.scala:25)
[error] at scala.reflect.macros.contexts.Typers$$anonfun$typecheck$2.apply(Typers.scala:28)
[error] at scala.reflect.macros.contexts.Typers$$anonfun$typecheck$2.apply(Typers.scala:28)
[error] at scala.reflect.macros.contexts.Typers$class.withWrapping$1(Typers.scala:26)
[error] at scala.reflect.macros.contexts.Typers$class.typecheck(Typers.scala:28)
[error] at scala.reflect.macros.contexts.Context.typecheck(Context.scala:6)
[error] at scala.reflect.macros.contexts.Context.typecheck(Context.scala:6)
[error] at macrotests.DoSomethingToLabelled$$anonfun$2.apply(DoSomethingToLabelled.scala:19)
[error] at macrotests.DoSomethingToLabelled$$anonfun$2.apply(DoSomethingToLabelled.scala:19)
[error] at scala.collection.immutable.List.map(List.scala:284)
[error] at macrotests.DoSomethingToLabelled$.impl(DoSomethingToLabelled.scala:19)
My debug printing tells me the extracted parent types are the same in each test but for some reason the top level object cannot resolve that the TypeName("Labelled") is actually a macrotests.Labelled. Is anyone able to help shed some light here? The macro appears to work outside of the testing context but I'd really like to understand what's going on so I can write some proper tests.
Try
toolbox.compile {
toolbox.parse {
"""
|import macrotests.DoSomethingToLabelled
|
|#DoSomethingToLabelled
|object LabelledWithHmm extends macrotests.Labelled[String] {
| override val label = "hmm"
|}
|""".stripMargin
}
}
or even
toolbox.compile {
toolbox.parse {
"""
|import macrotests.DoSomethingToLabelled
|
|#DoSomethingToLabelled
|object LabelledWithHmm extends _root_.macrotests.Labelled[String] {
| override val label = "hmm"
|}
|""".stripMargin
}
}
By the way, why do you need toolbox? Why not to write just
#DoSomethingToLabelled
object LabelledWithHmm extends Labelled[String] {
override val label = "hmm"
}
in tests? Then the fact that the code compiles will be checked at compile time rather than at runtime with toolbox.
https://github.com/scala/bug/issues?utf8=%E2%9C%93&q=is%3Aissue+is%3Aopen+%28toolbox+%26%26+%28import+%7C%7C+package%29%29
https://github.com/scala/bug/issues/6393
#xeno-by said:
It looks like we're doomed w.r.t this one.
The problem is that Scala reflection and reflective compiler (which is underlying toolboxes) use a different model of classfile loading than vanilla scalac does. Vanilla compiler has its classpath as a list of directories/jars on the filesystem, so it can exhaustively enumerate the packages on the classpath. Reflective compiler works with arbitrary classloaders, and classloaders don't have a concept of enumerating packages.
As a result, when a reflective compiler sees "math" having "import scala.; import java.lang." imports in the lexical context, it doesn't know whether that "math" stands for root.math, scala.math or java.lang.math. So it has to speculate and provisionally creates a package for root.math, which ends up being a wrong choice.
We could probably support a notion of "overloaded" packages, so that the compiler doesn't have to speculate and can store all the possible options, but that'd require redesign of reflection and probably of the typer as well.

Scala internal compiler error (possibly?)

Code runs fine in scala repl, will not compile as part of a scala source code base. Seems like the compiler is throwing some kind of an internal error.
I have a List[(String,(Int,Int))] I want to convert to Map[String,List[(Int,Int)] so that I can search it faster. It is basically a set of buckets indexed by the String component of the map that I can then search over and over using maybe binary search, once I have zeroed in on a bucket. I am trying to build the Map structure from the List structure, using a foldLeft operation.
val tpls:List[(String,(Int,Int))] = sm.compressedSegmentList.
map(_.split("|")).
map(s => (s(1), s(2).toInt, s(3).toInt)).
sortBy(_._1).
map(s => (s._1 -> (s._2,s._3)))
val chrBuckets:Map[String,List[(Int,Int)]] =
tpls.
foldLeft(Map().empty.asInstanceOf[String,List[(Int,Int)]])(
(y,x) => {
if (!y.contains(x._1)) y ++ Map(x._1->List(x._2))
else y ++ Map(x._1 -> (y(x._1):+x._2))
}
)
In the scala REPL, I can do the following:
scala> val l1 = List(("a",(1,2)),("a", (2,3)), ("a",(3,4)), ("b", (17,18)), ("b", (18,19)), ("c", (20,21)), ("d",(0,0)))
l1: List[(String, (Int, Int))] = List((a,(1,2)), (a,(2,3)), (a,(3,4)), (b,(17,18)), (b,(18,19)), (c,(20,21)), (d,(0,0)))
scala> l1.foldLeft(Map().empty.asInstanceOf[Map[String,List[(Int,Int)]]])((y,x) => if (!y.contains(x._1)) y ++ Map(x._1->List(x._2)) else y ++ Map(x._1 -> (y(x._1):+x._2)))
res88: Map[String,List[(Int, Int)]] = Map(a -> List((1,2), (2,3), (3,4)), b -> List((17,18), (18,19)), c -> List((20,21)), d -> List((0,0)))
However, when I compile my project using "sbt compile", it fails to compile by throwing up, such as:
[error] last tree to typer: Ident(scala)
[error] tree position: line 26 of /usr/home/maketo/util/SegmentMatcher.scala
[error] tree tpe: scala.type
[error] symbol: final package scala
[error] symbol definition: final package scala (a ModuleSymbol)
[error] symbol package: <none>
[error] symbol owners: package scala
[error] call site: class SegmentMatcher in package util in package util
[error]
[error] == Source file context for tree position ==
[error]
[error] 23 // within the bucket we can do binary search on "segStart" and "segEnd" fields
[error] 24 // after we sort first by segStart and then segEnd
[error] 25 val chrBuckets:Map[String,List[(Int,Int)]] = tpls.
[error] 26 foldLeft(Map().empty.asInstanceOf[String,List[(Int,Int)]])((y,x) => { if (!y.contains(x._1)) y ++ Map(x._1->List(x._2)) else y ++ Map(x._1 -> (y(x._1):+x._2))})
[error] 27
[error] 28 // take a segment and match it against a segmentation
[error] 29 // return segment ID or None
[error] at scala.reflect.internal.SymbolTable.throwAssertionError(SymbolTable.scala:183)
[error] at scala.tools.nsc.typechecker.Typers$Typer.vanillaAdapt$1(Typers.scala:1189)
[error] at scala.tools.nsc.typechecker.Typers$Typer.adapt(Typers.scala:1243)
[error] at scala.tools.nsc.typechecker.Typers$Typer.typed(Typers.scala:5740)
[error] at scala.tools.nsc.typechecker.Typers$Typer.$anonfun$typed1$60(Typers.scala:5142)
[error] at scala.tools.nsc.typechecker.Typers$Typer.typedTypeSelectionQualifier$1(Typers.scala:5142)
[error] at scala.tools.nsc.typechecker.Typers$Typer.typedSelectOrSuperCall$1(Typers.scala:5150)
.. snipped for brevity, to this:
[error] (Compile / compileIncremental) java.lang.AssertionError: assertion failed:
[error] Context(SegmentMatcher.scala) {
[error] owner = value chrBuckets
[error] tree = Apply:tpls.foldLeft(Map().empty.asInstanceOf[String, List[scala.Tuple2[Int,
[error] scope = 0 decls
[error] contextMode = MacrosEnabled TypeConstructorAllowed
[error] outer.owner = value chrBuckets
[error] }
[error] while compiling: /usr/home/maketo/SegmentMatcher.scala
[error] during phase: typer
[error] library version: version 2.12.8
[error] compiler version: version 2.12.8
Yes, that's an internal compiler error. The following snippet crashes 2.12.5 - 2.12.8 and 2.13.0-M5:
Map().asInstanceOf[Int, List[(Int, Int)]]
Workaround (that's what you should have done anyway, compiler error or not):
Map.empty[Int, List[(Int, Int)]]
I understand you are looking to figure out whether the above was a compiler error, but, if you are looking for a workaround, see if one of these 2 snippets work for you.
// Initialize
val tpls: List[(String, (Int, Int))] = List(
"foo" -> (1, 2),
"bar" -> (3, 4),
"foo" -> (5, 6)
)
// Option 1
tpls.groupBy(_._1).mapValues(_.map(_._2))
// returns Map(foo -> List((1,2), (5,6)), bar -> List((3,4)))
// Option 2
val emptyMap = Map.empty[String, List[(Int,Int)]].withDefaultValue(List.empty[(Int, Int)])
tpls.foldLeft(emptyMap) { case (mapSoFar, (segmentName, interval)) =>
mapSoFar.updated(segmentName, interval::mapSoFar(segmentName))
}
// returns Map(foo -> List((5,6), (1,2)), bar -> List((3,4)))
The second result is in reverse order, but, I just wanted to note that for lists :: is significantly less expensive that :+ (in case you don't care about the order)

Symbol 'type cats.MonadFilter' is missing fromthe classpath

I am reading this tutorial on tagless final.
Based on this I have defined my dependencies as
object Dependencies {
lazy val scalaTest = "org.scalatest" %% "scalatest" % "3.0.5"
lazy val cats = "org.typelevel" %% "cats-core" % "1.2.0"
lazy val monix = "io.monix" %% "monix" % "2.3.3"
lazy val monixCats = "io.monix" %% "monix-cats" % "2.3.3"
}
The following is my code
// future
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent._
import scala.concurrent.duration._
// cats
import cats.Monad
import cats.implicits._
// monix
import monix.eval.Task
import monix.cats._
import monix.cats.reverse._
trait ProductRepository[M[_]] {
def findProduct(productId: ProductId) : M[Option[Product]]
def saveProduct(product: Product) : M[Unit]
def incrementProductSales(productId: ProductId, quantity: Long) : M[Unit]
}
class ProductRepositoryWithFuture extends ProductRepository[Future] {
def findProduct(productId: ProductId) : Future[Option[Product]] = {
Future.successful(Some(Product(productId, "foo")))
}
def saveProduct(product: Product) : Future[Unit] = {
Future.successful()
}
def incrementProductSales(productId: ProductId, quanity: Long) : Future[Unit] = {
Future.successful()
}
}
class ProductRepositoryWithTask extends ProductRepository[Task] {
def findProduct(productId: ProductId) : Task[Option[Product]] = {
Task.now(Some(Product(productId, "foo")))
}
def saveProduct(product: Product) : Task[Unit] = {
Task.unit
}
def incrementProductSales(productId: ProductId, quantity: Long) : Task[Unit] = {
Task.unit
}
}
But I get bunch of errors. It seems that the version of cats which I am using is not compatible with the one Monix uses.
I also tried to remove my cats dependency and just imported monix so that monix pulls in its own version of cats. but even that doesn't compile.
error] /Users/foobar/code/tagless/src/main/scala/example/Hello.scala:54:24: Symbol 'type cats.MonadFilter' is missing fromthe classpath.
[error] This symbol is required by 'method monix.cats.MonixToCatsCore7.monixToCatsMonadFilter'.
[error] Make sure that type MonadFilter is in your classpath and check for conflicting dependencies with `-Ylog-classpath`.
[error] A full rebuild may help if 'MonixToCatsCore7.class' was compiled against an incompatible version of cats.
[error] repo.findProduct(id).flatMap{
[error] ^
[error] /Users/foobar/code/tagless/src/main/scala/example/Hello.scala:54:23: diverging implicit expansion for type monix.types.Comonad[M]
[error] starting with method catsToMonixComonad in trait CatsCoreToMonix5
[error] repo.findProduct(id).flatMap{
[error] ^
[error] /Users/foobar/code/tagless/src/main/scala/example/Hello.scala:54:28: value flatMap is not a member of type parameter M[Option[example.Application.Product]]
[error] repo.findProduct(id).flatMap{
[error] ^
[error] /Users/foobar/code/tagless/src/main/scala/example/Hello.scala:56:30: value copy is not a member of Any
[error] val newProduct = p.copy(name = name)
[error] ^
[error] /Users/foobar/code/tagless/src/main/scala/example/Hello.scala:56:40: reassignment to val
[error] val newProduct = p.copy(name = name)
[error] ^
[error] /Users/foobar/code/tagless/src/main/scala/example/Hello.scala:57:27: diverging implicit expansion for type monix.types.MonadError[M,E]
[error] starting with method catsToMonixMonadError in trait CatsCoreToMonix3
[error] repo.saveProduct(newProduct).map(_ => Some(p))
[error] ^
[error] /Users/foobar/code/tagless/src/main/scala/example/Hello.scala:57:40: value map is not a member of type parameter M[Unit]
[error] repo.saveProduct(newProduct).map(_ => Some(p))
[error] ^
[error] /Users/foobar/code/tagless/src/main/scala/example/Hello.scala:59:16: diverging implicit expansion for type cats.Comonad[M]
[error] starting with method monixToCatsComonad in trait MonixToCatsCore5
[error] Monad[M].pure(None)
[error] ^
[error] 8 errors found
The errors are caused by incompatibilities between your dependencies.
For example monix 2.3.3 depends on cats 0.9.0 while you're trying to use 1.2.0 which is binary incompatible.
You should try either upgrading monix to 3.x or downgrading cats to 0.9.0.
P.S. The transition from cats 0.9.0 to 1.x has a lot of breaking changes and you have to make sure that all libraries you're using are compiled against the same (or at least binary compatible) version of cats.

How can I use Scala macros to create an object?

I am trying to create a Scala macro that will generate an object - something like
object SomeEnum {
sealed abstract class Enum(name: String)
case object Option1 extends Enum("option1")
case object Option2 extends Enum("option2")
private val elements: Seq[Enum] = Seq(Option1, Option2)
def apply(code: String): Enum = {
...
}
}
I thought I might be able to create a macro createEnum, so I could just put
createEnum("SomeEnum", "Option1", "Option2") into my code and have it generate. Seems like it's calling out for a macro.
But I must not be understanding macros. I am using Scala 2.11.6, and just to try to get something working, I created the following:
object createEnumObj {
def createEnumImpl(c: scala.reflect.macros.whitebox.Context)(ename: c.Expr[String]): c.universe.ModuleDef = {
import c.universe._
val Literal(Constant(s_ename: String)) = ename.tree
val oname = TermName(s_ename)
val barLine = q"val bar: Int = 5"
q"object $oname { $barLine }"
}
def createEnum(ename: String): Unit = macro createEnumImpl
}
This is in a separate project - everything seems to be compiling for it OK.
If I stick a call to createEnumObj.createEnum into some source and try to compile that, I get a billion lines (give or take a few) of exception output, which seems to repeat something like this:
[error] (main/compile:compile) java.lang.AssertionError: assertion failed:
[error] object foo extends scala.AnyRef {
[error] def <init>() = {
[error] super.<init>();
[error] ()
[error] };
[error] val bar: Int = 5
[error] }
[error] while compiling: /Users/bob/ICL/ironcore-id/src/main/scala/package.scala
[error] during phase: typer
[error] library version: version 2.11.6
[error] compiler version: version 2.11.6
[error] reconstructed args: -Xfuture ...
error]
[error] last tree to typer: term foo
[error] tree position: line 8 of /Users/bob/ICL/ironcore-id/src/main/scala/package.scala
[error] symbol: <none>
[error] symbol definition: <none> (a NoSymbol)
[error] symbol package: <none>
[error] symbol owners:
[error] call site: <none> in <none>
[error]
[error] == Source file context for tree position ==
[error]
[error] 5 type DateTime = Int
[error] 6
[error] 7 createEnumObj.createEnum("foo")
[error] 8
[error] 9 }
[error] Total time: 2 s, completed Jun 18, 2015 2:46:05 PM
What I am trying to do doesn't seem too dissimilar to this question, but I'm obviously missing something. Any ideas about how to accomplish this would be gratefully accepted.
Thanks,
Bob