type parameter mismatch with WeakTypeTag reflection + quasiquoting (I think!) - scala

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)
}

Related

Scala3: Crafting Types Through Metaprogramming?

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
}
}

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)}
}

Run Macro to generate code from parent class in Scala

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"))

How to prevent typecheck in StaticAnnotation?

I am creating some macro libraries that reads some information from annotation on the enclosing method.
#info(foo(bar, baz))
def enclosing() = {
myMacro()
}
These information are encoded as foo(bar, baz) in a StaticAnnotation #info.
foo(bar, baz) contains information myMacro need, however, foo(bar, baz) is not able to type-check at the position #info, and cause compiler error when type-checking foo(bar, baz).
I wonder if I can create a macro dontTypecheck that prevent foo(bar, baz) being type checked. So that I can create something like:
#info(dontTypecheck {
foo(bar, baz)
})
def enclosing() = {
myMacro()
}
The dontTypecheck macro should produce a Tree that contains untype-checked foo(bar, baz).
How to create the dontTypecheck macro?
one idea is use another annotation save info
class Info[T](t: T) extends scala.annotation.StaticAnnotation {
}
class AnnInfo extends StaticAnnotation {
def macroTransform(annottees: Any*): Any = macro AnnInfImpl.impl
}
trait AnnotationUtils {
val c: scala.reflect.macros.blackbox.Context
import c.universe._
final def getAnnotation(x: MemberDef) = x.mods.annotations
}
class AnnInfImpl(val c: blackbox.Context) extends AnnotationUtils {
import c.universe._
// edit 1
def impl(annottees: Tree*): Tree = {
annottees.head match {
case x: DefDef =>
// collect value from `#Info(value)`
val info: List[Tree] = getAnnotation(x).collect { case q"new $name ($value)" => value }
val newBody =
q"""
{
val info = ${info.map(e => show(e))}
println(info)// just print it
${x.rhs}
}"""
DefDef(
mods = Modifiers(), //dropMods
name = x.name,
tparams = x.tparams,
vparamss = x.vparamss,
tpt = x.tpt,
rhs = newBody
)
}
}
}
// test
class AnnInfoTest {
val a = 1
val b = 2
def f(a: Int, b: Int) = a + b
#Info(f(a, b))
#AnnInfo
def e = ???
}
if you call e will print List(f(a, b))

Instantiating a case class with default args via reflection

I need to be able to instantiate various case classes through reflection, both by figuring out the argument types of the constructor, as well as invoking the constructor with all default arguments.
I've come as far as this:
import reflect.runtime.{universe => ru}
val m = ru.runtimeMirror(getClass.getClassLoader)
case class Bar(i: Int = 33)
val tpe = ru.typeOf[Bar]
val classBar = tpe.typeSymbol.asClass
val cm = m.reflectClass(classBar)
val ctor = tpe.declaration(ru.nme.CONSTRUCTOR).asMethod
val ctorm = cm.reflectConstructor(ctor)
// figuring out arg types
val arg1 = ctor.paramss.head.head
arg1.typeSignature =:= ru.typeOf[Int] // true
// etc.
// instantiating with given args
val p = ctorm(33)
Now the missing part:
val p2 = ctorm() // IllegalArgumentException: wrong number of arguments
So how can I create p2 with the default arguments of Bar, i.e. what would be Bar() without reflection.
So in the linked question, the :power REPL uses internal API, which means that defaultGetterName is not available, so we need to construct that from hand. An adoption from #som-snytt 's answer:
def newDefault[A](implicit t: reflect.ClassTag[A]): A = {
import reflect.runtime.{universe => ru, currentMirror => cm}
val clazz = cm.classSymbol(t.runtimeClass)
val mod = clazz.companionSymbol.asModule
val im = cm.reflect(cm.reflectModule(mod).instance)
val ts = im.symbol.typeSignature
val mApply = ts.member(ru.newTermName("apply")).asMethod
val syms = mApply.paramss.flatten
val args = syms.zipWithIndex.map { case (p, i) =>
val mDef = ts.member(ru.newTermName(s"apply$$default$$${i+1}")).asMethod
im.reflectMethod(mDef)()
}
im.reflectMethod(mApply)(args: _*).asInstanceOf[A]
}
case class Foo(bar: Int = 33)
val f = newDefault[Foo] // ok
Is this really the shortest path?
Not minimized... and not endorsing...
scala> import scala.reflect.runtime.universe
import scala.reflect.runtime.universe
scala> import scala.reflect.internal.{ Definitions, SymbolTable, StdNames }
import scala.reflect.internal.{Definitions, SymbolTable, StdNames}
scala> val ds = universe.asInstanceOf[Definitions with SymbolTable with StdNames]
ds: scala.reflect.internal.Definitions with scala.reflect.internal.SymbolTable with scala.reflect.internal.StdNames = scala.reflect.runtime.JavaUniverse#52a16a10
scala> val n = ds.newTermName("foo")
n: ds.TermName = foo
scala> ds.nme.defaultGetterName(n,1)
res1: ds.TermName = foo$default$1
Here's a working version that you can copy into your codebase:
import scala.reflect.api
import scala.reflect.api.{TypeCreator, Universe}
import scala.reflect.runtime.universe._
object Maker {
val mirror = runtimeMirror(getClass.getClassLoader)
var makerRunNumber = 1
def apply[T: TypeTag]: T = {
val method = typeOf[T].companion.decl(TermName("apply")).asMethod
val params = method.paramLists.head
val args = params.map { param =>
makerRunNumber += 1
param.info match {
case t if t <:< typeOf[Enumeration#Value] => chooseEnumValue(convert(t).asInstanceOf[TypeTag[_ <: Enumeration]])
case t if t =:= typeOf[Int] => makerRunNumber
case t if t =:= typeOf[Long] => makerRunNumber
case t if t =:= typeOf[Date] => new Date(Time.now.inMillis)
case t if t <:< typeOf[Option[_]] => None
case t if t =:= typeOf[String] && param.name.decodedName.toString.toLowerCase.contains("email") => s"random-$arbitrary#give.asia"
case t if t =:= typeOf[String] => s"arbitrary-$makerRunNumber"
case t if t =:= typeOf[Boolean] => false
case t if t <:< typeOf[Seq[_]] => List.empty
case t if t <:< typeOf[Map[_, _]] => Map.empty
// Add more special cases here.
case t if isCaseClass(t) => apply(convert(t))
case t => throw new Exception(s"Maker doesn't support generating $t")
}
}
val obj = mirror.reflectModule(typeOf[T].typeSymbol.companion.asModule).instance
mirror.reflect(obj).reflectMethod(method)(args:_*).asInstanceOf[T]
}
def chooseEnumValue[E <: Enumeration: TypeTag]: E#Value = {
val parentType = typeOf[E].asInstanceOf[TypeRef].pre
val valuesMethod = parentType.baseType(typeOf[Enumeration].typeSymbol).decl(TermName("values")).asMethod
val obj = mirror.reflectModule(parentType.termSymbol.asModule).instance
mirror.reflect(obj).reflectMethod(valuesMethod)().asInstanceOf[E#ValueSet].head
}
def convert(tpe: Type): TypeTag[_] = {
TypeTag.apply(
runtimeMirror(getClass.getClassLoader),
new TypeCreator {
override def apply[U <: Universe with Singleton](m: api.Mirror[U]) = {
tpe.asInstanceOf[U # Type]
}
}
)
}
def isCaseClass(t: Type) = {
t.companion.decls.exists(_.name.decodedName.toString == "apply") &&
t.decls.exists(_.name.decodedName.toString == "copy")
}
}
And, when you want to use it, you can call:
val user = Maker[User]
val user2 = Maker[User].copy(email = "someemail#email.com")
The code above generates arbitrary and unique values. The data aren't exactly randomised. It's best for using in tests.
It works with Enum and nested case class. You can also easily extend it to support some other special types.
Read our full blog post here: https://give.engineering/2018/08/24/instantiate-case-class-with-arbitrary-value.html
This is the most complete example how to create case class via reflection with default constructor parameters(Github source):
import scala.reflect.runtime.universe
import scala.reflect.internal.{Definitions, SymbolTable, StdNames}
object Main {
def newInstanceWithDefaultParameters(className: String): Any = {
val runtimeMirror: universe.Mirror = universe.runtimeMirror(getClass.getClassLoader)
val ds = universe.asInstanceOf[Definitions with SymbolTable with StdNames]
val classSymbol = runtimeMirror.staticClass(className)
val classMirror = runtimeMirror.reflectClass(classSymbol)
val moduleSymbol = runtimeMirror.staticModule(className)
val moduleMirror = runtimeMirror.reflectModule(moduleSymbol)
val moduleInstanceMirror = runtimeMirror.reflect(moduleMirror.instance)
val defaultValueMethodSymbols = moduleMirror.symbol.info.members
.filter(_.name.toString.startsWith(ds.nme.defaultGetterName(ds.newTermName("apply"), 1).toString.dropRight(1)))
.toSeq
.reverse
.map(_.asMethod)
val defaultValueMethods = defaultValueMethodSymbols.map(moduleInstanceMirror.reflectMethod).toList
val primaryConstructorMirror = classMirror.reflectConstructor(classSymbol.primaryConstructor.asMethod)
primaryConstructorMirror.apply(defaultValueMethods.map(_.apply()): _*)
}
def main(args: Array[String]): Unit = {
val instance = newInstanceWithDefaultParameters(classOf[Bar].getName)
println(instance)
}
}
case class Bar(i: Int = 33)