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

问题描述 投票:0回答:1

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

scalac: Error while emitting M_SEMANT
Method too large: M_SEMANT.visit_1_1_0 (LNode;)V

存储库

scala jvm sbt
1个回答
0
投票

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

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

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

我用过Scalameta。你的方法

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

所以

if
then
else
成为嵌套方法。编译后,嵌套方法被移动到类级别

嵌套方法的代价

您可以对所有

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

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

Method too large
了。

变换前的源在

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


project/build.sbt

libraryDependencies ++= Seq(
  "org.scalameta" %% "scalameta" % "4.7.7"
)

build.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
  )

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

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

项目/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)

}

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

插入空行是 https://github.com/scalameta/scalameta/issues/2046

的解决方法
© www.soinside.com 2019 - 2024. All rights reserved.