Scalac 2.13 编译大型自动生成的 scala 文件:方法太大

Nod*_*.JS 0 jvm code-generation scala sbt

我想知道是否有办法绕过这个限制。显然,这个限制是由于 JVM 而不是 Scala 造成的。该代码是自动生成的,因此不可能将其拆分为多个方法。

scalac: Error while emitting M_SEMANT
Method too large: M_SEMANT.visit_1_1_0 (LNode;)V
Run Code Online (Sandbox Code Playgroud)

存储库

Dmy*_*tin 5

我设法(几乎)编译了您自动生成的源代码。唯一可以击败代码生成器的是另一个代码生成器:)

该代码是自动生成的,因此不可能将其拆分为多个方法。

我想这并不完全正确。只是最好自动拆分代码。

我用的是斯卡拉梅塔。您的visit_1_1_0类方法M_SEMANT有许多分支if (...) ... else ...(以及自动生成的代码的其余部分)。我把一个分支改造if ($condExpr) $thenExpr else $elseExpr

def $condName(): Boolean = $condExpr
def $thenName() = $thenExpr
def $elseName() = $elseExpr
if ($condName()) $thenName() else $elseName()
Run Code Online (Sandbox Code Playgroud)

使ifthenelse成为嵌套方法。编译后,嵌套方法将移至类级别

嵌套方法的成本

您可以对所有内容递归地应用此转换if-else(然后您应该取消注释中的行ScalametaTransformer),但事实证明重写最外层的if-else内容就足够了visit_1_1_0。获取的方法似乎受到 JVM 限制。

这是结果sbt transformed/clean transform transformed/compile

matches M_SEMANT
matches template
matches visit_1_1_0
matches if-else
[error] /media/data/Projects1/cool-aps-semant1/cool-aps-semant/transformed/target/scala-2.13/src_managed/main/cool-semant.scala:31:15: type mismatch;
[error]  found   : Unit
[error]  required: Int
[error]               if (!cond) {
[error]               ^
[error] /media/data/Projects1/cool-aps-semant1/cool-aps-semant/transformed/target/scala-2.13/src_managed/main/cool-semant.scala:25:11: type mismatch;
[error]  found   : Unit
[error]  required: Int
[error]           if (!cond) {
[error]           ^
[error] two errors found
[error] (transformed / Compile / compileIncremental) Compilation failed
Run Code Online (Sandbox Code Playgroud)

由于这两个地方在你的原始代码中是相同的,我猜那些编译错误就在那里。但我们已经没有了Method too large

变换前的源在 中src/main/scala,变换后的源在 中transformed/target/scala-2.13/src_managed/main


项目/build.sbt

libraryDependencies ++= Seq(
  "org.scalameta" %% "scalameta" % "4.7.7"
)
Run Code Online (Sandbox Code Playgroud)

构建.sbt

ThisBuild / scalaVersion := "2.13.10"

lazy val root = (project in file("."))
  .settings(
    name := "aps-cool"
  )

lazy val transformed = project
  .settings(
    Compile / unmanagedSourceDirectories += (Compile / sourceManaged).value
  )

lazy val transform = taskKey[Unit]("Transform sources")

transform := {
  val inputDir  = (root / Compile / scalaSource).value
  val outputDir = (transformed / Compile / sourceManaged).value
  Generator.gen(inputDir, outputDir, Seq("cool-semant.scala"), Map("cool-semant.scala" -> Seq(960)))
}
Run Code Online (Sandbox Code Playgroud)

项目/Generator.scala

import sbt.*

object Generator {
  def gen(inputDir: File, outputDir: File, filesToTransform: Seq[String] = Seq(), emptyLineIndices: Map[String, Seq[Int]] = Map()): Unit = {
    val finder: PathFinder = inputDir ** "*.scala"

    for (inputFile <- finder.get) yield {
      val inputStr = IO.read(inputFile)
      val outputFile = outputDir / inputFile.relativeTo(inputDir).get.toString
      val outputStr =
        if (filesToTransform.isEmpty || filesToTransform.contains(inputFile.name))
          ScalametaTransformer.transform(inputStr, emptyLineIndices.getOrElse(inputFile.name, Seq()))
        else inputStr
      IO.write(outputFile, outputStr)
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

项目/ScalametaTransformer.scala

import scala.meta.*
import scala.util.Properties

object ScalametaTransformer {
  private val ifElseTransformer = new Transformer {
    override def apply(tree: Tree): Tree = tree match {
      case q"if ($condExpr) $thenExpr else $elseExpr" =>
        println(s"matches if-else")
        val condName = Term.fresh("cond")
        val thenName = Term.fresh("then")
        val elseName = Term.fresh("else")
        q"""
          def $condName(): Boolean = $condExpr
          def $thenName() = $thenExpr
          def $elseName() = $elseExpr
          if ($condName()) $thenName() else $elseName()
        """
      // apply recursively:
//      val condExpr1 = super.apply(condExpr).asInstanceOf[Term]
//      val thenExpr1 = super.apply(thenExpr).asInstanceOf[Term]
//      val elseExpr1 = super.apply(elseExpr).asInstanceOf[Term]
//      q"""
//        def $condName() = $condExpr1
//        def $thenName() = $thenExpr1
//        def $elseName() = $elseExpr1
//        if ($condName()) $thenName() else $elseName()
//      """

      case _ => super.apply(tree)
    }
  }

  private val classMethodTransformer = new Transformer {
    override def apply(tree: Tree): Tree = tree match {
      case q"..$mods class M_SEMANT[..$tparams] ..$ctorMods (...$paramss) $template" =>
        println("matches M_SEMANT")
        val template1 = template match {
          case template"{ ..$earlyStats } with ..$inits { $self => ..$stats }" =>
            println("matches template")
            val stats1 = stats.map {
              case q"..$mods def visit_1_1_0[..$tparams](...$paramss): $tpeopt = $expr" =>
                println("matches visit_1_1_0")
                val expr1 = ifElseTransformer(expr).asInstanceOf[Term]
                q"..$mods def visit_1_1_0[..$tparams](...$paramss): $tpeopt = $expr1"
              case t => t
            }

            template"{ ..$earlyStats } with ..$inits { $self => ..$stats1 }"
        }
        q"..$mods class M_SEMANT[..$tparams] ..$ctorMods (...$paramss) $template1"

      case _ => super.apply(tree)
    }
  }

  def transform(str: String, emptyLineIndices: Seq[Int] = Seq()): String = {
    val origTree = str.parse[Source].get
    val newTree = classMethodTransformer(origTree)
    insertEmptyLines(newTree.toString, emptyLineIndices)
  }

  private def insertEmptyLines(str: String, indices: Seq[Int]): String =
    indices.foldLeft(str)(insertEmptyLine)

  private def insertEmptyLine(str: String, index: Int): String =
    str.linesIterator.patch(index, Iterator.fill(1)(""), 0).mkString(Properties.lineSeparator)
}
Run Code Online (Sandbox Code Playgroud)

https://github.com/amir734jj/cool-aps-semant/pull/1

插入空行是https://github.com/scalameta/scalameta/issues/2046的解决方法