Splitting scalac plugin into multiple files - scala

I'd like to split my scalac plugin into multiple files. This sounds easy but I haven't managed to pull it off due to path-dependent type issues stemming from the import global._ line.
Here's Lex Spoon's sample plugin:
package localhost
import scala.tools.nsc
import nsc.Global
import nsc.Phase
import nsc.plugins.Plugin
import nsc.plugins.PluginComponent
class DivByZero(val global: Global) extends Plugin {
import global._
val name = "divbyzero"
val description = "checks for division by zero"
val components = List[PluginComponent](Component)
private object Component extends PluginComponent {
val global: DivByZero.this.global.type = DivByZero.this.global
val runsAfter = "refchecks"
// Using the Scala Compiler 2.8.x the runsAfter should be written as below
// val runsAfter = List[String]("refchecks");
val phaseName = DivByZero.this.name
def newPhase(_prev: Phase) = new DivByZeroPhase(_prev)
class DivByZeroPhase(prev: Phase) extends StdPhase(prev) {
override def name = DivByZero.this.name
def apply(unit: CompilationUnit) {
for ( tree # Apply(Select(rcvr, nme.DIV), List(Literal(Constant(0)))) <- unit.body;
if rcvr.tpe <:< definitions.IntClass.tpe)
{
unit.error(tree.pos, "definitely division by zero")
}
}
}
}
}
How can I put Component and DivByZeroPhase in their own files without having the import global._ in scope?

Here's a really old project where I've done the same thing:
https://github.com/jsuereth/osgi-scalac-plugin/blob/master/src/main/scala/scala/osgi/compiler/OsgiPlugin.scala
If you don't need to pass path-dependent types from the global, don't worry about trying to keep the "this.global" portions of it relevant.

In the Scala Refactoring library, I solved it by having a trait CompilerAccess:
trait CompilerAccess {
val global: tools.nsc.Global
}
Now all the other traits that need to access global just declare CompilerAccess as a dependency:
trait TreeTraverser {
this: CompilerAccess =>
import global._
...
}
and then there's a class that mixes in all these traits and provides an instance of global:
trait SomeRefactoring extends TreeTraverser with OtherTrait with MoreTraits {
val global = //wherever you get your global from
}
This scheme worked quite well for me.

You can create a separate class for your component and pass global in:
class TemplateComponent(val global: Global) extends PluginComponent {
import global._
val runsAfter = List[String]("refchecks")
val phaseName = "plugintemplate"
def newPhase(prev: Phase) = new StdPhase(prev) {
override def name = phaseName
def apply(unit:CompilationUnit) = {
}
}
}

Related

How to use Array in JCommander in Scala

I want to use JCommander to parse args.
I wrote some code:
import com.beust.jcommander.{JCommander, Parameter}
import scala.collection.mutable.ArrayBuffer
object Config {
#Parameter(names = Array("--categories"), required = true)
var categories = new ArrayBuffer[String]
}
object Main {
def main(args: Array[String]): Unit = {
val cfg = Config
JCommander
.newBuilder()
.addObject(cfg)
.build()
.parse(args.toArray: _*)
println(cfg.categories)
}
}
Howewer it fails with
com.beust.jcommander.ParameterException: Could not invoke null
Reason: Can not set static scala.collection.mutable.ArrayBuffer field InterestRulesConfig$.categories to java.lang.String
What am i doing wrong?
JCommander uses knowledge about types in Java to map values to parameters. But Java doesn't have a type scala.collection.mutable.ArrayBuffer. It has a type java.util.List. If you want to use JCommander you have to stick to Java's build-in types.
If you want to use Scala's types use one of Scala's libraries that handle in in more idiomatic manner: scopt or decline.
Working example
import java.util
import com.beust.jcommander.{JCommander, Parameter}
import scala.jdk.CollectionConverters._
object Config {
#Parameter(names = Array("--categories"), required = true)
var categories: java.util.List[Integer] = new util.ArrayList[Integer]()
}
object Hello {
def main(args: Array[String]): Unit = {
val cfg = Config
JCommander
.newBuilder()
.addObject(cfg)
.build()
.parse(args.toArray: _*)
println(cfg.categories)
println(cfg.categories.getClass())
val a = cfg.categories.asScala
for (x <- a) {
println(x.toInt)
println(x.toInt.getClass())
}
}
}

Creating functional tests Scala Playframework 2.6 Macwire

I wrote some traits to use it as a base for my functional tests
This file is for creating a DB in memory (H2 + Evolutions)
BlogApiDBTest.scala
package functional.common
import play.api.db.Databases
import play.api.db.evolutions.Evolutions
trait BlogApiDBTest {
implicit val testDatabase = Databases.inMemory(
name = "blog_db",
urlOptions = Map(
"MODE" -> "MYSQL"
),
config = Map(
"logStatements" -> true
)
)
org.h2.engine.Mode.getInstance("MYSQL").convertInsertNullToZero = false
Evolutions.applyEvolutions(testDatabase)
}
Here I am overriding some injected components for testing purposes
BlogApiComponentsTest.scala
package functional.common
import common.BlogApiComponents
import org.scalatestplus.play.components.WithApplicationComponents
import play.api.{BuiltInComponents, Configuration}
trait BlogApiComponentsTest extends WithApplicationComponents with BlogApiDBTest {
override def components: BuiltInComponents = new BlogApiComponents(context) {
override lazy val configuration: Configuration = context.initialConfiguration
override lazy val blogDatabase = testDatabase
}
}
This is the base class for my functional tests
BlogApiOneServerPerTestWithComponents.scala
package functional.common
import org.scalatestplus.play.PlaySpec
import org.scalatestplus.play.components.{OneServerPerTestWithComponents}
trait BlogApiOneServerPerTestWithComponents extends PlaySpec with OneServerPerTestWithComponents with BlogApiComponentsTest {
}
Finally the test I am trying to execute
PostControllerSpec.scala
package functional.controllers
import functional.common.BlogApiOneServerPerTestWithComponents
import org.scalatest.concurrent.{IntegrationPatience, ScalaFutures}
import play.api.mvc.{Results}
import play.api.test.{FakeRequest, Helpers}
import play.api.test.Helpers.{GET, route}
class PostControllerSpec extends BlogApiOneServerPerTestWithComponents
with Results
with ScalaFutures
with IntegrationPatience {
"Server query should" should {
"provide an Application" in {
val Some(result) = route(app, FakeRequest(GET, "/posts"))
Helpers.contentAsString(result) must be("success!")
}
}
}
Then I get
blog-api/test/functional/controllers/PostControllerSpec.scala:18:31: Cannot write an instance of play.api.mvc.AnyContentAsEmpty.type to HTTP response. Try to define a Writeable[play.api.mvc.AnyContentAsEmpty.type]
Here is the code
Adding the following import should make it work:
import play.api.test.Helpers._
Looking at the signature of route
def route[T](app: Application, req: Request[T])(implicit w: Writeable[T]): Option[Future[Result]]
we see it expects an implicit w: Writeable[T]. The above import will provide it via Writables

Inject object into App in scala main

Here is the code:
import akka.actor.ActorSystem
import akka.stream.ActorMaterializer
import akka.stream.scaladsl.Sink
import com.google.inject.Inject
import org.bytedeco.javacv.{CanvasFrame, Frame}
class WebCamWindow #Inject()(webCam: WebCam) {
implicit val system = ActorSystem()
implicit val materializer = ActorMaterializer()
val canvas = new CanvasFrame("Webcam")
// Set Canvas frame to close on exit
canvas.setDefaultCloseOperation(javax.swing.JFrame.EXIT_ON_CLOSE)
val imageDimensions = Dimensions(width = 640, height = 480)
val webcamSource = webCam.source
val graph = webcamSource
.map(MediaConversion.toMat) // most OpenCV manipulations require a Matrix
.map(Flip.horizontal)
.map(MediaConversion.toFrame) // convert back to a frame
.map(canvas.showImage)
.to(Sink.ignore)
def run(): Unit = {
graph.run()
}
}
I would like to make this an App object and run it, but I don't know how to deal with the dependency injection here. Could anyone help?
Full project can be found here: https://bitbucket.org/kindlychung/testscalacv
First step is to make it into a trait (just for convenience):
trait WebCamWindowLike {
def webCam: WebCam
implicit val system ...//all the code is here
}
Then you can have old injectable WebCamWindow:
class WebCamWindow #Inject()(val webCam: WebCam) extends WebCamWindowLike
As well as independent runnable object:
object WebCamWindowApp extends App with WebCamWindowLike {
private val injector = Guice.createInjector(new AppInjectory())
override val webCam = injector.getInstance(classOf[WebCam])
run()
}
Where AppInjectory extends AbstractModule is your actual injectory for Guice that takes care of all of your dependencies.
Another option is (if you want to get rid of Guice) manual "injection":
object WebCamWindowApp extends App with WebCamWindowLike {
override val webCam = new WebCam(...)
run()
}

Casbah: No implicit view available error

In a Play app, using Salat and Casbah, I am trying to de-serialize a DBObject into an object of type Task, but I am getting this error when calling .asObject:
No implicit view available from com.mongodb.casbah.Imports.DBObject =>
com.mongodb.casbah.Imports.MongoDBObject. Error occurred in an
application involving default arguments.
The object is serialized correctly with .asDBObject, and written to the database as expected.
What is causing this behaviour, and what can be done to solve it? Here's the model involved:
package models
import db.{MongoFactory, MongoConnection}
import com.novus.salat._
import com.novus.salat.global._
import com.novus.salat.annotations._
import com.mongodb.casbah.Imports._
import com.mongodb.casbah.commons.Imports._
import play.api.Play
case class Task(label: String, _id: ObjectId=new ObjectId)
object Task {
implicit val ctx = new Context {
val name = "Custom_Classloader"
}
ctx.registerClassLoader(Play.classloader(Play.current))
val taskCollection = MongoFactory.database("tasks")
def create(label: String): Task = {
val task = new Task(label)
val dbObject = grater[Task].asDBObject(task)
taskCollection.save(dbObject)
grater[Task].asObject(dbObject)
}
def all(): List[Task] = {
val results = taskCollection.find()
val tasks = for (item <- results) yield grater[Task].asObject(item)
tasks.toList
}
}
Versions
casbah: "2.8.1"
scala: "2.11.6"
salat: "1.9.9"
Instructions on creating a custom context:
First, define a custom context as
implicit val ctx = new Context { /* custom behaviour */ }
in a package object
Stop importing com.novus.salat.global._
Import your own custom context everywhere instead.
Source: https://github.com/novus/salat/wiki/CustomContext

Can Guice inject Scala objects

In Scala, can I use Guice to inject Scala objects?
For example, can I inject into s in the following object?
object GuiceSpec {
#Inject
val s: String = null
def get() = s
}
Some research on Google revealed that you can accomplish this as follows (the code that follows is a ScalaTest unit test):
import org.junit.runner.RunWith
import org.scalatest.WordSpec
import org.scalatest.matchers.MustMatchers
import org.scalatest.junit.JUnitRunner
import com.google.inject.Inject
import com.google.inject.Module
import com.google.inject.Binder
import com.google.inject.Guice
import uk.me.lings.scalaguice.ScalaModule
#RunWith(classOf[JUnitRunner])
class GuiceSpec extends WordSpec with MustMatchers {
"Guice" must {
"inject into Scala objects" in {
val injector = Guice.createInjector(new ScalaModule() {
def configure() {
bind[String].toInstance("foo")
bind[GuiceSpec.type].toInstance(GuiceSpec)
}
})
injector.getInstance(classOf[String]) must equal("foo")
GuiceSpec.get must equal("foo")
}
}
}
object GuiceSpec {
#Inject
var s: String = null
def get() = s
}
This assumes you are using scala-guice and ScalaTest.
The above answer is correct, but if you don't want to use ScalaGuice Extensions, you can do the following:
val injector = Guice.createInjector(new ScalaModule() {
def configure() {
bind[String].toInstance("foo")
}
#Provides
def guiceSpecProvider: GuiceSpec.type = GuiceSpec
})