Background
I'm trying to add a #Prisms annotation to Monocle that will work as follows. Given:
#Prisms sealed trait Foo
case object A extends Foo
case object B extends Foo
it will generate a companion object for Foo:
object Foo {
val a = monocle.macros.GenPrism.apply[Foo, A]
val b = monocle.macros.GenPrism.apply[Foo, B]
}
(or if the companion object already exists, it will add those methods to it).
The macro relies on directKnownSubclasses to find A and B, so there will be a note in the Monocle readme recommending that people use Typelevel Scala. I've configured Monocle to use TLS on my branch.
First attempt
I tried using TypeNames to represent the parent type Foo and child type (A or B):
def findSubclasses(typeName: TypeName): Set[TypeName] = {
// Note: disable macros to prevent stack overflow caused by infinite typing loop!
val tpe = c.typecheck(Ident(typeName), mode = c.TYPEmode, silent = true, withMacrosDisabled = true)
tpe.symbol.asClass.knownDirectSubclasses.map(_.asType.name)
}
def prisms(parentTypename: TypeName, childNames: Set[TypeName]): Set[Tree] = childNames.map { childName =>
val prismName = TermName(prefix + childName.decodedName.toString.toLowerCase)
q"""val $prismName = monocle.macros.GenPrism.apply[$parentTypename, $childName]"""
}
This generates what looks like reasonable code:
{
sealed trait Foo;
object Foo {
val a = monocle.macros.GenPrism.apply[Foo, A];
val b = monocle.macros.GenPrism.apply[Foo, B]
};
()
}
But it doesn't seem to work:
[error] /Users/chris/code/Monocle/test/shared/src/test/scala/other/PrismsAnnotationSpec.scala:5: not found: type A
[error] #monocle.macros.Prisms sealed trait Foo
[error] ^
[error] one error found
Second attempt
I also tried using TypeSymbols instead of TypeNames:
def prisms(parentTypename: TypeName, childSymbols: Set[TypeSymbol]): Set[Tree] = {
val parentSymbol: TypeSymbol =
c.typecheck(Ident(parentTypename), mode = c.TYPEmode, silent = true, withMacrosDisabled = true)
.symbol
.asType
childSymbols.map { childSymbol =>
val prismName = TermName(prefix + childSymbol.name.decodedName.toString.toLowerCase)
q"""val $prismName = monocle.macros.GenPrism.apply[$parentSymbol, $childSymbol]"""
}
}
But this gives me the following error, so I'm guessing the generated tree is invalid:
[error] /Users/chris/code/Monocle/test/shared/src/test/scala/other/PrismsAnnotationSpec.scala:5: macro annotation could not be expanded (the most common reason for that is that you need to enable the macro paradise plugin; another possibility is that you try to use macro annotation in the same compilation run that defines it)
[error] #monocle.macros.Prisms sealed trait Foo
[error] ^
[error] one error found
Third attempt
I also tried turning the TypeSymbols into Types:
def prisms(parentTypename: TypeName, childSymbols: Set[TypeSymbol]): Set[Tree] = {
val parentSymbol: TypeSymbol =
c.typecheck(Ident(parentTypename), mode = c.TYPEmode, silent = true, withMacrosDisabled = true)
.symbol
.asType
childSymbols.map { childSymbol =>
val prismName = TermName(prefix + childSymbol.name.decodedName.toString.toLowerCase)
q"""val $prismName = monocle.macros.GenPrism.apply[${parentSymbol.toType}, ${childSymbol.toType}]"""
}
}
But I get the same error, saying the annotation could not be expanded.
Now I'm out of ideas.
How to reproduce
The complete code is available on this branch: https://github.com/cb372/Monocle/tree/sealed-trait-macro-annotation.
First attempt
Second attempt
Third attempt
The macro is used in test/shared/src/test/scala/other/PrismsAnnotationSpec.scala. To compile it, run sbt test:compile.
Related
OUTLINE
I have an API that looks something like this:
package com.example
object ExternalApi {
def create[T <: SpecialElement](elem: T): TypeConstructor[T] =
TypeConstructor(elem)
def create1[T <: SpecialElement](elem: T): TypeConstructor[T] =
TypeConstructor(elem)
def create2[T <: SpecialElement](elem: T): TypeConstructor[T] =
TypeConstructor(elem)
//...
}
object MyApi {
def process[T <: TypeConstructor[_ <: SpecialElement]](
l: T,
metadata: List[String]): T = {
println("I've been called!")
//do some interesting stuff with the List's type parameter here
l
}
}
case class TypeConstructor[E](elem: E)
trait SpecialElement
The ExternalApi (which is actually external to my lib, so no modifying that) has a series of calls that I'd like to automatically wrap with MyApi.process calls, with the metadata argument derived from the final type of T.
To illustrate, the calls to be wrapped could have any form, including nested calls, and calls within other AST subtree types (such as Blocks), e.g. :
package com.example.test
import com.example.{ExternalApi, SpecialElement}
object ApiPluginTest extends App {
//one possible form
val targetList = ExternalApi.create(Blah("name"))
//and another
ExternalApi.create2(ExternalApi.create1(Blah("sth")).elem)
//and yet another
val t = {
val sub1 = ExternalApi.create(Blah("anything"))
val sub2 = ExternalApi.create1(sub1.elem)
sub2
}
}
case class Blah(name: String) extends SpecialElement
Since compiler plugins handle matching structures within ASTs recursively "for free", I've decided to go with them.
However, due to the fact that I need to match a specific type signature, the plugin follows the typer phase.
Here's the code of the PluginComponent:
package com.example.plugin
import com.example.{SpecialElement, TypeConstructor}
import scala.tools.nsc.Global
import scala.tools.nsc.plugins.PluginComponent
import scala.tools.nsc.transform.Transform
class WrapInApiCallComponent(val global: Global)
extends PluginComponent
with Transform {
protected def newTransformer(unit: global.CompilationUnit) =
WrapInApiCallTransformer
val runsAfter: List[String] = List("typer") //since we need the type
val phaseName: String = WrapInApiCallComponent.Name
import global._
object WrapInApiCallTransformer extends Transformer {
override def transform(tree: global.Tree) = {
val transformed = super.transform(tree)
transformed match {
case call # Apply(_, _) =>
if (call.tpe != null && call.tpe.finalResultType <:< typeOf[
TypeConstructor[_ <: SpecialElement]]) {
println(s"Found relevant call $call")
val typeArguments = call.tpe.typeArgs.map(_.toString).toList
val listSymbOf = symbolOf[List.type]
val wrappedFuncSecondArgument =
q"$listSymbOf.apply(..$typeArguments)"
val apiObjSymbol = symbolOf[com.example.MyApi.type]
val wrappedCall =
q"$apiObjSymbol.process[${call.tpe.finalResultType}]($call, $wrappedFuncSecondArgument)"
//explicit typing, otherwise later phases throw NPEs etc.
val ret = typer.typed(wrappedCall)
println(showRaw(ret))
println("----")
ret
} else {
call
}
case _ => transformed
}
}
}
}
object WrapInApiCallComponent {
val Name = "api_embed_component"
}
This seems to resolve identifiers, as well as types, correctly, outputting e.g.:
Apply(TypeApply(Select(TypeTree().setOriginal(Ident(com.example.MyApi)), TermName("process")), List(TypeTree())), List(Apply(TypeApply(Select(Select(Select(Ident(com), com.example), com.example.MyApi), TermName("create")), List(TypeTree())), List(Apply(Select(Ident(com.example.test.Blah), TermName("apply")), List(Literal(Constant("name")))))), Apply(TypeApply(Select(TypeTree().setOriginal(Ident(scala.collection.immutable.List)), TermName("apply")), List(TypeTree())), List(Literal(Constant("com.example.test.Blah"))))))
Unfortunately, I get an error during compilation starting with:
scala.reflect.internal.FatalError:
[error]
[error] Unexpected tree in genLoad: com.example.MyApi.type/class scala.reflect.internal.Trees$TypeTree at: RangePosition([projectpath]/testPluginAutoWrap/compiler_plugin_test/src/main/scala/com/example/test/ApiPluginTest.scala, 108, 112, 112)
[error] while compiling: [projectpath]/testPluginAutoWrap/compiler_plugin_test/src/main/scala/com/example/test/ApiPluginTest.scala
[error] during phase: jvm
[error] library version: version 2.12.4
[error] compiler version: version 2.12.4
[error] reconstructed args: -Xlog-implicits -classpath [classpath here]
[error]
[error] last tree to typer: TypeTree(class String)
[error] tree position: line 23 of [projectpath]/testPluginAutoWrap/compiler_plugin_test/src/main/scala/com/example/test/ApiPluginTest.scala
QUESTION
It looks I'm screwing something up with type definitions, but what is it?
Specifically:
How do I correctly wrap every ExternalApi.createX call with an MyApi.process call, constrained by the requirements provided above?
NOTES
Given the amount of boilerplate required, I've set up a complete example project. It's available here.
The answer does not have to define a compiler plugin. If you're able to cover all the relevant calls with a macro, that is fine as well.
Originally the wrapped call was to something like: def process[T <: TypeConstructor[_ <: SpecialElement] : TypeTag](l: T): T, the setup here is actually a workaround. So if you are able to generate a wrapped call of this type, i.e. one that includes a runtime TypeTag[T], that's fine as well.
I am trying to write a type provider macro that uses the vampiric methods trick described in this gist; this is a minimal model of my implementation:
object DeriveFamily {
def minimal: Any = macro DeriveFamilyMacros.minimal
}
object DeriveFamilyMacros {
class body(tree: Any) extends StaticAnnotation
def bodyImpl(c: Context) = {
import c.universe._
val field = c.macroApplication.symbol
val bodyAnn = field.annotations.filter(_.tree.tpe <:< typeOf[body]).head
bodyAnn.tree.children.tail.head
}
def minimal(c: Context): c.Tree = {
import c.universe._
q"""object Foobar { val _x = "X"; #DeriveFamilyMacros.body(Foobar._x) def x: String = macro DeriveFamilyMacros.bodyImpl }; Foobar"""
}
}
This is a dumb example, the real implementation tries to use this trick to create bundles of derived typeclass instances that are accessible without reflective calls. The issue that both the real version and this one have is that they work fine if I use them in a function but they crash the compiler if I try to use them at the root of a class or object definition:
object Working {
def x = {
val foobar = DeriveFamily.minimal
println(foobar.x)
}
x // Prints "X"
}
object NotWorking {
val foobar = DeriveFamily.minimal
println(foobar.x)
/*
[trace] Stack trace suppressed: run last common/test:compileIncremental for the full output.
[error] (common/test:compileIncremental) java.lang.IllegalArgumentException: Could not find proxy for var Foobar$module: runtime.VolatileObjectRef in List(variable Foobar$module, value foobar, Object NotWorking, package <root>) (currentOwner= value <local NotWorking> )
*/
}
Any help in fixing this will be very welcome, thanks.
I am currently reading Scala in depth and I struggle with a point about existential types.
Using those sources : https://github.com/jsuereth/scala-in-depth-source/blob/master/chapter6/existential-types/existential.scala
with openjdk 7 and scala 2.10.3
The following instructions gives me a error :
val x = new VariableStore[Int](12)
val d = new Dependencies {}
val t = x.observe(println)
d.addHandle(t)
<console>:14: error: method addHandle in trait Dependencies cannot be accessed in types.Dependencies
Access to protected method addHandle not permitted because
enclosing object $iw is not a subclass of
trait Dependencies in package types where target is defined
d.addHandle(t)
^
And I can't find out why and how I arrive to this error.
Edit 1 :
I added the following code from Kihyo's answer :
class MyDependencies extends Dependencies {
override def addHandle(handle: Ref) = super.addHandle(handle)
}
val x = new VariableStore[Int](12)
val d = new MyDependencies
val t = x.observe(println)
d.addHandle(t) //compiles
It make addHandle public instead of protected.
Now I have the following error message :
type mismatch; found : x.Handle (which expands to) x.HandleClass required: d.Ref (which
expands to) x.Handle forSome { val x: sid.types.obs.Observable }
HandleClass is a Handle and Ref is a Handle of any Observer (if I get it right) so the value t should be accepted as a correct type for the exception.
In the trait Dependencies, addHandle is defined like that:
protected def addHandle(handle : Ref) : Unit
protected means, only subclasses can access this method and thats why you get the error. (which basically tells you exactly that)
Your code could work when you create a subclass that makes addHandle public:
class MyDependencies extends Dependencies {
override def addHandle(handle: Ref) = super.addHandle(handle)
}
val x = new VariableStore[Int](12)
val d = new MyDependencies
val t = x.observe(println)
d.addHandle(t) //compiles
But I have no idea about that example and what you want to do with it.
#Edit1:
I get the same error as you, but I can't explain why. It worked for me when I extend App instead of having a main-method:
object TestObs extends App {
val x = new VariableStore[Int](12)
val d = new MyDependencies
val t = x.observe(println)
d.addHandle(t)
}
Maybe someone else has some insight on this.
In my DSL I want this functionality:
class Test {
val compA = dependant(true, true)(Component("parameters"))
//and this shortcut:
val compB = dependant Component("parameters")
}
where:
def dependant(onEnable: Boolean, onDisable: Boolean)(c: Component): Component = {
//...
}
def dependant(c: Component): Component = dependant(false, true)(c)
all is fine, however, I cannot use this syntax:
val compB = dependant Component("parameters")
because it says
ambiguous reference to overloaded definition, both method dependant in
class Test of type (onEnable: Boolean, onDisable: Boolean)(c:
Component)Component and method dependant in class Test of type (c:
Component)Component match expected type ?
But if I enclose the parameter in parenthesis:
val compB = dependant(Component("parameters"))
the error is gone. Obviously, the compiler fails in desugarating the parenthesisless case. Is this expected or am I doing something wrong? If this is expected, then Why? How can I reclaim the ability to use the method dependant as a prefix, without parenthesis?
In dependant Component("parameters") you are trying to use a prefix notation to call the dependant. Scala's support for prefix notation is limited.
See Scala - Prefix Unary Operators.
An alternative is to use a postfix notation (as in Component("parameters") dependant).
If you can modify the implementaiton of Componenet, this simply means adding the dependant methods to Component:
class Component(name: String) {
def dependant: Component = //...
def dependant(onEnable: Boolean, onDisable: Boolean): Component = {
//...
}
}
class Test {
val compA = Component("parameters") dependant(true, true)
val compB = Component("parameters") dependant
}
If you can't modify Component, you can use the "pimp my library idiom".
See http://www.decodified.com/scala/2010/12/02/the-quickpimp-pattern.html for a short introduction to this idiom (along with a warning on using an anonymous class as below):
case class Component(name: String)
implicit def toPostifxDependentOps( c: Component ) = new {
def dependant: Component = dependant(false, true)
def dependant(onEnable: Boolean, onDisable: Boolean): Component = {
//...
}
}
class Test {
val compA = Component("parameters") dependant(true, true)
val compB = Component("parameters") dependant
}
Writing myObject functionName paraminstead of myObject.functionName(param) works as expected if you propvide an object. If you don't, the compiler will be lost. For example:
scala> println("Hello")
Hello
scala> println "Hello"
<console>:1: error: ';' expected but string literal found.
println "Hello"
^
A possible workaround: create an object to wrap your method:
scala> case class Component(name: String, components: Option[Component] = None)
defined class Component
scala> object depends {def on(c: Component) = Component("dependant", Some(c))}
defined module depends
scala> depends on Component("foo")
res3: Component = Component(dependant,Some(Component(foo,None)))
The following macro, extracted from a larger example, is supposed to create a tree with nothing but a reference to this:
def echoThisImpl(c:Context): c.Expr[Any] = {
import c.universe._
val selfTree = This(c.enclosingClass.symbol)
c.Expr[AnyRef](selfTree)
}
def echoThis: Any = macro CallMacro.echoThisImpl
But a call to echoThis such as
object Testing extends App {
val thisValue = CallMacro.echoThis
println(thisValue)
}
fails to compile, with the message
[error] /home/rafael/dev/scala/goose/goose-macros/src/test/scala/Testing.scala:8: type mismatch;
[error] found : <noprefix>
[error] required: Any
[error] val thisValue = CallMacro.echoThis
If I set the -Ymacro-debug-lite flag the generated tree is This(newTermName("<local Testing>")).
There are two options of achieving what you want:
1) Use This(tpnme.EMPTY). Currently this doesn't compile, so you'll have to use This(newTypeName("")) instead, but in RC1 this will be fixed.
2) Use This(c.enclosingClass.symbol.asModule.moduleClass). Currently this doesn't work, because of https://issues.scala-lang.org/browse/SI-6394, but in RC1 this will be fixed.