I have a super and a subclass as follows:
class Animal(var x: Int) {
def greeting: String = "hi im an animal"
def copy: Animal = new Animal(x)
}
class Lion(override var x: Int) extends Animal(x){
override def greeting: String = "hi im a lion"
override def copy: Lion = new Lion(x)
}
I want both of them to have the exact same copy function (imagine it being larger than what I've given), except for the return type, I would like the Lion class to return a Lion when copy is invoked.
How can I cleanly override the Animal copy method without having code duplication?
In principle, methods apply/unapply, canEqual/equals/hashCode, toString, copy, productArity/productElement/productIterator/productPrefix can be generated with Shapeless case classes a la carte although I'm not sure whether this works with class hierarchies.
Anyway, you can generate apply with a macro annotation
import scala.annotation.{StaticAnnotation, compileTimeOnly}
import scala.language.experimental.macros
import scala.reflect.macros.blackbox
#compileTimeOnly("enable macro annotations")
class copy extends StaticAnnotation {
def macroTransform(annottees: Any*): Any = macro CopyMacro.impl
}
object CopyMacro {
def impl(c: blackbox.Context)(annottees: c.Tree*): c.Tree = {
import c.universe._
annottees match {
case q"$mods class $tpname[..$tparams] $ctorMods(...$paramss) extends { ..$earlydefns } with ..$parents { $self => ..$stats }" :: tail =>
val paramNamess = paramss.map(_.map {
case q"$_ val $tname: $_ = $_" => tname
case q"$_ var $tname: $_ = $_" => tname
})
val tparamNames = tparams.map {
case q"$_ type $tpname[..$_] = $_" => tpname
}
val doesOverrideCopy = parents.map {
case q"${parent#tq"$_[..$_]"}(...$_)" => parent
case parent#tq"$_[..$_]" => parent
}.exists(tree =>
c.typecheck(tree.duplicate, mode = c.TYPEmode)
.tpe
.member(TermName("copy")) != NoSymbol
)
val copyMod = if (doesOverrideCopy) Modifiers(Flag.OVERRIDE) else NoMods
q"""
$mods class $tpname[..$tparams] $ctorMods(...$paramss) extends { ..$earlydefns } with ..$parents { $self =>
..$stats
$copyMod def copy: $tpname[..$tparamNames] = new $tpname[..$tparamNames](...$paramNamess)
}
..$tail
"""
}
}
}
Usage:
#copy
class Animal(val x: Int) {
def greeting: String = "hi im an animal"
}
#copy
class Lion(override val x: Int) extends Animal(x) {
override def greeting: String = "hi im a lion"
}
//scalac: {
// class Animal extends scala.AnyRef {
// <paramaccessor> val x: Int = _;
// def <init>(x: Int) = {
// super.<init>();
// ()
// };
// def greeting: String = "hi im an animal";
// def copy: Animal = new Animal(x)
// };
// ()
//}
//scalac: {
// class Lion extends Animal(x) {
// override <paramaccessor> val x: Int = _;
// def <init>(x: Int) = {
// super.<init>();
// ()
// };
// override def greeting: String = "hi im a lion";
// override def copy: Lion = new Lion(x)
// };
// ()
//}
Alternatively, since class Animal(val x: Int) is case-class-like you can try to use shapeless.Generic
implicit class CopyOps[A](a: A)(implicit generic: Generic[A]) {
def copy: A = generic.from(generic.to(a))
}
Related
I have a macro that I use to generate some code to call methods dynamically. The macro is more complex than this, but for simplicity let's say it works something like this
def myMacro[T]: Seq[MethodName]
so than when called on
class Hello {
def one(a: Int, b: UserId): String = a.toString + b.id
def two(c: Option[Int]): String = ""
def three(d: Seq[Int], f: Set[Int]): String = ""
}
println(myMacro[Hello]) // Seq("one", "two", "three")
I need this macro to generate code for an internal framework we use at Candu, but I need to be able to call it from the parent's class. So what I want to achieve is:
trait Superclass {
def aFakeMethod: String = ""
val methods = myMacro[Self] // FIXME! self is not defined here...
}
class Hello extends Superclass {
def one(a: Int, b: UserId): String = a.toString + b.id
def two(c: Option[Int]): String = ""
def three(d: Seq[Int], f: Set[Int]): String = ""
}
val hi = new Hello
println(hi.methods) // Seq("one", "two", "three")
Because the high number of classes in the framework, modifying the api between Hello and Superclass is very expansive. So I would need a way to do this without changing code in Hello
Any suggestions on how this could be achieved?
If myMacro worked outside Hello it should work inside Superclass as well
import scala.language.experimental.macros
import scala.reflect.macros.blackbox
def myMacro[T]: Seq[String] = macro impl[T]
def impl[T: c.WeakTypeTag](c: blackbox.Context): c.Tree = {
import c.universe._
val methodNames = weakTypeOf[T].decls
.filter(symb => symb.isMethod && !symb.isConstructor)
.map(_.name.toString).toList
val methodNamesTree = methodNames.foldRight[Tree](q"Nil")((name, names) => q"$name :: $names")
q"..$methodNamesTree"
}
Usage:
sealed trait Superclass {
def aFakeMethod: String = ""
val methods = myMacro[Hello]
}
val hi = new Hello
println(hi.methods) // List("one", "two", "three")
If for some reason you can't use the name of Hello you can try to make Superclass sealed and use knownDirectSubclasses
def myMacro1(): Seq[String] = macro impl1
def impl1(c: blackbox.Context)(): c.Tree = {
import c.universe._
val child = c.enclosingClass.symbol.asClass.knownDirectSubclasses.head
q"myMacro[$child]"
}
Usage:
sealed trait Superclass {
def aFakeMethod: String = ""
val methods = myMacro1()
}
val hi = new Hello
println(hi.methods) // List("one", "two", "three")
Or you can replace deprecated c.enclosingClass.symbol.asClass with c.internal.enclosingOwner.owner.asClass (now enclosingOwner is val methods, enclosingOwner.owner is trait Superclass).
If you can't make Superclass sealed try to traverse all classes and look for those extending Superclass
def myMacro2(): Seq[Seq[String]] = macro impl2
def impl2(c: blackbox.Context)(): c.Tree = {
import c.universe._
def treeSymbol(tree: Tree): Symbol = c.typecheck(tree, mode = c.TYPEmode).symbol
val enclosingClassSymbol = c.internal.enclosingOwner.owner
def isEnclosingClass(tree: Tree): Boolean = treeSymbol(tree) == enclosingClassSymbol
var methodss = Seq[Seq[String]]()
val traverser = new Traverser {
override def traverse(tree: Tree): Unit = {
tree match {
case q"$_ class $_[..$_] $_(...$_) extends { ..$_ } with ..$parents { $_ => ..$stats }"
if parents.exists(isEnclosingClass(_)) =>
val methods = stats.collect {
case q"$_ def $tname[..$_](...$_): $_ = $_" => tname.toString
}
methodss :+= methods
case _ => ()
}
super.traverse(tree)
}
}
c.enclosingRun.units.foreach(unit => traverser.traverse(unit.body))
def namesToTree[A: Liftable](names: Seq[A]): Tree =
names.foldRight[Tree](q"Seq()")((name, names) => q"$name +: $names")
namesToTree[Tree](methodss.map(namesToTree[String](_)))
}
Usage:
trait Superclass {
def aFakeMethod: String = ""
val methods = myMacro2()
}
class Hello1 extends Superclass {
def four = ???
def five = ???
}
class Hello extends Superclass {
def one(a: Int, b: UserId): String = a.toString + b.id
def two(c: Option[Int]): String = ""
def three(d: Seq[Int], f: Set[Int]): String = ""
}
val hi = new Hello
println(hi.methods) // List(List("four", "five"), List("one", "two", "three"))
I wonder is it possible to pass implicit params through singletons like that
case class Greet(g: String)
object Foo {
def greet(name: String)(implicit greet: Greet = Greet("Hello")) = println(greet.g + " " + name)
}
object Bar {
def greetBar = Foo.greet("Bar")
}
object Main {
def main(args: Array[String]): Unit = {
implicit val greet: Greet = Greet("Goodbye")
Foo.greet("Sunshine") // Goodbye Sunshine
Bar.greetBar // Hello Bar
}
}
Bar.greetBar doesn't affected by implicit value in main, but I want it to be affected without passing implicit param to greetBar, so is there any way to do something like that? Maybe there is a way to set an implicit for object but in outer of it?
You should add implicit parameter to the method
object Bar {
def greetBar(implicit greet: Greet /*= Greet("Hello")*/) = Foo.greet("Bar")
}
implicit val greet: Greet = Greet("Goodbye")
Bar.greetBar // Goodbye Bar
or make the object a class and add implicit parameter to the class
class Bar(implicit greet: Greet /*= Greet("Hello")*/) {
def greetBar = Foo.greet("Bar")
}
implicit val greet: Greet = Greet("Goodbye")
(new Bar).greetBar // Goodbye Bar
I commented out default value /*= Greet("Hello")*/. If you want greetBar not to compile when there is no implicit in scope then you should keep it commented out. If you want behavior similar to greet (i.e. Greet("Hello") when there is no implicit in scope) then you should uncomment it.
Please notice that you can avoid repeating default value if you define lower-priority implicit in companion object
case class Greet(g: String)
object Greet {
implicit val lowPriorityGreet: Greet = Greet("Hello")
}
object Foo {
def greet(name: String)(implicit greet: Greet) = println(greet.g + " " + name)
}
object Bar {
def greetBar(implicit greet: Greet) = Foo.greet("Bar")
}
// class Bar(implicit greet: Greet) {
// def greetBar = Foo.greet("Bar")
// }
implicit val greet: Greet = Greet("Goodbye")
Foo.greet("Sunshine") // Goodbye Sunshine
Bar.greetBar // Goodbye Bar
// (new Bar).greetBar // Goodbye Bar
See also
How to wrap a method having implicits with another method in Scala?
I want to do this to set Greet implict for all methods in Bar
In principle, you can do this with a macro annotation (but you shouldn't)
import scala.annotation.{StaticAnnotation, compileTimeOnly}
import scala.language.experimental.macros
import scala.reflect.macros.blackbox
#compileTimeOnly("enable macro annotations")
class greetAware extends StaticAnnotation {
def macroTransform(annottees: Any*): Any = macro GreetAwareMacro.impl
}
object GreetAwareMacro {
def impl(c: blackbox.Context)(annottees: c.Tree*): c.Tree = {
import c.universe._
val greet = TermName(c.freshName("greet"))
val implicitGreet = q"""implicit val $greet: Greet = Greet("Hello")"""
def isImplicit(param: Tree): Boolean = param match {
case q"$mods val $_: $_ = $_" => mods.hasFlag(Flag.IMPLICIT)
}
annottees match {
case q"$mods object $tname extends { ..$earlydefns } with ..$parents { $self => ..$body }" :: Nil =>
val body1 = body.map {
case q"$mods def $tname[..$tparams](...$paramss): $tpt = $expr" =>
val paramss1 =
if (paramss.nonEmpty && paramss.last.nonEmpty && isImplicit(paramss.last.head))
paramss.init :+ (paramss.last :+ implicitGreet)
else paramss :+ List(implicitGreet)
q"$mods def $tname[..$tparams](...$paramss1): $tpt = $expr"
case notMethod => notMethod
}
q"$mods object $tname extends { ..$earlydefns } with ..$parents { $self => ..$body1 }"
}
}
}
Usage:
#greetAware
object Foo {
def greet(name: String) = println(implicitly[Greet].g + " " + name)
}
#greetAware
object Bar {
def greetBar = Foo.greet("Bar")
def xxx(i: Int) = ???
def yyy(i: Int)(implicit s: String) = ???
}
implicit val greet: Greet = Greet("Goodbye")
Foo.greet("Sunshine") // Goodbye Sunshine
Bar.greetBar // Goodbye Bar
//scalac: object Foo extends scala.AnyRef {
// def <init>() = {
// super.<init>();
// ()
// };
// def greet(name: String)(implicit greet$macro$1: Greet = Greet("Hello")) = println(implicitly[Greet].g.$plus(" ").$plus(name))
//}
//scalac: object Bar extends scala.AnyRef {
// def <init>() = {
// super.<init>();
// ()
// };
// def greetBar(implicit greet$macro$2: Greet = Greet("Hello")) = Foo.greet("Bar");
// def xxx(i: Int)(implicit greet$macro$2: Greet = Greet("Hello")) = $qmark$qmark$qmark;
// def yyy(i: Int)(implicit s: String, greet$macro$2: Greet = Greet("Hello")) = $qmark$qmark$qmark
//}
Generate companion object for case class with scala-macros
some code example that i tried, it works i can get list of tuple (name -> type) but how to generate the object in the same scope?
import c.universe._
val tpe = weakTypeOf[T]
val fields = tpe.decls.collectFirst {
case m: MethodSymbol if m.isPrimaryConstructor => m
} .get
.paramLists
.head
val extractParams = fields.map { field =>
val name = field.asTerm.name
val fieldName = name.decodedName.toString
val NullaryMethodType(fieldType) = tpe.decl(name).typeSignature
c.Expr[List[(String, String)]](
q"""
($fieldName, ${fieldType.toString})
"""
)
Is it possible to annotate some case class and the generated companion make visible in the same scope?
// test case: defined a class with some fields
#GenerateCompanionWithFields
case class SomeCaseClass(i: Int, b: Byte, c: Char)
goal:
SomeCaseClass.i() // returns Int
SomeCaseClass.b() // returns Byte
SomeCaseClass.c() // returns Char
Your code seems to be intended for def macros. But if you want to generate companion you should use macro annotations
import scala.annotation.{StaticAnnotation, compileTimeOnly}
import scala.language.experimental.macros
import scala.reflect.macros.whitebox
object Macros {
#compileTimeOnly("enable macro paradise")
class GenerateCompanionWithFields extends StaticAnnotation {
def macroTransform(annottees: Any*): Any = macro Macro.impl
}
object Macro {
def impl(c: whitebox.Context)(annottees: c.Tree*): c.Tree = {
import c.universe._
annottees match {
case (cls # q"$_ class $tpname[..$_] $_(...$paramss) extends { ..$_ } with ..$_ { $_ => ..$_ }") :: Nil =>
val newMethods = paramss.flatten.map {
case q"$_ val $tname: $tpt = $_" =>
q"def $tname(): String = ${tpt.toString}"
}
q"""
$cls
object ${tpname.toTermName} {
..$newMethods
}
"""
}
}
}
}
import Macros._
object App {
#GenerateCompanionWithFields
case class SomeCaseClass(i: Int, b: Byte, c: Char)
}
//Warning:scalac: {
// case class SomeCaseClass extends scala.Product with scala.Serializable {
// <caseaccessor> <paramaccessor> val i: Int = _;
// <caseaccessor> <paramaccessor> val b: Byte = _;
// <caseaccessor> <paramaccessor> val c: Char = _;
// def <init>(i: Int, b: Byte, c: Char) = {
// super.<init>();
// ()
// }
// };
// object SomeCaseClass extends scala.AnyRef {
// def <init>() = {
// super.<init>();
// ()
// };
// def i(): String = "Int";
// def b(): String = "Byte";
// def c(): String = "Char"
// };
// ()
//}
automatically generate case object for case class
I'm using Swagger to annotate my API, and in our API we rely a lot on enumeratum. If I don't do anything, swagger won't recognize it and just call it object.
For example, I have this code that works:
sealed trait Mode extends EnumEntry
object Mode extends Enum[Mode] {
override def values = findValues
case object Initial extends Mode
case object Delta extends Mode
}
#ApiModel
case class Foobar(
#ApiModelProperty(dataType = "string", allowedValues = "Initial,Delta")
mode: Mode
)
However, I would like to avoid repeating the values as some of my types have many more than this example; I don't want to manually keep that in sync.
The problem is that the #ApiModel wants a constant in reference, so I can't do something like reference = Mode.values.mkString(",").
I did try a macro with macro paradise, typically so I can write:
#EnumeratumApiModel(Mode)
sealed trait Mode extends EnumEntry
object Mode extends Enum[Mode] {
override def values = findValues
case object Initial extends Mode
case object Delta extends Mode
}
...but it doesn't work because the macro pass can't access the Mode object.
What solution do I have to avoid repeating the values in the annotation?
This includes code so is too big for a comment.
I tried, that wouldn't work because the #ApiModel annotation wants a String constant as a value (and not a reference to a constant)
This piece of code compiles just fine for me (notice how you should avoid explicitly specifying the type):
import io.swagger.annotations._
import enumeratum._
#ApiModel(reference = Mode.reference)
sealed trait Mode extends EnumEntry
object Mode extends Enum[Mode] {
final val reference = "enum(Initial,Delta)" // this works!
//final val reference: String = "enum(Initial,Delta)" // surprisingly this doesn't!
override def values = findValues
case object Initial extends Mode
case object Delta extends Mode
}
So it seems to be enough to have another macro that would generate such reference string and I assume you already have one (or you can create one basing on the code of EnumMacros.findValuesImpl).
Update
Here is some code for POC that this can actually work. First you start with following macro annotation:
import scala.language.experimental.macros
import scala.annotation.{StaticAnnotation, compileTimeOnly}
import scala.reflect.macros.whitebox.Context
import scala.collection.immutable._
#compileTimeOnly("enable macro to expand macro annotations")
class SwaggerEnumContainer extends StaticAnnotation {
def macroTransform(annottees: Any*) = macro SwaggerEnumMacros.genListString
}
#compileTimeOnly("enable macro to expand macro annotations")
class SwaggerEnumValue(val readOnly: Boolean = false, val required: Boolean = false) extends StaticAnnotation {
def macroTransform(annottees: Any*) = macro SwaggerEnumMacros.genParamAnnotation
}
class SwaggerEnumMacros(val c: Context) {
import c.universe._
def genListString(annottees: c.Expr[Any]*): c.Expr[Any] = {
val result = annottees.map(_.tree).toList match {
case (xxx#q"object $name extends ..$parents { ..$body }") :: Nil =>
val enclosingObject = xxx.asInstanceOf[ModuleDef]
val q"${tq"$pname[..$ptargs]"}(...$pargss)" = parents.head
val enumTraitIdent = ptargs.head.asInstanceOf[Ident]
val subclassSymbols: List[TermName] = enclosingObject.impl.body.foldLeft(List.empty[TermName])((list, innerTree) => {
innerTree match {
case innerObj: ModuleDefApi =>
val innerParentIdent = innerObj.impl.parents.head.asInstanceOf[Ident]
if (enumTraitIdent.name.equals(innerParentIdent.name))
innerObj.name :: list
else
list
case _ => list
}
})
val reference = subclassSymbols.map(n => n.encodedName.toString).mkString(",")
q"""
object $name extends ..$parents {
final val allowableValues = $reference
..$body
}
"""
}
c.Expr[Any](result)
}
def genParamAnnotation(annottees: c.Expr[Any]*): c.Expr[Any] = {
val annotationParams: AnnotationParams = extractAnnotationParameters(c.prefix.tree)
val baseSwaggerAnnot =
q""" new ApiModelProperty(
dataType = "string",
allowableValues = Mode.allowableValues
) """.asInstanceOf[Apply] // why I have to force cast?
val swaggerAnnot: c.universe.Apply = annotationParams.addArgsTo(baseSwaggerAnnot)
annottees.map(_.tree).toList match {
// field definition
case List(param: ValDef) => c.Expr[Any](decorateValDef(param, swaggerAnnot))
// field in a case class = constructor param
case (param: ValDef) :: (rest#(_ :: _)) => decorateConstructorVal(param, rest, swaggerAnnot)
case _ => c.abort(c.enclosingPosition, "SwaggerEnumValue is expected to be used for value definitions")
}
}
def decorateValDef(valDef: ValDef, swaggerAnnot: Apply): ValDef = {
val q"$mods val $name: $tpt = $rhs" = valDef
val newMods: Modifiers = mods.mapAnnotations(al => swaggerAnnot :: al)
q"$newMods val $name: $tpt = $rhs"
}
def decorateConstructorVal(annottee: c.universe.ValDef, expandees: List[Tree], swaggerAnnot: Apply): c.Expr[Any] = {
val q"$_ val $tgtName: $_ = $_" = annottee
val outputs = expandees.map {
case q"$mods class $tpname[..$tparams] $ctorMods(...$paramss) extends { ..$earlydefns } with ..$parents { $self => ..$stats }" => {
// paramss is a 2d array so map inside map
val newParams: List[List[ValDef]] = paramss.map(_.map({
case valDef: ValDef if valDef.name == tgtName => decorateValDef(valDef, swaggerAnnot)
case otherParam => otherParam
}))
q"$mods class $tpname[..$tparams] $ctorMods(...$newParams) extends { ..$earlydefns } with ..$parents { $self => ..$stats }"
}
case otherTree => otherTree
}
c.Expr[Any](Block(outputs, Literal(Constant(()))))
}
case class AnnotationParams(readOnly: Boolean, required: Boolean) {
def customCopy(name: String, value: Any) = {
name match {
case "readOnly" => copy(readOnly = value.asInstanceOf[Boolean])
case "required" => copy(required = value.asInstanceOf[Boolean])
case _ => c.abort(c.enclosingPosition, s"Unknown parameter '$name'")
}
}
def addArgsTo(annot: Apply): Apply = {
val additionalArgs: List[AssignOrNamedArg] = List(
AssignOrNamedArg(q"readOnly", q"$readOnly"),
AssignOrNamedArg(q"required", q"$required")
)
Apply(annot.fun, annot.args ++ additionalArgs)
}
}
private def extractAnnotationParameters(tree: Tree): AnnotationParams = tree match {
case ap: Apply =>
val argNames = Array("readOnly", "required")
val defaults = AnnotationParams(readOnly = false, required = false)
ap.args.zipWithIndex.foldLeft(defaults)((acc, argAndIndex) => argAndIndex match {
case (lit: Literal, index: Int) => acc.customCopy(argNames(index), c.eval(c.Expr[Any](lit)))
case (namedArg: AssignOrNamedArg, _: Int) =>
val q"$name = $lit" = namedArg
acc.customCopy(name.asInstanceOf[Ident].name.toString, c.eval(c.Expr[Any](lit)))
case _ => c.abort(c.enclosingPosition, "Failed to parse annotation params: " + argAndIndex)
})
}
}
And then you can do this:
sealed trait Mode extends EnumEntry
#SwaggerEnumContainer
object Mode extends Enum[Mode] {
override def values = findValues
case object Initial extends Mode
case object Delta extends Mode
}
#ApiModel
case class Foobar(#ApiModelProperty(dataType = "string", allowableValues = Mode.allowableValues) mode: Mode)
Or you can do this which I think is a bit cleaner
#ApiModel
case class Foobar2(
#SwaggerEnumValue mode: Mode,
#SwaggerEnumValue(true) mode2: Mode,
#SwaggerEnumValue(required = true) mode3: Mode,
i: Int, s: String = "abc") {
#SwaggerEnumValue
val modeField: Mode = Mode.Delta
}
Note that this is still only a POC. Known deficiencies include:
#SwaggerEnumContainer can't handle case when some fake allowableValues is already defined with some fake value (which might be nicer for IDE)
#SwaggerEnumValue only supports two attributes from the range available in the original #ApiModelProperty
Inspired by travisbrown, I'm trying to use a macro to create some "smart constructors".
Given
package mypkg
sealed trait Hello[A]
case class Ohayo[A,B](a: (A,B)) extends Hello[A]
and
val smartConstructors = FreeMacros.liftConstructors[Hello]
The macro should find all the subclasses of Hello, look at their constructors, and extract a few elements to populate this tree for the "smart constructor":
q"""
def $methodName[..$typeParams](...$paramLists): $baseType =
$companionSymbol[..$typeArgs](...$argLists)
"""
I hoped to get:
val smartConstructors = new {
def ohayo[A, B](a: (A, B)): Hello[A] = Ohayo[A, B](a)
}
but instead get:
error: type mismatch;
found : (A(in class Ohayo), B(in class Ohayo))
required: ((some other)A(in class Ohayo), (some other)B(in class Ohayo))
val liftedConstructors = FreeMacros.liftConstructors[Hello]
At a glance, the tree looks ok to me:
scala> q" new { ..$wellTyped }"
res1: u.Tree =
{
final class $anon extends scala.AnyRef {
def <init>() = {
super.<init>();
()
};
def ohayo[A, B](a: (A, B)): net.arya.constructors.Hello[A] = Ohayo[A, B](a)
};
new $anon()
}
but I guess it invisibly isn't. If I naively try to freshen up the typeParams with info.typeParams.map(p => TypeName(p.name.toString)), I get "can't splice A as type parameter" when I do the quasiquoting.
Where am I going wrong? Thanks for taking a look.
-Arya
import scala.language.experimental.macros
import scala.reflect.api.Universe
import scala.reflect.macros.whitebox
class FreeMacros(val c: whitebox.Context) {
import c.universe._
import FreeMacros._
def liftedImpl[F[_]](implicit t: c.WeakTypeTag[F[_]]): Tree = {
val atc = t.tpe
val childSymbols: Set[ClassSymbol] = subCaseClassSymbols(c.universe)(atc.typeSymbol.asClass)
val wellTyped = childSymbols.map(ctorsForSymbol(c.universe)(atc)).unzip
q"new { ..${wellTyped} }"
}
}
object FreeMacros {
def liftConstructors[F[_]]: Any = macro FreeMacros.liftedImpl[F]
def smartName(name: String): String = (
name.toList match {
case h :: t => h.toLower :: t
case Nil => Nil
}
).mkString
def subCaseClassSymbols(u: Universe)(root: u.ClassSymbol): Set[u.ClassSymbol] = {
val subclasses = root.knownDirectSubclasses
val cast = subclasses.map(_.asInstanceOf[u.ClassSymbol])
val partitioned = mapped.partition(_.isCaseClass)
partitioned match {
case (caseClasses, regularClasses) => caseClasses ++ regularClasses.flatMap(r => subCaseClassSymbols(u)(r))
}
}
def ctorsForSymbol(u: Universe)(atc: u.Type)(caseClass: u.ClassSymbol): (u.DefDef, u.DefDef) = {
import u._
import internal._
// these didn't help
// def clearTypeSymbol(s: Symbol): TypeSymbol = internal.newTypeSymbol(NoSymbol, s.name.toTypeName, s.pos, if(s.isImplicit)Flag.IMPLICIT else NoFlags)
// def clearTypeSymbol2(s: Symbol): TypeSymbol = internal.newTypeSymbol(NoSymbol, s.name.toTypeName, NoPosition, if(s.isImplicit)Flag.IMPLICIT else NoFlags)
// def clearTypeDef(d: TypeDef): TypeDef = internal.typeDef(clearTypeSymbol(d.symbol))
val companionSymbol: Symbol = caseClass.companion
val info: Type = caseClass.info
val primaryCtor: Symbol = caseClass.primaryConstructor
val method = primaryCtor.asMethod
val typeParams = info.typeParams.map(internal.typeDef(_))
// val typeParams = info.typeParams.map(s => typeDef(newTypeSymbol(NoSymbol, s.name.toTypeName, NoPosition, NoFlags)))
// val typeParams = info.typeParams.map(s => internal.typeDef(clearTypeSymbol2(s)))
val typeArgs = info.typeParams.map(_.name)
val paramLists = method.paramLists.map(_.map(internal.valDef(_)))
val argLists = method.paramLists.map(_.map(_.asTerm.name))
val baseType = info.baseType(atc.typeSymbol)
val List(returnType) = baseType.typeArgs
val methodName = TermName(smartName(caseClass.name.toString))
val wellTyped =
q"""
def $methodName[..$typeParams](...$paramLists): $baseType =
$companionSymbol[..$typeArgs](...$argLists)
"""
wellTyped
}
}
P.S. I have been experimenting with toolbox.untypecheck / typecheck per this article but haven't found a working combination.
you need using
clas.typeArgs.map(_.toString).map(name => {
TypeDef(Modifiers(Flag.PARAM),TypeName(name), List(),TypeBoundsTree(EmptyTree, EmptyTree))
}
replace
info.typeParams.map(p => TypeName(p.name.toString))
it si my code
object GetSealedSubClass {
def ol3[T]: Any = macro GetSealedSubClassImpl.ol3[T]
}
class GetSealedSubClassImpl(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 ol3[T: c.WeakTypeTag]: c.universe.Tree = {
//get all sub class
val subClass = c.weakTypeOf[T]
.typeSymbol.asClass.knownDirectSubclasses
.map(e => e.asClass.toType)
//check type params must ia s sealed class
if (subClass.size < 1)
c.abort(c.enclosingPosition, s"${c.weakTypeOf[T]} is not a sealed class")
// get sub class constructor params
val subConstructorParams = subClass.map { e =>
//get constructor
e.members.filter(_.isConstructor)
//if the class has many Constructor then you need filter the main Constructor
.head.map(s => s.asMethod)
//get function param list
}.map(_.asMethod.paramLists.head)
.map(_.map(e => q"""${e.name.toTermName}:${e.info} """))
val outfunc = subClass zip subConstructorParams map {
case (clas, parm) =>
q"def smartConstructors[..${
clas.typeArgs.map(_.toString).map(name => {
TypeDef(Modifiers(Flag.PARAM), TypeName(name), List(), TypeBoundsTree(EmptyTree, EmptyTree))
})
}](..${parm})=${clas.typeSymbol.name.toTermName} (..${parm})"
}
val outClass =
q"""
object Term{
..${outfunc}
}
"""
showInfo(show(outClass))
q"""{
$outClass
Term
}
"""
}
}
using like this
sealed trait Hello[A]
case class Ohayo[A, B](a: (A, B)) extends Hello[A]
object GetSealed extends App {
val a = GetSealedSubClass.ol3[Hello[_]]
val b=a.asInstanceOf[ {def smartConstructors[A, B](a: (A, B)): Ohayo[A, B]}].smartConstructors(1, 2).a
println(b)
}