Можно ли использовать макрос для изменения сгенерированного кода вызова экземпляра структурного типа?

Например, в виде следующего кода:

object Test extends App
{
    trait Class
    {
        val f1: Int
    }

    val c = new Class {
        val f1: Int = 1
        val f2: String = "Class"
    }

    println(c.f1)
    println(c.f2)
}

Я просматриваю байт-код с помощью декомпилятора и замечаю, что компиляция генерирует java-интерфейс «Test.Class» как псевдокод:

trait Class
{
    val f1: Int
}

и класс «Test$$anon$1», реализующий «Test.Class», псевдокод как:

class Test$$anon$1 extends Class
{
    val f1: Int = 1
    val f2: String = "Class"
}

а затем компилятор инициализирует переменную 'c' как:

c = new Test$$anon$1()

затем вызывает член 'f1' как обычный вызов:

println(c.f1)

но он вызывает «f2», используя отражение:

println(reflMethod(c, f2))

Здесь, поскольку определение анонимного класса «Test$$anon$1» видно в той же области, можно ли использовать макрос для изменения сгенерированного кода, чтобы вызвать «f2» как обычное поле, избегая отражения?

Я просто хочу изменить код вызова в той же области, а не изменять код отражения в разных областях, например. экземпляр структурного типа в качестве аргумента в вызове функции. Так что я думаю, что это возможно в теории. Но я не знаком с макросом scala, предложения и примеры кода приветствуются. Спасибо!


person xiagao1982    schedule 08.06.2020    source источник
comment
Макросы Scala должны были бы генерировать действительный код Scala, который можно было бы скомпилировать. Что было бы здесь правильным кодом Scala, если бы не что-то, что должно использовать отражение?   -  person Mateusz Kubuszok    schedule 08.06.2020
comment
Тем не менее, я сомневаюсь, что это возможно, но это не было бы хорошей идеей и чрезмерной инженерией. Если вам не нужна структурная типизация, не используйте ее (укажите f2 либо в Class, либо в явном подтипе)   -  person cchantep    schedule 08.06.2020


Ответы (1)


Макросы (точнее, аннотации макросов, поскольку макросы def не имеют отношения к этой задаче) недостаточно. Вы хотите переписать не класс (черт, объект) или его параметр или член, а локальные выражения. Вы можете сделать это с помощью плагина-компилятора (см. также) во время компиляции или с Scalameta генерация кода перед компиляцией.

Если вы выбираете Scalameta, то на самом деле вы хотите переписать свои выражения семантически, а не синтаксически, потому что вы хотите перейти от локального выражения new Class... к определению trait Class... и проверить, есть ли там правильные члены. Итак, вам нужна Scalameta + SemanticDB. Удобнее использовать Scalameta + SemanticDB с Scalafix (см. также раздел для пользователей).

Вы можете создать собственное правило перезаписи. Затем вы можете использовать его либо для перезаписи кода на месте, либо для генерации кода (см. ниже).

rules/src/main/scala/MyRule.scala

import scalafix.v1._
import scala.meta._

class MyRule extends SemanticRule("MyRule") {
  override def isRewrite: Boolean = true

  override def description: String = "My Rule"

  override def fix(implicit doc: SemanticDocument): Patch = {
    doc.tree.collect {
      case tree @ q"new { ..$stats } with ..$inits { $self => ..$stats1 }" =>
        val symbols = stats1.collect {
          case q"..$mods val ..${List(p"$name")}: $tpeopt = $expr" =>
            name.syntax
        }

        val symbols1 = inits.headOption.flatMap(_.symbol.info).flatMap(_.signature match {
          case ClassSignature(type_parameters, parents, self, declarations) =>
            Some(declarations.map(_.symbol.displayName))
          case _ => None
        })

        symbols1 match {
          case None => Patch.empty
          case Some(symbols1) if symbols.forall(symbols1.contains) => Patch.empty
          case _ =>
            val anon = Type.fresh("anon$meta$")
            val tree1 =
              q"""
                class $anon extends ${template"{ ..$stats } with ..$inits { $self => ..$stats1 }"}
                new ${init"$anon()"}
              """
            Patch.replaceTree(tree, tree1.syntax)
        }
    }.asPatch
  }
}

в/src/main/scala/Test.scala

object Test extends App
{
  trait Class
  {
    val f1: Int
  }

  val c = new Class {
    val f1: Int = 1
    val f2: String = "Class"
  }

  println(c.f1)
  println(c.f2)
}

out/target/scala-2.13/src_managed/main/scala/Test.scala (после sbt out/compile)

object Test extends App
{
  trait Class
  {
    val f1: Int
  }

  val c = {
  class anon$meta$2 extends Class {
    val f1: Int = 1
    val f2: String = "Class"
  }
  new anon$meta$2()
}

  println(c.f1)
  println(c.f2)
}

build.sbt

name := "scalafix-codegen-demo"

inThisBuild(
  List(
    scalaVersion := "2.13.2",
    addCompilerPlugin(scalafixSemanticdb),
    scalacOptions ++= List(
      "-Yrangepos"
    )
  )
)

lazy val rules = project
  .settings(
    libraryDependencies += "ch.epfl.scala" %% "scalafix-core" % "0.9.16"
  )

lazy val in = project

lazy val out = project
  .settings(
    sourceGenerators.in(Compile) += Def.taskDyn {
      val root = baseDirectory.in(ThisBuild).value.toURI.toString
      val from = sourceDirectory.in(in, Compile).value
      val to = sourceManaged.in(Compile).value
      val outFrom = from.toURI.toString.stripSuffix("/").stripPrefix(root)
      val outTo = to.toURI.toString.stripSuffix("/").stripPrefix(root)
      Def.task {
        scalafix
          .in(in, Compile)
//          .toTask(s" ProcedureSyntax --out-from=$outFrom --out-to=$outTo")
          .toTask(s" --rules=file:rules/src/main/scala/MyRule.scala --out-from=$outFrom --out-to=$outTo")
          .value
        (to ** "*.scala").get
      }
    }.taskValue
  )

проект/plugins.sbt

addSbtPlugin("ch.epfl.scala" % "sbt-scalafix" % "0.9.16")

Другие примеры:

https://github.com/olafurpg/scalafix-codegen

https://github.com/DmytroMitin/scalafix-codegen

https://github.com/DmytroMitin/scalameta-demo

Условная компиляция Scala

Аннотация макроса для переопределения функции toString функции Scala

Как объединить несколько импортов в scala?

person Dmytro Mitin    schedule 08.06.2020
comment
Большое спасибо за исчерпывающие коды и ссылки! Я тщательно их изучу. Надеюсь, я смогу изучить больше навыков Scala и работать над этим. - person xiagao1982; 08.06.2020