Scala3: Crafting Types Through Metaprogramming? - scala
I am coding using scala3, leveraging programmatic structural types.
The structural types happen to mimic existing case classes:
their definition is pure boiler plate,
hence the temptation to craft them through meta-programming.
I understand how to craft a function implementation, typically via typeclass derivation.
But here we are trying to craft a (structural) type.
This was possible in scala2, via class macro annotation, but those are gone in scala3.
Is there a way ? If so how ?
Code below is the result I would like to obtain :
// Library part
trait View extends scala.Selectable :
def selectDynamic(key:String) =
println(s"$key is being looked up")
???
// DSL Definition part
case class SomeDefWithInt ( i : Int )
case class SomeDefWithString( s : String )
// Boiler-plate code
type ViewOf[M] = M match
case SomeDefWithInt => View { def i : Int }
case SomeDefWithString => View { def s : String }
// Mockup usage
class V extends View
val v = V()
v.asInstanceOf[ViewOf[SomeDefWithInt ]].i
v.asInstanceOf[ViewOf[SomeDefWithString]].s
is it possible to create ViewOf[M] of an arbitrary case class M ?
Thank you !
Just in case, here is what I meant by hiding ViewOf inside a type class (type classes is an alternative to match types). Sadly, in Scala 3 this is wordy.
(version 1)
import scala.annotation.experimental
import scala.quoted.{Expr, Quotes, Type, quotes}
// Library part
trait View extends Selectable {
def applyDynamic(key: String)(args: Any*): Any = {
println(s"$key is being looked up with $args")
if (key == "i") 1
else if (key == "s") "a"
else ???
}
def selectDynamic(key: String): Any = {
println(s"$key is being looked up")
if (key == "i1") 2
else if (key == "s1") "b"
else ???
}
}
// type class
trait ViewOf[M <: Product] {
type Out <: View
}
object ViewOf {
transparent inline given mkViewOf[M <: Product]: ViewOf[M] = ${givenImpl[M]}
#experimental // because .newClass is #experimental
def givenImpl[M <: Product : Type](using Quotes): Expr[ViewOf[M]] = {
import quotes.reflect.*
extension (symb: Symbol) {
def setFlags(flags: Flags): Symbol = {
given dotty.tools.dotc.core.Contexts.Context =
quotes.asInstanceOf[scala.quoted.runtime.impl.QuotesImpl].ctx
symb.asInstanceOf[dotty.tools.dotc.core.Symbols.Symbol]
.denot.setFlag(flags.asInstanceOf[dotty.tools.dotc.core.Flags.FlagSet])
symb
}
}
def newType(cls: Symbol, name: String, tpe: TypeRepr, flags: Flags = Flags.EmptyFlags, privateWithin: Symbol = Symbol.noSymbol): Symbol = {
given dotty.tools.dotc.core.Contexts.Context =
quotes.asInstanceOf[scala.quoted.runtime.impl.QuotesImpl].ctx
import dotty.tools.dotc.core.Decorators.toTypeName
dotty.tools.dotc.core.Symbols.newSymbol(
cls.asInstanceOf[dotty.tools.dotc.core.Symbols.Symbol],
name.toTypeName,
flags.asInstanceOf[dotty.tools.dotc.core.Flags.FlagSet],
tpe.asInstanceOf[dotty.tools.dotc.core.Types.Type],
privateWithin.asInstanceOf[dotty.tools.dotc.core.Symbols.Symbol]
).asInstanceOf[Symbol]
}
val M = TypeRepr.of[M]
val fields = M.typeSymbol.caseFields
val viewImplDecls = (cls: Symbol) =>
fields.flatMap(fieldSymb =>
Seq(
Symbol.newMethod(cls, fieldSymb.name, MethodType(Nil)(_ => Nil, _ => M.memberType(fieldSymb)), // vararg? MatchError: Inlined
Flags.Deferred, privateWithin = Symbol.noSymbol),
Symbol.newVal(cls, fieldSymb.name + "1", M.memberType(fieldSymb),
Flags.Deferred, privateWithin = Symbol.noSymbol)
)
)
val viewImplParents = List(TypeTree.of[AnyRef], TypeTree.of[View])
val viewImplCls = Symbol.newClass(Symbol.spliceOwner, "ViewImpl", viewImplParents.map(_.tpe), viewImplDecls, selfType = None)
.setFlags(Flags.Trait)
val methodDefs = fields.flatMap(fieldSymb => {
val methodSymb = viewImplCls.declaredMethod(fieldSymb.name).head
val valSymb = viewImplCls.fieldMember(fieldSymb.name + "1")
Seq(
DefDef(methodSymb, _ => None),
ValDef(valSymb, None)
)
})
val viewImplClsDef = ClassDef(viewImplCls, viewImplParents, body = methodDefs)
val viewOfImplDecls = (cls: Symbol) => List(newType(cls, "Out",
TypeBounds(viewImplCls.typeRef, viewImplCls.typeRef), Flags.Override))
val viewOfTypeTree = TypeTree.of[ViewOf[M]]
val viewOfImplParents = List(TypeTree.of[AnyRef], viewOfTypeTree)
val viewOfImplCls = Symbol.newClass(Symbol.spliceOwner, "ViewOfImpl", viewOfImplParents.map(_.tpe), viewOfImplDecls, selfType = None)
val outSymb = viewOfImplCls.declaredType("Out").head
val outTypeDef = TypeDef(outSymb)
val viewOfImplClsDef = ClassDef(viewOfImplCls, viewOfImplParents, body = List(outTypeDef))
val newViewOfImpl = Apply(Select(New(TypeIdent(viewOfImplCls)), viewOfImplCls.primaryConstructor), Nil)
val res = Block(List(viewImplClsDef, viewOfImplClsDef), newViewOfImpl).asExprOf[ViewOf[M]]
println(res.show + "=" + res.asTerm.show(using Printer.TreeStructure))
res
}
}
extension (v: View) {
def refine[M <: Product](using viewOf: ViewOf[M]): viewOf.Out = v.asInstanceOf[viewOf.Out]
}
// DSL Definition part
case class SomeDefWithInt ( i : Int )
case class SomeDefWithString( s : String )
// Mockup usage
class V extends View
val v = V()
println(v.refine[SomeDefWithInt].i())
// i is being looked up with ArraySeq()
// 1
println(v.refine[SomeDefWithString].s())
// s is being looked up with ArraySeq()
// a
println(v.refine[SomeDefWithInt].i1)
// i1 is being looked up
// 2
println(v.refine[SomeDefWithString].s1)
// s1 is being looked up
// b
//scalac: {
// trait ViewImpl extends java.lang.Object with Macros.View {
// def i(): scala.Int
// val i1: scala.Int
// }
// class ViewOfImpl extends java.lang.Object with Macros.ViewOf[App.SomeDefWithInt] {
// type Out // actually, type Out = ViewImpl
// }
// new ViewOfImpl()
//}=Block(List(ClassDef("ViewImpl", DefDef("<init>", Nil, Inferred(), None), List(Inferred(), Inferred()), None, List(DefDef("i", List(TermParamClause(Nil)), Inferred(), None), ValDef("i1", Inferred(), None))), ClassDef("ViewOfImpl", DefDef("<init>", Nil, Inferred(), None), List(Inferred(), Inferred()), None, List(TypeDef("Out", TypeBoundsTree(Inferred(), Inferred()))))), Apply(Select(New(Inferred()), "<init>"), Nil))
//scalac: {
// trait ViewImpl extends java.lang.Object with Macros.View {
// def s(): scala.Predef.String
// val s1: scala.Predef.String
// }
// class ViewOfImpl extends java.lang.Object with Macros.ViewOf[App.SomeDefWithString] {
// type Out // actually, type Out = ViewImpl
// }
// new ViewOfImpl()
//}=Block(List(ClassDef("ViewImpl", DefDef("<init>", Nil, Inferred(), None), List(Inferred(), Inferred()), None, List(DefDef("s", List(TermParamClause(Nil)), Inferred(), None), ValDef("s1", Inferred(), None))), ClassDef("ViewOfImpl", DefDef("<init>", Nil, Inferred(), None), List(Inferred(), Inferred()), None, List(TypeDef("Out", TypeBoundsTree(Inferred(), Inferred()))))), Apply(Select(New(Inferred()), "<init>"), Nil))
ViewOf[M] is meant is to be used by a DSL user, so no way to hide it within a derived type class.
Not sure I understood.
Method Override with Scala 3 Macros
`tq` equivalent in Scala 3 macros
How to generate a class in Dotty with macro?
How to splice multiple expressions in quote syntax of scala 3 macros?
How to access parameter list of case class in a dotty macro
https://github.com/lampepfl/dotty/discussions/14056
Another implementation of the type class (with a refinement type instead of trait type)
(version 2)
trait ViewOf[M <: Product] {
type Out <: View
}
object ViewOf {
transparent inline given mkViewOf[M <: Product]: ViewOf[M] = ${givenImpl[M]}
#experimental // because .newClass is #experimental
def givenImpl[M <: Product : Type](using Quotes): Expr[ViewOf[M]] = {
import quotes.reflect.*
def makeRefinement(parent: TypeRepr, names: List[String], infos: List[TypeRepr]): TypeRepr =
names.zip(infos).foldLeft(parent){ case (acc, (name, tpe)) => Refinement(acc, name, tpe) }
def newType(cls: Symbol, name: String, tpe: TypeRepr, flags: Flags = Flags.EmptyFlags, privateWithin: Symbol = Symbol.noSymbol): Symbol = {
given dotty.tools.dotc.core.Contexts.Context =
quotes.asInstanceOf[scala.quoted.runtime.impl.QuotesImpl].ctx
import dotty.tools.dotc.core.Decorators.toTypeName
dotty.tools.dotc.core.Symbols.newSymbol(
cls.asInstanceOf[dotty.tools.dotc.core.Symbols.Symbol],
name.toTypeName,
flags.asInstanceOf[dotty.tools.dotc.core.Flags.FlagSet],
tpe.asInstanceOf[dotty.tools.dotc.core.Types.Type],
privateWithin.asInstanceOf[dotty.tools.dotc.core.Symbols.Symbol]
).asInstanceOf[Symbol]
}
val M = TypeRepr.of[M]
val fields = M.typeSymbol.caseFields
val fieldNames = fields.flatMap(fieldSymb => Seq(fieldSymb.name, fieldSymb.name + "1"))
val fieldMethodTypes = fields.flatMap(fieldSymb => Seq(
MethodType(List("args"))(_ => List(AnnotatedType(TypeRepr.of[Any], '{new scala.annotation.internal.Repeated()}.asTerm)), _ => M.memberType(fieldSymb)),
ByNameType(M.memberType(fieldSymb)))
)
val refinement = makeRefinement(TypeRepr.of[View], fieldNames, fieldMethodTypes)
val viewOfImplDecls = (cls: Symbol) => List(newType(cls, "Out",
TypeBounds(refinement, refinement), Flags.Override))
val viewOfTypeTree = TypeTree.of[ViewOf[M]]
val viewOfImplParents = List(TypeTree.of[AnyRef], viewOfTypeTree)
val viewOfImplCls = Symbol.newClass(Symbol.spliceOwner, "ViewOfImpl", viewOfImplParents.map(_.tpe), viewOfImplDecls, selfType = None)
val outSymb = viewOfImplCls.declaredType("Out").head
val outTypeDef = TypeDef(outSymb)
val viewOfImplClsDef = ClassDef(viewOfImplCls, viewOfImplParents, body = List(outTypeDef))
val newViewOfImpl = Apply(Select(New(TypeIdent(viewOfImplCls)), viewOfImplCls.primaryConstructor), Nil)
val res = Block(List(viewOfImplClsDef), newViewOfImpl).asExprOf[ViewOf[M]]
println(res.show + "=" + res.asTerm.show(using Printer.TreeStructure))
res
}
}
println(v.refine[SomeDefWithInt].i(10, "x", true))
//i is being looked up with ArraySeq((10,x,true))
//1
println(v.refine[SomeDefWithString].s(20, "y", 30L))
//s is being looked up with ArraySeq((20,y,30))
//a
println(v.refine[SomeDefWithInt].i1)
//i1 is being looked up
//2
println(v.refine[SomeDefWithString].s1)
//s1 is being looked up
//b
//scalac: {
// class ViewOfImpl extends java.lang.Object with Macros.ViewOf[App.SomeDefWithInt] {
// type Out // actually, type Out = View {def i(args: Any*): Int; def i1: Int}
// }
// new ViewOfImpl()
//}=Block(List(ClassDef("ViewOfImpl", DefDef("<init>", Nil, Inferred(), None), List(Inferred(), Inferred()), None, List(TypeDef("Out", TypeBoundsTree(Inferred(), Inferred()))))), Apply(Select(New(Inferred()), "<init>"), Nil))
Also we can use Mirror instead of reflection
(version 3)
trait ViewOf[M <: Product] {
type Out <: View
}
object ViewOf {
transparent inline given mkViewOf[M <: Product]: ViewOf[M] = ${givenImpl[M]}
def givenImpl[M <: Product : Type](using Quotes): Expr[ViewOf[M]] = {
import quotes.reflect.*
def makeRefinement(parent: TypeRepr, namesAndTypes: List[(String, TypeRepr)]): TypeRepr =
namesAndTypes.foldLeft(parent) { case (acc, (name, tpe)) => Refinement(acc, name, tpe) }
def mkNamesAndTypes[mels: Type, mets: Type]: List[(String, TypeRepr)] =
(Type.of[mels], Type.of[mets]) match {
case ('[EmptyTuple], '[EmptyTuple]) => Nil
case ('[mel *: melTail], '[met *: metTail] ) => {
val name = Type.valueOfConstant[mel].get.toString
val name1 = name + "1"
//scala.MatchError: Inlined(Ident(Macros$),List(),Apply(Select(New(Select(Select(Select(Ident(scala),annotation),internal),Repeated)),<init>),List())) (of class dotty.tools.dotc.ast.Trees$Inlined)
//val methodType = MethodType(List("args"))(_ => List(AnnotatedType(TypeRepr.of[Any], '{new scala.annotation.internal.Repeated()}.asTerm)), _ => TypeRepr.of[met])
val methodType = MethodType(Nil)(_ => Nil, _ => TypeRepr.of[met])
val methodType1 = ByNameType(TypeRepr.of[met])
(name, methodType) :: (name1, methodType1) :: mkNamesAndTypes[melTail, metTail]
}
}
val namesAndTypes = Expr.summon[Mirror.ProductOf[M]].get match {
case '{ $m: Mirror.ProductOf[M] { type MirroredElemLabels = mels; type MirroredElemTypes = mets } } =>
mkNamesAndTypes[mels, mets]
}
val res = makeRefinement(TypeRepr.of[View], namesAndTypes).asType match {
case '[tpe] =>
'{
new ViewOf[M] {
type Out = tpe
}
}
}
println(res.show)
res
}
}
Unfortunately, this doesn't work because of an extra type ascription (Expr looses type refinement)
//scalac: {
// final class $anon() extends Macros.ViewOf[App.SomeDefWithInt] {
// type Out = Macros.View {
// def i(): scala.Int
// def i1: scala.Int
// }
// }
//
// (new $anon(): Macros.ViewOf[App.SomeDefWithInt]) // <--- HERE!!!
//}
https://github.com/lampepfl/dotty/issues/15566 (for structural refinements i.e. defs, their loss seems to be expected behavior, but type refinement loss can be a bug)
So, at least once we have to use low-level newClass to avoid type ascription
(version 4)
trait ViewOf[M <: Product] {
type Out <: View
}
object ViewOf {
transparent inline given mkViewOf[M <: Product]: ViewOf[M] = ${givenImpl[M]}
#experimental // because .newClass is #experimental
def givenImpl[M <: Product : Type](using Quotes): Expr[ViewOf[M]] = {
import quotes.reflect.*
def makeRefinement(parent: TypeRepr, namesAndTypes: List[(String, TypeRepr)]): TypeRepr =
namesAndTypes.foldLeft(parent) { case (acc, (name, tpe)) => Refinement(acc, name, tpe) }
def mkNamesAndTypes[mels: Type, mets: Type]: List[(String, TypeRepr)] =
(Type.of[mels], Type.of[mets]) match {
case ('[EmptyTuple], '[EmptyTuple]) => Nil
case ('[mel *: melTail], '[met *: metTail] ) => {
val name = Type.valueOfConstant[mel].get.toString
val name1 = name + "1"
val methodType = MethodType(List("args"))(_ => List(AnnotatedType(TypeRepr.of[Any], '{new scala.annotation.internal.Repeated()}.asTerm)), _ => TypeRepr.of[met])
val methodType1 = ByNameType(TypeRepr.of[met])
(name, methodType) :: (name1, methodType1) :: mkNamesAndTypes[melTail, metTail]
}
}
val namesAndTypes = Expr.summon[Mirror.ProductOf[M]].get match {
case '{ $m: Mirror.ProductOf[M] { type MirroredElemLabels = mels; type MirroredElemTypes = mets } } =>
mkNamesAndTypes[mels, mets]
}
val refinement = makeRefinement(TypeRepr.of[View], namesAndTypes)
def newType(cls: Symbol, name: String, tpe: TypeRepr, flags: Flags = Flags.EmptyFlags, privateWithin: Symbol = Symbol.noSymbol): Symbol = {
given dotty.tools.dotc.core.Contexts.Context =
quotes.asInstanceOf[scala.quoted.runtime.impl.QuotesImpl].ctx
import dotty.tools.dotc.core.Decorators.toTypeName
dotty.tools.dotc.core.Symbols.newSymbol(
cls.asInstanceOf[dotty.tools.dotc.core.Symbols.Symbol],
name.toTypeName,
flags.asInstanceOf[dotty.tools.dotc.core.Flags.FlagSet],
tpe.asInstanceOf[dotty.tools.dotc.core.Types.Type],
privateWithin.asInstanceOf[dotty.tools.dotc.core.Symbols.Symbol]
).asInstanceOf[Symbol]
}
val viewOfImplDecls = (cls: Symbol) => List(newType(cls, "Out",
TypeBounds(refinement, refinement),
Flags.Override))
val viewOfTypeTree = TypeTree.of[ViewOf[M]]
val viewOfImplParents = List(TypeTree.of[AnyRef], viewOfTypeTree)
val viewOfImplCls = Symbol.newClass(Symbol.spliceOwner, "ViewOfImpl", viewOfImplParents.map(_.tpe), viewOfImplDecls, selfType = None)
val outSymb = viewOfImplCls.declaredType("Out").head
val outTypeDef = TypeDef(outSymb)
val viewOfImplClsDef = ClassDef(viewOfImplCls, viewOfImplParents, body = List(outTypeDef))
// this would be an extra type ascription to be avoided
// val newViewOfImpl = Typed(Apply(Select(New(TypeIdent(viewOfImplCls)), viewOfImplCls.primaryConstructor), Nil), TypeTree.of[ViewOf[M]])
val newViewOfImpl = Apply(Select(New(TypeIdent(viewOfImplCls)), viewOfImplCls.primaryConstructor), Nil)
val res = Block(List(viewOfImplClsDef), newViewOfImpl).asExprOf[ViewOf[M]]
println(res.show + "=" + res.asTerm.show(using Printer.TreeStructure))
res
}
}
Related
Scala 3 macros: create a new polynmorphic function using the reflect api
I intend to create the simple polymorphic function expression below using the Quotes.reflect API: new PolyFunction { def apply[X](a: X): X = a } What I have attempted is shown below with parts that I could not implement replaced by ???: val name: String = "$anon" val parents = List(TypeTree.of[Object], TypeTree.of[PolyFunction]) def decls(cls: Symbol): List[Symbol] = List( Symbol.newMethod( cls, "apply", MethodType(List("a"))( { mt => ??? }, mt => ??? ) ) ) val cls = Symbol.newClass(Symbol.spliceOwner, name, parents = parents.map(_.tpe), decls, selfType = None) val applySym = cls.declaredMethod("apply").head val applyDef = DefDef(applySym, argss => Some(???)) val clsDef = ClassDef(cls, parents, body = List(applyDef)) val closure = Block(List(clsDef),Apply(Select(New(TypeIdent(cls)), cls.primaryConstructor), Nil)) closure.asExpr The problem, mainly, is I am unable to declare the type parameter X, and its reference in the first value parameter and the functions return type. I have noticed some reflection functions are annotated as experimental.
Try import scala.annotation.experimental import scala.quoted.* inline def newPoly: PolyFunction = ${newPolyImpl} #experimental def newPolyImpl(using Quotes): Expr[PolyFunction] = { import quotes.reflect.* val name: String = "$anon" val parents = List(TypeTree.of[Object], TypeTree.of[PolyFunction]) def decls(cls: Symbol): List[Symbol] = List( Symbol.newMethod( cls, "apply", PolyType(List("X"))(_ => List(TypeBounds.empty), polyType => { val typeParam = polyType.param(0) MethodType(List("a"))(_ => List(typeParam), _ => typeParam) }) ) ) val cls = Symbol.newClass(Symbol.spliceOwner, name, parents = parents.map(_.tpe), decls, selfType = None) val applySym = cls.declaredMethod("apply").head // argss=List(List(TypeTree[TypeRef(NoPrefix,type X)]), List(Ident(a))) val applyDef = DefDef(applySym, argss => Some(argss(1)(0).asInstanceOf[Term])) val clsDef = ClassDef(cls, parents, body = List(applyDef)) val closure = Block(List(clsDef), Apply(Select(New(TypeIdent(cls)), cls.primaryConstructor), Nil)) closure.asExprOf[PolyFunction] } Usage: newPoly //scalac: { // class $anon extends java.lang.Object with scala.PolyFunction { // def apply[X](a: X): X = a // } // new $anon() //} Scala 3.2.1 Method Override with Scala 3 Macros Scala3: Crafting Types Through Metaprogramming? `tq` equivalent in Scala 3 macros
Scala3 macro summon typeclass instance of a TypeTree (no type arg)
trait Show[T] { def show(t: T): String } Give such Show typeclass, I want to generate show for case class like def caseClassShow[A](using Type[A], Quotes): Expr[Show[A]] = { import quotes.reflect._ def shows(caseClassExpr: Expr[A]): Expr[String] = { val caseClassTerm = caseClassExpr.asTerm val parts = TypeRepr.of[A].typeSymbol.caseFields.collect { case cf if cf.isValDef => val valDefTree = cf.tree.asInstanceOf[ValDef] val valType = valDefTree.tpt val showCtor = TypeTree.of[Show[_]] val valShowType = Applied(showCtor, List(valType)) val showInstance = Expr.summon[valShowType] // compile error, how to summon the instance here val valuePart = Apply(Select.unique(showInstance, "show"), List(Select(caseClassTerm, cf))) '{s"${Expr(cf.name)}:${valuePart}"} } val strParts = Expr.ofList(parts) '{$strParts.mkString(",")} } '{ new Show[A] { def show(a: A) = { ${shows('{a})} } } } } But the showInstance part won't compile, so how to summon an implicit Show[X] here ?
Implicits.search can be used to summon implicit instance if there is no type arg avaiable for Expr.summon val valDefTree = cf.tree.asInstanceOf[ValDef] val valType = valDefTree.tpt val showCtor = TypeRepr.typeConstructorOf(classOf[Show[_]]) val valShowType = showCtor.appliedTo(valType.tpe) Implicits.search(valShowType) match { case si: ImplicitSearchSuccess => val siExpr: Expr[Show[Any]] = si.tree.asExpr.asInstanceOf[Expr[Show[Any]]] val valueExpr = Select(caseClassTerm, cf).asExpr '{$siExpr.show($valueExpr)} }
how to display value of case class in scala
case class Keyword(id: Int = 0, words: String) val my= Keyword(123, "hello") val fields: Array[Field] = my.getClass.getDeclaredFields for (i <- fields.indices) { println(fields(i).getName +":"+ my.productElement(i)) } id:123 title:keyword's title it's ok. def outputCaseClass[A](obj:A){ val fields: Array[Field] = obj.getClass.getDeclaredFields for (i <- fields.indices) { println(fields(i).getName +":"+ obj.productElement(i)) } } outputCaseClass(my) it's wrong
import scala.reflect.runtime.{universe => ru} def printCaseClassParams[C: scala.reflect.ClassTag](instance: C):Unit = { val runtimeMirror = ru.runtimeMirror(instance.getClass.getClassLoader) val instanceMirror = runtimeMirror.reflect(instance) val tpe = instanceMirror.symbol.toType tpe.members .filter(member => member.asTerm.isCaseAccessor && member.asTerm.isMethod) .map(member => { val term = member.asTerm val termName = term.name.toString val termValue = instanceMirror.reflectField(term).get termName + ":" + termValue }) .toList .reverse .foreach(s => println(s)) } // Now you can use it with any case classes, case class Keyword(id: Int = 0, words: String) val my = Keyword(123, "hello") printCaseClassParams(my) // id:123 // words:hello
productElement is a Method of the Product Base trait. Try to use a method signature like this: def outputCaseClass[A <: Product](obj:A){ .. } However it still won't work for inner case classes (fields also reports the $outer-Field, which productElement won't return and so it crashes with IndexOutOfBoundsException).
type parameter mismatch with WeakTypeTag reflection + quasiquoting (I think!)
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) }
How to get a name of a class member?
I want to be able to do something like this: prepare form: val formDescription = formBuilder(_.textField[User](_.firstName) .textField[User](_.lastName) ).build showForm(formDescription) extract data from user filled form, using User: //contains data of a form submitted by a user: val formData: Map[String, String] = getFormData val newUser = User(id = randomUuid, firstName = formData.extract[User](_.firstName)) One solution I see is to use a dynamic proxy that extends provided class and remembers what was invoked on him: def getFieldName[T:Manifest](foo: T => Any) = { val clazz = implicitly[Manifest[T]].erasure val proxy = createDynamicProxy(clazz) foo(proxy) proxy.lastInvokedMethodName } Is there a better way to do it? Is there any lib that implements it already?
This reflective approach takes a case class and invokes its companion apply, calling getField and fetching default args if the field is not in the data. import scala.reflect.runtime.{currentMirror => cm, universe => uni} import uni._ def fromXML(xml: Node): Option[PluginDescription] = { def extract[A]()(implicit tt: TypeTag[A]): Option[A] = { // extract one field def getField(field: String): Option[String] = { val text = (xml \\ field).text.trim if (text == "") None else Some(text) } val apply = uni.newTermName("apply") val module = uni.typeOf[A].typeSymbol.companionSymbol.asModule val ts = module.moduleClass.typeSignature val m = (ts member apply).asMethod val im = cm reflect (cm reflectModule module).instance val mm = im reflectMethod m def getDefault(i: Int): Option[Any] = { val n = uni.newTermName("apply$default$" + (i+1)) val m = ts member n if (m == NoSymbol) None else Some((im reflectMethod m.asMethod)()) } def extractArgs(pss: List[List[Symbol]]): List[Option[Any]] = pss.flatten.zipWithIndex map (p => getField(p._1.name.encoded) orElse getDefault(p._2)) val args = extractArgs(m.paramss) if (args exists (!_.isDefined)) None else Some(mm(args.flatten: _*).asInstanceOf[A]) } // check the top-level tag xml match { case <plugin>{_*}</plugin> => extract[PluginDescription]() case _ => None } } The idea was to do something like: case class User(id: Int = randomUuid, firstName: String, lastName: String) val user = extract[User]()
That's my own solution: package utils import javassist.util.proxy.{MethodHandler, MethodFilter, ProxyFactory} import org.specs2.mutable._ import javassist.util.proxy.Proxy import java.lang.reflect.{Constructor, Method} class DynamicProxyTest extends Specification with MemberNameGetter { "Dynamic proxy" should { "extract field name" in { memberName[TestClass](_.a) must ===("a") memberName[TestClass](_.i) must ===("i") memberName[TestClass](_.b) must ===("b") memberName[TestClass](_.variable) must ===("variable") memberName[TestClass](_.value) must ===("value") memberName[TestClass](_.method) must ===("method") } } } trait MemberNameGetter { def memberName[T: Manifest](foo: T => Any) = { val mf = manifest[T] val clazz = mf.erasure val proxyFactory = new ProxyFactory proxyFactory.setSuperclass(clazz) proxyFactory.setFilter(new MethodFilter { def isHandled(p1: Method) = true }) val newClass = proxyFactory.createClass() var lastInvokedMethod: String = null val mh = new MethodHandler { def invoke(p1: Any, p2: Method, p3: Method, p4: Array[AnyRef]) = { lastInvokedMethod = p2.getName p3.invoke(p1, p4: _*) } } val constructor = defaultConstructor(newClass) val parameters = defaultConstructorParameters(constructor) // val proxy = constructor.newInstance("dsf", new Integer(0)) val proxy2 = constructor.newInstance(parameters: _*) proxy2.asInstanceOf[Proxy].setHandler(mh) foo(proxy2.asInstanceOf[T]) lastInvokedMethod } private def defaultConstructor(c: Class[_]) = c.getConstructors.head private def defaultConstructorParameters(constructor: Constructor[_]) = { val parameterTypes = constructor.getParameterTypes parameterTypes.map{ case Integer.TYPE => Integer.valueOf(0) case _ => null } } } case class TestClass(a: String, i: Int, b: Boolean) { var variable = "asdf" val value = "asdfasdfasd" def method = "method" } val mh = new MethodHandler { def invoke(p1: Any, p2: Method, p3: Method, p4: Array[AnyRef]) = { lastInvokedMethod = p2.getName p3.invoke(p1, p4: _*) } } val constructor = defaultConstructor(newClass) val parameters = defaultConstructorParameters(constructor) // val proxy = constructor.newInstance("dsf", new Integer(0)) val proxy2 = constructor.newInstance(parameters: _*) proxy2.asInstanceOf[Proxy].setHandler(mh) foo(proxy2.asInstanceOf[T]) lastInvokedMethod } private def defaultConstructor(c: Class[_]) = c.getConstructors.head private def defaultConstructorParameters(constructor: Constructor[_]) = { val parameterTypes = constructor.getParameterTypes parameterTypes.map{ case Integer.TYPE => Integer.valueOf(0) case java.lang.Double.TYPE => java.lang.Double.valueOf(0) case java.lang.Long.TYPE => java.lang.Long.valueOf(0) case java.lang.Boolean.TYPE => java.lang.Boolean.FALSE case _ => null } } } case class TestClass(a: String, i: Int, b: Boolean) { var variable = "asdf" val value = "asdfasdfasd" def method = "method" }