I'm trying to override a method with use of Scala 3 macros and TASTY. I'd like to override any method of any type. Now I'm starting with this simple case.
I have a test base class:
class TestClass {
def func(s: String) = "base"
}
I'd like to achieve this, but with use of TASTY as I found out that it is impossible to call new A
on a generic type with quotes and splices:
'{
new TestClass() {
override def func(s: String) = "override"
}
}.asExprOf[A]
I printed AST of the above code and I managed almost to recreate it. The problem is that I cannot call new
on the generated class - I don't see a way to access the symbol or type of the new class. I also tried Symbol.requiredClass()
with the new name, though it returned some symbol, I got an error during macro expansion that the class isn't found.
My questions are:
- Is it possible at all to derive custom types without explicitly invoking:
new Class {}
in quotes? - Does
ClassDef.copy
register a new name that can assist with new instance creation? - Can manual invocations of
ClassDef
create an instance of the class? - How can I make use of a symbol returned by
Symbol.requiredClass
as it returns something even if not defined before?
The code that I created:
import scala.quoted.*
object NewClass {
def newClassImpl[A: Type](e: Expr[A])(using Quotes): Expr[A] = {
import quotes.reflect.*
val typeRep = TypeRepr.of[A]
val ret = typeRep.classSymbol.map(_.tree) match {
case Some(
cd @ ClassDef(
name: String,
constr: DefDef,
parents: List[Tree],
selfOpt: Option[ValDef],
body: List[Statement]
)
) =>
println(cd.show(using Printer.TreeAnsiCode))
val newItemsOwner = Symbol.spliceOwner.owner
println("newItemsOwner = " + newItemsOwner)
def createFunction(args: Term)(using Quotes): Term = {
args
}
val newConstrSymbol = Symbol.newMethod(
newItemsOwner,
"<init>",
MethodType(Nil)(
_ => Nil,
_ => TypeRepr.of[Unit]
),
Flags.EmptyFlags,
Symbol.noSymbol
)
val newConstrDef: DefDef = DefDef(
newConstrSymbol,
{
case List(List(paramTerm: Term)) =>
Some(createFunction(paramTerm).changeOwner(newConstrSymbol))
case _ => None
}
)
val newMethodSymbol = Symbol.newMethod(
newItemsOwner,
"func",
MethodType(List("s"))(
_ => List(TypeRepr.of[String]),
_ => TypeRepr.of[String]
),
Flags.Override,
Symbol.noSymbol
)
val newMethodDef: DefDef = DefDef(
newMethodSymbol,
{
case List(List(paramTerm: Term)) =>
Some(createFunction(paramTerm).changeOwner(newMethodSymbol))
case _ => None
}
)
val parentSel = Select.unique(New(TypeTree.of[A]), "<init>")
val parent = Apply(parentSel, Nil)
val newClassDef: ClassDef = ClassDef.copy(cd)(
name + "$gen",
newConstrDef,
parent :: Nil,
None,
newMethodDef :: Nil
)
val app = Apply(
Select(New(TypeIdent(Symbol.requiredClass(name + "$gen"))), newConstrDef.symbol),
Nil
)
val block = Block(newClassDef :: Nil, Typed(app, TypeTree.of[A]))
val finalTerm = Inlined(Some(TypeTree.of[NewClass$]), Nil, block)
println(finalTerm.show(using Printer.TreeAnsiCode))
println(finalTerm.show(using Printer.TreeStructure))
finalTerm.asExprOf[A]
case other =>
println("No class def found: " + other)
e
}
println("Returned:")
println(ret.asTerm.show(using Printer.TreeAnsiCode))
println(ret.asTerm.show(using Printer.TreeStructure))
ret
}
inline def newClass[A](a: A): A = ${ newClassImpl[A]('{ a }) }
}
The returned code is printed without complains as:
{
@scala.annotation.internal.SourceFile("src/main/scala/MethodsMain.scala") class TestClass$gen() extends TestClass {
override def func(s: java.lang.String): java.lang.String = s
}
(new TestClass$gen(): TestClass)
}
But if returned by the macro I got an error during expansion:
[error] |Bad symbolic reference. A signature
[error] |refers to TestClass$gen/T in package <empty> which is not available.
[error] |It may be completely missing from the current classpath, or the version on
[error] |the classpath might be incompatible with the version used when compiling the signature.
[error] | This location contains code that was inlined from NewClass.scala:86
Usage:
val res:TestClass = NewClass.newClass[TestClass](new TestClass)
Thanks for any help.