annotation macro that rewrites and impls a trait, generics not processed correctly - scala-macros

I am writing a macro that needs to create a class that rewrites a trait, having the same methods/args of the trait but different return type.
So say we got:
trait MyTrait[T]
{
def x(t1: T)(t2: T): T
}
#AnnProxy
class MyClass[T] extends MyTrait[T]
MyClass will be rewritten to:
class MyClass[T] {
def x(t1: T)(t2: T): R[T]
}
(so x will now return R[T] instead of T)
I wrote the macro and debugging it, it produces this code:
Expr[Any](class MyClass[T] extends scala.AnyRef {
def <init>() = {
super.<init>();
()
};
def x(t1: T)(t2: T): macrotests.R[T] = $qmark$qmark$qmark
})
#AnnProxy
As you see the signature seems ok. But when trying to use the macro, I get a compilation error:
val my = new MyClass[Int]
my.x(5)(6)
Error:(14, 7) type mismatch;
found : Int(5)
required: T
x.x(5)(6)
^
So it seems the method's generic T is not the same as the class [T]. Any ideas how to fix?
This is my macro so far. I am not any good with macros (coin'd this up with a lot of help from stackoverflow), but this is the current state:
#compileTimeOnly("enable macro paradise to expand macro annotations")
class AnnProxy extends StaticAnnotation
{
def macroTransform(annottees: Any*): Any = macro IdentityMacro.impl
}
trait R[T]
object IdentityMacro
{
private val SDKClasses = Set("java.lang.Object", "scala.Any")
def impl(c: whitebox.Context)(annottees: c.Expr[Any]*): c.Expr[Any] = {
import c.universe._
def showInfo(s: String) = c.info(c.enclosingPosition, s.split("\n").mkString("\n |---macro info---\n |", "\n |", ""), true)
val classDef = annottees.map(_.tree).head.asInstanceOf[ClassDef]
val clazz = c.typecheck(classDef).symbol.asClass
val tparams = clazz.typeParams
val baseClasses = clazz.baseClasses.tail.filter(clz => !SDKClasses(clz.fullName))
val methods = baseClasses.flatMap {
base =>
base.info.decls.filter(d => d.isMethod && d.isPublic).map { decl =>
val termName = decl.name.toTermName
val method = decl.asMethod
val params = method.paramLists.map(_.map {
s =>
val vd = internal.valDef(s)
val f = tparams.find(_.name == vd.tpt.symbol.name)
val sym = if (f.nonEmpty) f.get else vd.tpt.symbol
q"val ${vd.name} : $sym "
})
val paramVars = method.paramLists.flatMap(_.map(_.name))
q""" def $termName (...$params)(timeout:scala.concurrent.duration.FiniteDuration) : macrotests.R[${method.returnType}] = {
???
}"""
}
}
val cde = c.Expr[Any] {
q"""
class ${classDef.name} [..${classDef.tparams}] {
..$methods
}
"""
}
showInfo(show(cde))
cde
}
}
EDIT: I managed to work around by building the class as a string and then using c.parse to compile it. Feels like a hack but it works. There must be a better way by manipulating the tree.
package macrotests
import scala.annotation.{StaticAnnotation, compileTimeOnly}
import scala.language.experimental.macros
import scala.reflect.macros.whitebox
#compileTimeOnly("enable macro paradise to expand macro annotations")
class AnnProxy extends StaticAnnotation
{
def macroTransform(annottees: Any*): Any = macro AnnProxyMacro.impl
}
trait R[T]
trait Remote[T]
object AnnProxyMacro
{
private val SDKClasses = Set("java.lang.Object", "scala.Any")
def impl(c: whitebox.Context)(annottees: c.Expr[Any]*): c.Expr[Any] = {
import c.universe._
val classDef = annottees.map(_.tree).head.asInstanceOf[ClassDef]
val clazz = c.typecheck(classDef).symbol.asClass
val baseClasses = clazz.baseClasses.tail.filter(clz => !SDKClasses(clz.fullName))
val methods = baseClasses.flatMap {
base =>
base.info.decls.filter(d => d.isMethod && d.isPublic).map { decl =>
val termName = decl.name.toTermName
val method = decl.asMethod
val params = method.paramLists.map(_.map {
s =>
val vd = internal.valDef(s)
val tq = vd.tpt
s"${vd.name} : $tq"
})
val paramVars = method.paramLists.flatMap(_.map(_.name))
val paramVarsArray = paramVars.mkString("Array(", ",", ")")
val paramsStr = params.map(_.mkString("(", ",", ")")).mkString(" ")
val retTpe = method.returnType.typeArgs.mkString("-unexpected-")
s""" def $termName $paramsStr (timeout:scala.concurrent.duration.FiniteDuration) : macrotests.Remote[$retTpe] = {
println($paramVarsArray.toList)
new macrotests.Remote[$retTpe] {}
}"""
}
}
val tparams = clazz.typeParams.map(_.name)
val tparamsStr = if (tparams.isEmpty) "" else tparams.mkString("[", ",", "]")
val code =
s"""
|class ${classDef.name}$tparamsStr (x:Int) {
|${methods.mkString("\n")}
|}
""".stripMargin
// print(code)
val cde = c.Expr[Any](c.parse(code))
cde
}
}

the code is very long , you can look at the github: https://github.com/1178615156/scala-macro-example/blob/master/stackoverflow/src/main/scala/so/AnnotationWithTrait.scala
import scala.annotation.StaticAnnotation
import scala.language.experimental.macros
import scala.reflect.macros.blackbox.Context
/**
* Created by yu jie shui on 2015/12/2.
*/
class AnnotationWithTrait extends StaticAnnotation {
def macroTransform(annottees: Any*): Any = macro AnnotationWithTraitImpl.apply
}
class AnnotationWithTraitImpl(val c: Context) {
import c.universe._
val SDKClasses = Set("java.lang.Object", "scala.Any")
def showInfo(s: String) = c.info(c.enclosingPosition, s.split("\n").mkString("\n |---macro info---\n |", "\n |", ""), true)
def apply(annottees: c.Expr[Any]*) = {
val classDef = annottees.map(_.tree).head.asInstanceOf[ClassDef]
val superClassSymbol= c.typecheck(classDef).symbol.asClass.baseClasses.tail
.filterNot(e => SDKClasses.contains(e.fullName)).reverse
val superClassTree= classDef match {
case q"$mod class $name[..$t](..$params) extends ..$superClass { ..$body }" =>
(superClass: List[Tree]).filterNot(e =>
typeOf[Object].members.exists(_.name == e.children.head.toString())
)
}
showInfo(show(superClassSymbol))
showInfo(show(superClassTree))
val impl = q"private[this] object ${TermName("impl")} extends ..${superClassTree}"
//
//get super class all can call method
val methods = superClassSymbol.map(_.info.members
.filterNot(_.isConstructor)
.filterNot(e => typeOf[Object].members.exists(_.name == e.name)).map(_.asMethod)).toList
case class ReplaceTypeParams(from: String, to: String)
type ClassReplace = List[ReplaceTypeParams]
//trait a[A]
//class b[B] extends a[B]
//need replace type params A to B
val classReplaceList: List[ClassReplace] = superClassTree zip superClassSymbol map {
case (superClassTree, superClassSymbol) =>
superClassSymbol.asClass.typeParams.map(_.name) zip superClassTree.children.tail map
(e => ReplaceTypeParams(e._1.toString, e._2.toString()))
}
val out = classReplaceList zip methods map {
case (classReplace, func) =>
func map { e => {
val funcName = e.name
val funcTypeParams = e.typeParams.map(_.name.toString).map(name => {
TypeDef(Modifiers(Flag.PARAM), TypeName(name), List(), TypeBoundsTree(EmptyTree, EmptyTree))
})
val funcParams = e.paramLists.map(_.map(e => q"${e.name.toTermName}:${
TypeName(
classReplace.find(_.from == e.info.toString).map(_.to).getOrElse(e.info.toString)
)} "))
val funcResultType = TypeName(
classReplace.find(_.from == e.returnType.toString).map(_.to).getOrElse(e.info.toString)
)
q"""
def ${funcName}[..${funcTypeParams}](...$funcParams):${funcResultType}=
impl.${funcName}[..${funcTypeParams}](...$funcParams)
"""
}
}
}
showInfo(show(out))
q"""
class ${classDef.name}[..${classDef.tparams}]{
$impl
..${out.flatten}
}
"""
}
}
test
trait MyTrait[MT1] {
def x(t1: MT1)(t2: MT1): MT1 = t1
}
trait MyTrait2[MT2] {
def t(t2: MT2): MT2 = t2
}
#AnnotationWithTrait
class MyClass[MCT1, MCT2] extends MyTrait[MCT1] with MyTrait2[MCT2]
object AnnotationWithTraitUsing extends App {
assert(new MyClass[Int, String].x(1)(2) == 1)
assert(new MyClass[Int, String].t("aaa") == "aaa")
}

Related

How to mirror dragonbones animation?

I make KorGE game with dragonbone animation. How to mirror this dragonbones animation? I want to character look to the right instead of the left:)
val factory = KorgeDbFactory()
val skeDeferred = asyncImmediately { Json.parse(resourcesVfs["Ubbie/Ubbie_ske.json"].readString())!! }
val texDeferred = asyncImmediately { resourcesVfs["Ubbie/Ubbie_tex.json"].readString() }
val imgDeferred = asyncImmediately { resourcesVfs["Ubbie/Ubbie_tex.png"].readBitmap().mipmaps() }
factory.parseDragonBonesData(skeDeferred.await())
factory.parseTextureAtlasData(Json.parse(texDeferred.await())!!, imgDeferred.await())
val armatureDisplay = factory.buildArmatureDisplay("ubbie")!!.position(600, 720).scale(1)
armatureDisplay.animation.play("walk")
addUpdater {
this += armatureDisplay
}
SCALE_X = -1 gives mirror effect
val SCALE_X=-1
val SCALE_Y=1
...
val armatureDisplay = factory.buildArmatureDisplay("ubbie")!!.position(600, 720).scale(SCALE_X, SCALE_Y)

access content of parent's field in graphene python

I am using graphene in python.
Let's say I have the following schema:
extends type Query {
a(search:String):A
}
type A {
b:B
important_info:ID
}
type B {
fieldone: String
fieldtwo: String
}
Now I'd like to query:
query {
a(search:"search string") {
b {
fieldone
}
}
}
however fieldone is based on important_info.
My class B looks like this:
class B(graphene.ObjectType):
fieldone = graphene.String()
fieldtwo = graphene.String()
def resolve_fieldone(self,info):
# Here I want access to important_info, but I don't know how ...
return "something based on important_info"
How can I access important info from within the resolver of fieldone?
It seems there is no obvious or documented solution for this requirement.
I solved it by adding the root object to info.context within the outermost type:
class B(ObjectType):
c = String()
def resolve_c(parent, info):
return 'foo' if info.context['z'] == '' else 'bar'
class A(ObjectType):
b = Field(B)
def resolve_b(parent, info):
return parent.b
class Query(ObjectType):
a = Field(A)
z = String()
def resolve_a(parent, info):
return some_function_to_get_a()
def resolve_z(parent, info):
z = some_function_to_get_z()
info.context['z'] = z
return z

Slick 3.0 Custom Code Generation for Oracle

I am trying to generate Slick 3.0 code for Oracle. The DB user points to two schemas that have tables with the same name so the generated code has duplicate classes. I would like to filter out the tables from the schema that end in "STAGE"
Here is the code:
object CodeGen2 extends App {
//https://stackoverflow.com/questions/28285129/slick-code-generation-for-only-a-single-schema
val slickDriver = "com.typesafe.slick.driver.oracle.OracleDriver"
val jdbcDriver = "oracle.jdbc.OracleDriver"
val url = "jdbc:oracle:thin:#dbhost:1521:dbsid"
val user = "dbuser"
val password = "dbpassword"
val destDir = "src/main/scala"
val destPackage = "com.mycompany.mypackage"
import scala.concurrent.{ExecutionContext, Await, Future}
import scala.concurrent.duration.Duration
import slick.codegen.SourceCodeGenerator
import scala.concurrent.ExecutionContext.Implicits.global
import slick.jdbc.JdbcModelBuilder
import slick.jdbc.meta.MTable
import com.typesafe.slick.driver.oracle.OracleDriver
import slick.jdbc.JdbcBackend.DatabaseFactoryDef
println("Starting codegen...")
val db = OracleDriver.simple.Database.forURL(url, user=user, password=password, driver=jdbcDriver)
val filteredTables = OracleDriver.defaultTables.filter(
(t: MTable) => !t.name.schema.get.endsWith("STAGE")
)
val modelAction = OracleDriver.createModel(filteredTables, true)
println("Generating model...")
val model = Await.result(db.run(modelAction), Duration.Inf)
val codegen = new SourceCodeGenerator(model) {
// for illustration
val noStage = model.tables.filter { table => !table.name.schema.get.endsWith("STAGE") }
noStage.foreach { table => println(table.name.schema.get) }
}
println("Generating files...")
codegen.writeToFile(
slickDriver, destDir, destPackage, "Tables", "Tables.scala"
)
// slick.codegen.SourceCodeGenerator.main(
// Array(slickDriver, jdbcDriver, url, destDir, destPackage, user, password)
// )
println("Finished codegen.")
}
I try and filter the defaultTables but the signature is Seq[MTable] => Boolean so I have no idea how to deal with that. Is filtering the tables passed to driver.createModel the correct approach? I looked and tried other code with override(n) methods but nothing seemed workable.
name := "slick-test"
version := "1.0"
scalaVersion := "2.11.6"
libraryDependencies ++= Seq(
"com.typesafe.slick" %% "slick" % "3.0.0",
"org.slf4j" % "slf4j-nop" % "1.6.4",
"com.typesafe.slick" %% "slick-extensions" % "3.0.0",
"com.typesafe.slick" %% "slick-codegen" % "3.0.0"
)
resolvers += "Typesafe Releases" at "http://repo.typesafe.com/typesafe/maven-releases/"
Thanks.
Try it this way :
val filteredTables = OracleDriver.defaultTables.map(seq => seq.filter(t => !t.name.schema.get.endsWith("STAGE")))
val modelAction = OracleDriver.createModel(Option(filteredTables), true)
Update: After I posted above, a workaround was found to the code generation problem for Oracle. It should be fixed in Slick 3.2. Replace the code above with this SourceCodeGenerator. See https://github.com/slick/slick/issues/1000
val codegen = new SourceCodeGenerator(model) {
// slated for fix in 3.2
override def Table = new Table(_) { table =>
// override generator responsible for columns
override def Column = new Column(_) {
override def defaultCode = v => {
def raw(v: Any) = rawType match {
case "String" => "\"" + v + "\""
case "Long" => v + "L"
case "Float" => v + "F"
case "Char" => "'" + v + "'"
case "scala.math.BigDecimal" => v.toString.trim + "d"
case "Byte" | "Short" | "Int" | "Double" | "Boolean" => v.toString
}
v match {
case Some(x) => s"Some(${raw(x)})"
case None => "None"
case x => raw(x)
}
}
}
}
}

LinearSeqOptimized#find Reference Copy

Looking at the Scala 2.10.0's implementation of LinearSeqOptimized#find in LinearSeqOptimized.scala, why is it necessary to call var these = this?
Why couldn't this simply be used?
override /*IterableLike*/
def find(p: A => Boolean): Option[A] = {
var these = this
while (!these.isEmpty) {
if (p(these.head)) return Some(these.head)
these = these.tail
}
None
}
Because you would have to have the same condition and operation out of the loop for this and then start using these.
It's much simpler to just put everyone in the same basket and do it all in the loop itself. Example:
def find(p: A => Boolean): Option[A] = {
if (!this.isEmpty && p(this.head)) {
return Some(this.head)
}
var these = this.tail
while (!these.isEmpty) {
if (p(these.head)) return Some(these.head)
these = these.tail
}
None
}
Not very smart, as you can see.
You could also easily implement this as a #tailrec operation:
#tailrec final def find[A](p : A => Boolean) : Option[A] = {
if ( this.isEmpty ) {
None
} else {
if ( p(this.head) ) {
Some(this.head)
} else {
this.tail.find(p)
}
}
}
And it isn't done like this in Scala because tailrec calls have to be final or private.

How to play the drop failed animation in ScalaFX/JavaFX 2?

In ScalaFX if I drop a TreeCell onto a control other than the containing TreeView then a short animation is played showing the dragged item returning to its original position.
The same happens if I drop the TreeCell outside of the application - e.g. onto the desktop.
If I drop the TreeCell onto the containing TreeView in a location that shouldn't be accepted I want the same animation to play, but setting:
event.dropCompleted = false
isn't enough to produce this effect.
Is there a way to play the drop failed animation from inside the TreeCell's onDragDropped event handler?
Edit
Added code sample as requested. Drag an entry and drop it outside the control and you will see the drop failed animation. Drag an entry and drop it below the list and the animation doesn't play. The question is how to make the animation play in the second case.
import scalafx.application.JFXApp
import scalafx.scene.Scene
import scalafx.scene.paint.Color
import scalafx.scene.layout.Pane
import scalafx.Includes._
import scalafx.scene.input.{TransferMode, DataFormat, DragEvent, MouseEvent}
import scalafx.scene.control._
import javafx.scene.control.{TreeItem => jfxTreeItem}
import java.nio.ByteBuffer
import java.util
import scalafx.beans.value.ObservableValue
class StringRootItem(text: String) extends TreeItem[String](text) {
override def toString() = text
}
class StringTreeItem(text: String) extends TreeItem[String](text) {
override def toString() = text
}
object DragAndDropControl {
def apply(rootNodeName: String, entries: List[String]): DragAndDropControl = {
val rootItem = new StringRootItem(rootNodeName)
rootItem.setExpanded(true)
val children = rootItem.getChildren
entries.foreach(entry => {
children.add(new StringTreeItem(entry))
})
new DragAndDropControl(rootItem)
}
}
class DragAndDropControl(rootItem: StringRootItem) extends TreeView[String](rootItem) {
showRoot = false
cellFactory = (tree: TreeView[String]) => {
new StringDragAndDropCell(tree)
}
object StringDragAndDropCell {
val customFormat: DataFormat = new DataFormat("string.entry.custom")
var transferItem: TreeItem[String] = null
def toBuffer(entry: String): ByteBuffer = ByteBuffer.allocate(10)
def fromBuffer(buffer: ByteBuffer): String = null
}
class StringDragAndDropCell(tree: TreeView[String]) extends TreeCell[String] {
import StringDragAndDropCell._
private var stringEntry: String = null
onDragDetected = (event: MouseEvent) => {
val db = startDragAndDrop(TransferMode.MOVE)
import scala.collection.JavaConversions._
if ((treeItem ne null) && (treeItem.value ne null) && (treeItem.value.getValue ne null)) {
transferItem = treeItem.value
val content: Map[javafx.scene.input.DataFormat, AnyRef] = Map(customFormat.delegate -> toBuffer(transferItem.value()))
val contentMap: util.Map[javafx.scene.input.DataFormat, AnyRef] = mapAsJavaMap(content)
db.setContent(contentMap)
}
event.consume()
}
onDragOver = (event: DragEvent) => {
val db = event.getDragboard
if (db.hasContent(customFormat))
event.acceptTransferModes(TransferMode.MOVE)
event.consume()
}
onDragDone = (event: DragEvent) => {
if (event.transferMode == TransferMode.MOVE)
event.dragboard.clear()
event.consume()
}
onDragDropped = (event: DragEvent) => {
val db = event.getDragboard
var success = false
if (db.hasContent(customFormat))
success = stringEntry ne null
if (success)
if (event.gestureSource != event.delegate.getGestureTarget) {
treeItem().getParent.getChildren.removeAll(transferItem)
treeItem().getParent.getChildren.add(index(), transferItem)
}
event.dropCompleted = success
event.consume()
}
/*
* Change handler for the treeItem property.
*
* The treeItem property is set by JavaFX after construction and at any time when the cell is recycled for UI virtualization.
*
* The text property must be updated when the treeItem is set in order for the cell to render correctly (without this the cell will appear blank.)
*/
super.treeItem.onChange((observable: ObservableValue[jfxTreeItem[String], jfxTreeItem[String]], oldValue: jfxTreeItem[String], newValue: jfxTreeItem[String]) => {
if (newValue ne null) {
stringEntry = newValue.getValue
text = stringEntry
}
else
stringEntry = null
})
override def toString(): String = {
if (stringEntry == null)
null
else
s"ScalaFX string tree cell ${stringEntry.toString}"
}
}
}
object TestDragAndDrop extends JFXApp
{
println("javafx.runtime.version: " + System.getProperties.get("javafx.runtime.version"))
println("java.runtime.version: " + System.getProperties.get("java.runtime.version"))
stage = new JFXApp.PrimaryStage {
title = "Test Drag and Drop"
width = 600
height = 472
scene = new Scene {
fill = Color.LIGHTGREEN
root = new Pane {
content = DragAndDropControl("Numbers", List("zero", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine"))
}
}
}
}

Resources