some of my scala annotation macro do not seem to get expanded, is there a way to inspect/log which expression gets passed to my annotation macro at compile time, because right now the code doesn't even compile...
def virtualize(tree: Tree): Tree = atPos(tree.pos) {
tree match {
case x =>
println("LOG: "+tree) //will only be printed during runtime
c.warning(tree.pos, "LOG: "+tree) //will only generate code for a warning
super.transform(tree)
}
}
Is there a way to issue compiler warnings in annotation macros?
Thanks a lot!
if you use idea then open terminal enter sbt ~compilewill Time compilation
then you can see compilation info log like follow in the terminal :
D:\git\scala-macro-example>sbt ~compile
[info] Loading project definition from D:\git\scala-macro-example\project
[info] Set current project to scala-macro-example (in build file:/D:/git/scala-macro-example/)
[info] Updating {file:/D:/git/scala-macro-example/}root...
[info] Resolving jline#jline;2.12.1 ...
[info] Done updating.
[info] Compiling 2 Scala sources to D:\git\scala-macro-example\module\macros\target\scala-2.11\classes...
[error] D:\git\scala-macro-example\module\macros\src\main\scala\macross\teach\WithHello.scala:16: type ClassWithFunc is not a member of package macross.annotat
ion
[error] class WithHelloImpl(val c: Context) extends macross.annotation.ClassWithFunc{
[error]
after
object ShowInfo {
class Show extends StaticAnnotation {
def macroTransform(annottees: Any*): Any = macro ShowImpl.apply
}
class ShowImpl(val c: Context) {
import c.universe._
def showInfo(s: String) =
c.info(c.enclosingPosition, s.split("\n").mkString("\n |---macro info---\n |", "\n |", ""), true)
def apply(annottees: c.Expr[Any]*): c.Expr[Any] = {
val a: Seq[c.universe.Tree] = annottees.map(_.tree)
showInfo(show(a.head))
c.Expr[Any](Block(a.toList, Literal(Constant(()))))
}
}
}
useing like following:
object ShowInfoUsing {
trait SuperTrait
class SuperClass
#ShowInfo.Show
class ShowInfoUsing(val i: Int = 1) extends SuperClass with SuperTrait {
def f = 1
val a = 1
}
}
you can see info logo in the terminal
[info] |---macro info---
[info] |class ShowInfoUsing extends SuperClass with SuperTrait {
[info] | <paramaccessor> val i: Int = _;
[info] | def <init>(i: Int = 1) = {
[info] | super.<init>();
[info] | ()
[info] | };
[info] | def f = 1;
[info] | val a = 1
[info] |}
[info] #ShowInfo.Show
[info]
Related
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.
I have a exception with actors due to how it is instanciated in my tests.
I'm using Play Scala 2.5 with the provided akka library.
Here is my controller:
class MyController #Inject()(implicit context: ExecutionContext, val messagesApi: MessagesApi, system: ActorSystem) extends Controller with I18nSupport {
val (out, channel) = Concurrent.broadcast[String]
val listenerActor = system.actorOf(Listener.props, "listener")
listenerActor ! Start(channel)
def stream = Action { implicit req =>
val source = Source.fromPublisher(Streams.enumeratorToPublisher(out))
Ok.chunked(source via EventSource.flow).as("text/event-stream")
}
def myAction = Action.async {
listenerActor ! NewMessage("Action myAction call")
}
}
Here is my actor :
object Listener {
def props = Props[Listener]
case class Start(out: Concurrent.Channel[String])
case class NewMessage(message: String)
}
class Listener extends Actor {
import Listener._
var out: Option[Concurrent.Channel[String]] = None
def receive = {
case Start(out) => this.out = Some(out)
case NewMessage(msg) => this.out.map(_.push("{ \"message\": \"" + msg + "\" }"))
}
}
And my test :
class MyControllerSpec extends PlaySpec with OneAppPerSuite with ScalaFutures with MockitoSugar {
val messagesApi = app.injector.instanceOf[MessagesApi]
val ec = app.injector.instanceOf[ExecutionContext]
val actorSystem = app.injector.instanceOf[ActorSystem]
val injector = new GuiceInjectorBuilder()
.overrides(bind[MessagesApi].toInstance(messagesApi))
.overrides(bind[ExecutionContext].toInstance(ec))
.overrides(bind[ActorSystem].toInstance(actorSystem))
.injector
def myController = injector.instanceOf(classOf[MyController])
"MyController" should {...}
}
All my tests fail with the exception :
com.google.inject.ProvisionException: Unable to provision, see the following errors:
[info]
[info] 1) Error injecting constructor, akka.actor.InvalidActorNameException: actor name [listener] is not unique!
[info] at controllers.MyController.<init>(MyController.scala:29)
[info] while locating controllers.MyController
[info]
[info] 1 error
[info] at at com.google.inject.internal.InjectorImpl$2.get(InjectorImpl.java:1025)
[info] at com.google.inject.internal.InjectorImpl.getInstance(InjectorImpl.java:1051)
[info] at play.api.inject.guice.GuiceInjector.instanceOf(GuiceInjectorBuilder.scala:405)
[info] at controllers.MyControllerSpec.myController(MyControllerSpec.scala:33)
[info] at controllers.MyControllerSpec$$anonfun$1$$anonfun$apply$mcV$sp$7.apply$mcV$sp(MyControllerSpec.scala:94)
[info] at controllers.MyControllerSpec$$anonfun$1$$anonfun$apply$mcV$sp$7.apply(MyControllerSpec.scala:92)
[info] at controllers.MyControllerSpec$$anonfun$1$$anonfun$apply$mcV$sp$7.apply(MyControllerSpec.scala:92)
[info] at org.scalatest.Transformer$$anonfun$apply$1.apply$mcV$sp(Transformer.scala:22)
[info] at org.scalatest.OutcomeOf$class.outcomeOf(OutcomeOf.scala:85)
[info] at org.scalatest.OutcomeOf$.outcomeOf(OutcomeOf.scala:104)
[info] ...
[info] Cause: akka.actor.InvalidActorNameException: actor name [listener] is not unique!
[info] at akka.actor.dungeon.ChildrenContainer$NormalChildrenContainer.reserve(ChildrenContainer.scala:130)
[info] at akka.actor.dungeon.Children$class.reserveChild(Children.scala:130)
[info] at akka.actor.ActorCell.reserveChild(ActorCell.scala:374)
[info] at akka.actor.dungeon.Children$class.makeChild(Children.scala:268)
[info] at akka.actor.dungeon.Children$class.attachChild(Children.scala:46)
[info] at akka.actor.ActorCell.attachChild(ActorCell.scala:374)
[info] at akka.actor.ActorSystemImpl.actorOf(ActorSystem.scala:591)
[info] at controllers.MyController.<init>(MyController.scala:34)
[info] at controllers.MyController$$FastClassByGuice$$5133fbab.newInstance(<generated>)
[info] at com.google.inject.internal.cglib.reflect.$FastConstructor.newInstance(FastConstructor.java:40)
How to organize the code so that my actor is instanciated properly ?
=========================================================================
Update
I fixed the controller code to have it work. It is not using dependency injection anymore.
class MyController #Inject()(implicit context: ExecutionContext, val messagesApi: MessagesApi) extends Controller with I18nSupport {
val (out, channel) = Concurrent.broadcast[String]
val listenerActor = ActorSystem("listener").actorOf(Props[Listener])
listenerActor ! Start(channel)
def stream = Action { implicit req =>
val source = Source.fromPublisher(Streams.enumeratorToPublisher(out))
Ok.chunked(source via EventSource.flow).as("text/event-stream")
}
def myAction = Action.async {
listenerActor ! NewMessage("Action myAction call")
}
}
And remove the code that was injecting the ActorSystem in the test.
ActorSystem("a name").actorOf(Props[youractor])
Check if this code can help you, it needs a constract name.
I have a code like
test("mockito test") {
class ToTest {
def run(maybe: Option[Int], q: Option[Int] = None): Int = 42
}
val mockTest = mock[ToTest]
when(mockTest.run(None, None)).thenReturn(98)
mockTest.run(None)
verify(mockTest, times(1)).run(None, None)
}
Which fails with
[info] - mockito test *** FAILED ***
[info] org.mockito.exceptions.verification.junit.ArgumentsAreDifferent: Argument(s) are different! Wanted:
[info] toTest$1.run(None, None);
[info] -> at xxx$$anonfun$3.apply$mcV$sp(xxx.scala:55)
[info] Actual invocation has different arguments:
[info] toTest$1.run(None, null);
Or another scenario
test("mockito test") {
class ToTest {
def run(maybe: Option[Int], q: Int = 5): Int = 42
}
val mockTest = mock[ToTest]
when(mockTest.run(None, 5)).thenReturn(101)
mockTest.run(None)
verify(mockTest, times(1)).run(None, 5)
}
which fails with
[info] - mockito test *** FAILED ***
[info] org.mockito.exceptions.verification.junit.ArgumentsAreDifferent: Argument(s) are different! Wanted:
[info] toTest$1.run(None, 5);
[info] -> at xxx$$anonfun$3.apply$mcV$sp(xxx.scala:55)
[info] Actual invocation has different arguments:
[info] toTest$1.run(None, 0);
I guess it's because there are no default parameters in Java. Is there any workaround for it?
Thank you.
I guess that's because of CGLIB (or Byte Buddy in case of 2.0 beta) generates the code in that case, not Scala compiler, hence the default parameters will be always null.
A workaround could be (at least in some cases) to use spy instead:
val mockTest = spy(classOf[ToTest])
Sorry, no sugar syntax for it in ScalaTest.
My understanding is that scala.util.Try wraps exceptions thrown any code inside the Try { ... } block.
Here is a simple example that wraps a NullPointerException.
object TryDemo extends App {
import scala.util.{Failure, Success, Try}
def doSomething(i: Int): String = {
if (i > 50) {
println("going to throw NullPointerException at index " + i)
throw new NullPointerException("- Exception at index " + i)
}
else
"some-result"
}
val t: Try[String] = Try {
var x = 0
while (x < 100) {
doSomething(x)
x += 1
}
"result-" + x
}
val result: Option[String] = t match {
case Success(s) => println("success " + s); Some(s)
case Failure(f) => println("failure " + f.getMessage()); None
}
}
This gives the following output as excepted.
going to throw NullPointerException at index 51
failure - Exception at index 51
However, when I try to do the following (with an invalid URL to ensure that it fails every time)
import play.api.libs.ws._
val wsURL = "http://invalidurl:9000/something"
val wsres: Try[Future[Response]] = Try {
WS.url(wsURL).withRequestTimeout(200).withQueryString(("param", value)).get
}
Exceptions are thrown instead of getting an error that is wrapped inside a Try.
Can anyone please explain why it works in the first case and not in the second ?
[info] java.net.ConnectException: http://invalidurl:9000/something
[info] at com.ning.http.client.providers.netty.NettyConnectListener.operationComplete(NettyConnectListener.java:103)
[info] at org.jboss.netty.channel.DefaultChannelFuture.notifyListener(DefaultChannelFuture.java:427)
[info] at org.jboss.netty.channel.DefaultChannelFuture.addListener(DefaultChannelFuture.java:145)
[info] at com.ning.http.client.providers.netty.NettyAsyncHttpProvider.doConnect(NettyAsyncHttpProvider.java:1068)
[info] at com.ning.http.client.providers.netty.NettyAsyncHttpProvider.execute(NettyAsyncHttpProvider.java:890)
[info] at com.ning.http.client.AsyncHttpClient.executeRequest(AsyncHttpClient.java:520)
[info] at play.api.libs.ws.WS$WSRequest.execute(WS.scala:177)
[info] at play.api.libs.ws.WS$WSRequestHolder.get(WS.scala:386)
[info] at controllers.geojson.GeoJsonTransfomer$$anonfun$9.apply(GeoJsonTransformer.scala:155)
[info] at controllers.geojson.GeoJsonTransfomer$$anonfun$9.apply(GeoJsonTransformer.scala:155)
[info] ...
[info] Cause: java.nio.channels.UnresolvedAddressException:
[info] at sun.nio.ch.Net.checkAddress(Net.java:127)
[info] at sun.nio.ch.SocketChannelImpl.connect(SocketChannelImpl.java:640)
[info] at org.jboss.netty.channel.socket.nio.NioClientSocketPipelineSink.connect(NioClientSocketPipelineSink.java:108)
[info] at org.jboss.netty.channel.socket.nio.NioClientSocketPipelineSink.eventSunk(NioClientSocketPipelineSink.java:70)
[info] at org.jboss.netty.handler.codec.oneone.OneToOneEncoder.handleDownstream(OneToOneEncoder.java:54)
[info] at org.jboss.netty.handler.codec.http.HttpClientCodec.handleDownstream(HttpClientCodec.java:97)
[info] at org.jboss.netty.handler.stream.ChunkedWriteHandler.handleDownstream(ChunkedWriteHandler.java:109)
[info] at org.jboss.netty.channel.Channels.connect(Channels.java:634)
[info] at org.jboss.netty.channel.AbstractChannel.connect(AbstractChannel.java:207)
[info] at org.jboss.netty.bootstrap.ClientBootstrap.connect(ClientBootstrap.java:229)
I'm using the following code to hook into SBT's logging system to send the logging messages to another process accessible via a server setting:
extraLoggers := {
val clientLogger = FullLogger {
new Logger {
def log(level: Level.Value, message: => String): Unit =
if(level >= Level.Info) server.value.send(Json.arr("print", level.toString(), message))
def success(message: => String): Unit = server.value.send(Json.arr("print", "info", message))
def trace(t: => Throwable): Unit = server.value.send(Json.arr("print", "error", t.toString))
}
}
val currentFunction = extraLoggers.value
(key: ScopedKey[_]) => clientLogger +: currentFunction(key)
}
When I look at the output being spewed out on the other server process, I don't see the messages with green [success] tags appearing. Everything else (i.e. all the [info] messages and the red [error] messages) appear just fine.
Printing out clientLogger.successEnabled gives me true.
What am I doing wrong?
DISCLAIMER: Please use with care as the answer might be incomplete or even totally wrong.
After consulting the sources of sbt my understanding is that extraLoggers is a setting that is only "A function that provides additional loggers for a given setting." and these additional loggers are additional to StandardMain.console.
If it were possible, you would therefore have to set logManager to have a reference to extraLoggers and your own custom sbt.ConsoleOut in build.sbt, e.g.
logManager := LogManager.defaults(extraLoggers.value, new ConsoleOut {
val lockObject = System.out
def print(s: String): Unit = synchronized { print(s) }
def println(s: String): Unit = synchronized { println(s) }
def println(): Unit = synchronized {
System.out.println()
}
})
It won't however work since sbt.ConsoleOut is a sealed trait and hence there is no way to use it outside the file it was defined.
Having said that, I believe, in sbt 0.13.1, it's not possible to "intercept" the [success] message that's printed out when showSuccess is true as it comes out from ConsoleOut that's outside your control.
What you can do with extraLoggers is to have your own custom logging for tasks and streams.value.log.success("Succezz") should work.
The sample build.sbt with extraLoggers and a t task to demo the custom logger.
extraLoggers := {
val clientLogger = FullLogger {
new Logger {
def log(level: Level.Value, message: => String): Unit =
if(level >= Level.Info) println(s"+++ $message at $level")
def success(message: => String): Unit = println(s"+++ success: $message")
def trace(t: => Throwable): Unit = println(s"+++ trace: throwable: $t")
}
}
val currentFunction = extraLoggers.value
(key: ScopedKey[_]) => clientLogger +: currentFunction(key)
}
val t = taskKey[Unit]("Show extraLoggers")
t := {
println(s"Using extraLoggers")
val s: TaskStreams = streams.value
val log = s.log
log.debug("Saying hi...")
log.info("Hello!")
log.error("Error")
log.success("Succezz")
}
With the file, executing the t task gives the following output:
$ sbt
[info] Loading global plugins from /Users/jacek/.sbt/0.13/plugins
[info] Loading project definition from /Users/jacek/sandbox/sbt-0.13.1-extra-loggers/project
[info] Set current project to sbt-0-13-1-extra-loggers (in build file:/Users/jacek/sandbox/sbt-0.13.1-extra-loggers/)
[sbt-0-13-1-extra-loggers]> about
[info] This is sbt 0.13.1
[info] The current project is {file:/Users/jacek/sandbox/sbt-0.13.1-extra-loggers/}sbt-0-13-1-extra-loggers 0.1-SNAPSHOT
[info] The current project is built against Scala 2.10.3
[info] Available Plugins: com.typesafe.sbt.SbtGit, com.typesafe.sbt.SbtProguard, growl.GrowlingTests, org.sbtidea.SbtIdeaPlugin, np.Plugin, com.timushev.sbt.updates.UpdatesPlugin
[info] sbt, sbt plugins, and build definitions are using Scala 2.10.3
[sbt-0-13-1-extra-loggers]> t
Using extraLoggers
[info] Hello!
+++ Hello! at info
[error] Error
+++ Error at error
[success] Succezz
+++ success: Succezz
[success] Total time: 0 s, completed Dec 16, 2013 10:30:48 PM