首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >用Scala 3宏重写方法

用Scala 3宏重写方法
EN

Stack Overflow用户
提问于 2021-07-27 19:48:15
回答 1查看 348关注 0票数 4

我试图用Scala 3宏和好吃来覆盖一个方法。我想重写任何类型的方法。现在我从这个简单的案例开始。

我有一个测试基类:

代码语言:javascript
复制
class TestClass {
  def func(s: String) = "base"
}

我想要做到这一点,但通过使用好吃的方法,我发现不可能在带有引号和剪接的泛型类型上调用new A

代码语言:javascript
复制
'{
    new TestClass() {
       override def func(s: String) = "override"
    }
}.asExprOf[A]

我打印了上述代码的AST,并几乎重新创建了它。问题是我不能在生成的类上调用new --我看不到访问新类的符号或类型的方法。我还尝试了使用新名称的Symbol.requiredClass(),虽然它返回了一些符号,但在宏展开过程中出现了一个错误,没有找到该类。

我的问题是:

  • 在不显式调用:new Class {}引号的情况下,可以派生自定义类型吗?
  • ClassDef.copy是否注册了一个可以帮助创建新实例的新名称?
  • ClassDef的手动调用可以创建类的实例吗?
  • 我如何使用Symbol.requiredClass返回的符号,因为它返回一些东西,即使以前没有定义?

我创建的代码:

代码语言:javascript
复制
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 }) }
}

返回的代码打印时没有任何抱怨,如下所示:

代码语言:javascript
复制
{
@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)
}

但是,如果由宏返回,则在展开过程中得到一个错误:

代码语言:javascript
复制
[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

用法:

代码语言:javascript
复制
val res:TestClass = NewClass.newClass[TestClass](new TestClass)

谢谢你的帮助。

EN

回答 1

Stack Overflow用户

发布于 2022-09-18 22:01:18

使用新方法Symbol.newClass (Scala3.1.3),这变得非常容易:

代码语言:javascript
复制
import scala.annotation.experimental
import scala.quoted.*

object NewClass {
  inline def newClass[A]: A = ${newClassImpl[A]}

  @experimental
  def newClassImpl[A: Type](using Quotes): Expr[A] = {
    import quotes.reflect.*

    val name: String = TypeRepr.of[A].typeSymbol.name + "Impl"
    val parents = List(TypeTree.of[A])

    def decls(cls: Symbol): List[Symbol] =
      List(Symbol.newMethod(cls, "func", MethodType(List("s"))(_ => List(TypeRepr.of[String]), _ => TypeRepr.of[String]), Flags.Override, Symbol.noSymbol))

    val cls = Symbol.newClass(Symbol.spliceOwner, name, parents = parents.map(_.tpe), decls, selfType = None)
    val funcSym = cls.declaredMethod("func").head

    val funcDef = DefDef(funcSym, argss => Some('{"override"}.asTerm))
    val clsDef = ClassDef(cls, parents, body = List(funcDef))
    val newCls = Typed(Apply(Select(New(TypeIdent(cls)), cls.primaryConstructor), Nil), TypeTree.of[A])

    Block(List(clsDef), newCls).asExprOf[A]
  }
}

用法:

代码语言:javascript
复制
class TestClass {
  def func(s: String) = "base"
}

val res: TestClass = NewClass.newClass[TestClass]

//{
//  class TestClassImpl extends TestClass {
//    override def func(s: java.lang.String): java.lang.String = "override"
//  }
//
//  (new TestClassImpl(): TestClass)
//}

res.func("xxx") // override

博文:在宏中生成任意类实现的可能性

Scaladoc:Symbol.newClass

发行:支持使用Scala 3宏创建给定Type[A]的新实例

如何访问构造函数:

Scala 2在类体中附加一个方法(元编程) (Scala 2,编译器插件)

票数 2
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/68550985

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档