0

I am wondering if there is a way to get around this limitation. Apparently, this limitation is because of JVM and not Scala. This code is auto-generated and so it's not possible to split it up into multiple methods.

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

Repository

Dmytro Mitin
  • 48,194
  • 3
  • 28
  • 66
Node.JS
  • 1,042
  • 6
  • 44
  • 114
  • 6
    That method is like `2700` lines long, jesus. - Anyways, no, there is nothing you can't do to fix that immediate problem since it is just a limitation of the target platform. - Possible workarounds to explore: Use **ScalaJS** or **ScalaNative** instead of compiling to the **JVM**, opening a bug issue on whatever tool you used to generate the code to see if they are able to fix the generator, check if the input you used to generate the code can be changed so that the generated code is not so long, or manually split the offending methods. – Luis Miguel Mejía Suárez Apr 20 '23 at 19:05
  • 1
    Just for the context, previous question was https://stackoverflow.com/questions/75746688 The code is generated with https://github.com/boyland/aps https://www2.eecs.berkeley.edu/Pubs/TechRpts/1996/CSD-96-916.pdf – Dmytro Mitin Apr 21 '23 at 11:38

1 Answers1

5

I managed to (almost) compile your auto-generated sources. The only what can defeat a code generator is another code generator :)

This code is auto-generated and so it's not possible to split it up into multiple methods.

I guess this is not completely true. Just it's better to split the code automatically.

I used Scalameta. Your method visit_1_1_0 of class M_SEMANT has many branching if (...) ... else ... (as well as the rest of your auto-generated code). I transform a branch if ($condExpr) $thenExpr else $elseExpr into

def $condName(): Boolean = $condExpr
def $thenName() = $thenExpr
def $elseName() = $elseExpr
if ($condName()) $thenName() else $elseName()

so that if, then, and else become nested methods. Upon compilation, nested methods are moved to the class level

The cost of nested methods

You can apply this transformation recursively, for all if-else (then you should uncomment lines in ScalametaTransformer) but it turned out that it was enough to re-write the outermost if-else in visit_1_1_0. Obtained methods seem to be under JVM limitation.

This is the result of 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

Since those two places are the same in your original code, I guess those compile errors were present there. But we don't have Method too large any more.

Sources before transform are in src/main/scala, sources after transform are in 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
  )

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

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

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

Inserting an empty line is a workaround for https://github.com/scalameta/scalameta/issues/2046

Dmytro Mitin
  • 48,194
  • 3
  • 28
  • 66