diff --git a/build.sbt b/build.sbt index 9a37bbc47..31427956c 100644 --- a/build.sbt +++ b/build.sbt @@ -98,7 +98,8 @@ lazy val core = projectMatrix BuildInfoKey.action("scala213")(Dependencies.scala213), BuildInfoKey.action("scala30")(Dependencies.scala30), BuildInfoKey.action("scala31Plus")(Dependencies.scala31Plus), - BuildInfoKey.action("scala34Plus")(Dependencies.scala34Plus) + BuildInfoKey.action("scala34Plus")(Dependencies.scala34Plus), + BuildInfoKey.action("scala372Plus")(Dependencies.scala372Plus) ), buildInfoPackage := "ch.epfl.scala.debugadapter" ) diff --git a/modules/core/src/main/scala/ch/epfl/scala/debugadapter/ClassPathEntry.scala b/modules/core/src/main/scala/ch/epfl/scala/debugadapter/ClassPathEntry.scala index c43eb9d9a..1cb02cbb5 100644 --- a/modules/core/src/main/scala/ch/epfl/scala/debugadapter/ClassPathEntry.scala +++ b/modules/core/src/main/scala/ch/epfl/scala/debugadapter/ClassPathEntry.scala @@ -38,14 +38,22 @@ final case class Module( final case class Library(artifactId: String, version: String, absolutePath: Path, sourceEntries: Seq[SourceEntry]) extends ManagedEntry { override def name: String = artifactId + + private def versionSuffix = artifactId.split('_').lastOption + + override def isScala2: Boolean = + scalaVersion.exists(_.isScala2) || versionSuffix.exists(_.startsWith("2.")) + override def isScala3: Boolean = + scalaVersion.exists(_.isScala3) || versionSuffix.exists(_.startsWith("3")) + override def isJava: Boolean = + scalaVersion.isEmpty && !versionSuffix.exists(suffix => suffix.startsWith("2.") || suffix.startsWith("3.")) + def scalaVersion: Option[ScalaVersion] = { - if (artifactId == "scala-library") Some(ScalaVersion(version)) - else { - artifactId - .split('_') - .lastOption - .filter(bv => bv.startsWith("2.12") || bv.startsWith("2.13") || bv.startsWith("3")) - .map(ScalaVersion.apply) - } + if ( + artifactId == "scala-library" || artifactId.startsWith("scala3-library_3") || + artifactId.startsWith("scala-compiler") || artifactId.startsWith("scala-compiler_3") + ) + Some(ScalaVersion(version)) + else None } } diff --git a/modules/core/src/main/scala/ch/epfl/scala/debugadapter/ScalaVersion.scala b/modules/core/src/main/scala/ch/epfl/scala/debugadapter/ScalaVersion.scala index 563647f44..5ed257bac 100644 --- a/modules/core/src/main/scala/ch/epfl/scala/debugadapter/ScalaVersion.scala +++ b/modules/core/src/main/scala/ch/epfl/scala/debugadapter/ScalaVersion.scala @@ -19,6 +19,10 @@ case class ScalaVersion(value: String) extends Ordered[ScalaVersion] { .get } + def minor: Int = parts match { + case (_, minor, _) => minor + } + override def compare(that: ScalaVersion): Int = (parts, that.parts) match { case ((x, _, _), (y, _, _)) if x != y => x - y @@ -41,5 +45,6 @@ object ScalaVersion { val `3.0` = ScalaVersion(BuildInfo.scala30) val `3.1+` = ScalaVersion(BuildInfo.scala31Plus) val `3.4+` = ScalaVersion(BuildInfo.scala34Plus) + val `3.7.2+` = ScalaVersion(BuildInfo.scala372Plus) val `3.5.0` = ScalaVersion("3.5.0") } diff --git a/modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/DebugTools.scala b/modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/DebugTools.scala index 36b4988f7..5ed9a3056 100644 --- a/modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/DebugTools.scala +++ b/modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/DebugTools.scala @@ -139,7 +139,11 @@ object DebugTools { } for { classLoader <- if (entry.isScala2) scala2Loader else if (entry.isScala3) scala3Loader else None - scalaVersion <- entry.scalaVersion + scalaVersion <- entry.scalaVersion.orElse { + if (entry.isScala2) Some(scala2Version) + else if (entry.isScala3) Some(scala3Version) + else None + } compiler <- ExpressionCompiler(scalaVersion, scalacOptions, classPath, classLoader) .warnFailure(logger, s"Cannot load expression compiler of Scala $scalaVersion") } yield entry -> compiler diff --git a/modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/EvaluationProvider.scala b/modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/EvaluationProvider.scala index 2150ab593..a0c1deb53 100644 --- a/modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/EvaluationProvider.scala +++ b/modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/EvaluationProvider.scala @@ -1,6 +1,5 @@ package ch.epfl.scala.debugadapter.internal -import ch.epfl.scala.debugadapter.BuildInfo import ch.epfl.scala.debugadapter.ClassEntry import ch.epfl.scala.debugadapter.DebugConfig import ch.epfl.scala.debugadapter.Debuggee @@ -143,15 +142,16 @@ private[internal] class EvaluationProvider( case m: ManagedEntry => m.scalaVersion match { case None => - s"Failed resolving scala-expression-compiler:${BuildInfo.version} for ${entry.name} (Missing Scala Version)" + s"Failed resolving expression compiler for ${entry.name} (Missing Scala Version)" case Some(sv) => - s"""|Failed resolving scala-expression-compiler:${BuildInfo.version} for Scala $sv. + val compilerSource = if (sv.isScala3 && sv.minor >= 7) "scala3-compiler" else "scala-expression-compiler" + s"""|Failed resolving $compilerSource for Scala $sv. |Please open an issue at https://github.com/scalacenter/scala-debug-adapter.""".stripMargin } case _: JavaRuntime => - s"Failed resolving scala-expression-compiler:${BuildInfo.version} for ${entry.name} (Missing Scala Version)" + s"Failed resolving expression compiler for ${entry.name} (Missing Scala Version)" case _ => - s"Failed resolving scala-expression-compiler:${BuildInfo.version} for ${entry.name} (Unknown Scala Version)" + s"Failed resolving expression compiler for ${entry.name} (Unknown Scala Version)" } private def containsMethodCall(tree: RuntimeEvaluationTree): Boolean = { diff --git a/modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/evaluator/ExpressionCompiler.scala b/modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/evaluator/ExpressionCompiler.scala index a13edfac0..507125d88 100644 --- a/modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/evaluator/ExpressionCompiler.scala +++ b/modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/evaluator/ExpressionCompiler.scala @@ -13,13 +13,26 @@ import scala.util.Failure import scala.util.Success import scala.util.Try -private[debugadapter] class ExpressionCompiler( +private[debugadapter] trait ExpressionCompiler { + def compile( + outDir: Path, + expressionClassName: String, + sourceFile: Path, + line: Int, + expression: String, + localNames: Set[String], + pckg: String, + testMode: Boolean + ): Try[Unit] +} + +private[debugadapter] class ExpressionCompilerPre37( instance: Any, compileMethod: Method, val scalaVersion: ScalaVersion, scalacOptions: Seq[String], classPath: String -) { +) extends ExpressionCompiler { def compile( outDir: Path, expressionClassName: String, @@ -55,6 +68,78 @@ private[debugadapter] class ExpressionCompiler( } } +private[debugadapter] class ExpressionCompilerPost37( + instance: Any, + compileMethod: Method, + classLoader: ClassLoader, + val scalaVersion: ScalaVersion, + scalacOptions: Seq[String], + classPath: String +) extends ExpressionCompiler { + + private def expressionCompilerConfig( + packageName: String, + outputClassName: String, + breakpointLine: Int, + expression: String, + localVariables: java.util.Set[String], + errorReporter: Consumer[String], + testMode: Boolean + ): Object = { + val clazz = Class.forName("dotty.tools.debug.ExpressionCompilerConfig", true, classLoader) + val instance = clazz.getDeclaredConstructor().newInstance() + val withPackageName = clazz.getMethod("withPackageName", classOf[String]).invoke(instance, packageName) + val withOutputClassName = + clazz.getMethod("withOutputClassName", classOf[String]).invoke(withPackageName, outputClassName) + val withBreakpointLine = clazz + .getMethod("withBreakpointLine", classOf[Int]) + .invoke(withOutputClassName, Integer.valueOf(breakpointLine)) + val withExpression = clazz.getMethod("withExpression", classOf[String]).invoke(withBreakpointLine, expression) + val withLocalVariables = + clazz.getMethod("withLocalVariables", classOf[java.util.Set[String]]).invoke(withExpression, localVariables) + val withErrorReporter = + clazz.getMethod("withErrorReporter", classOf[Consumer[String]]).invoke(withLocalVariables, errorReporter) + withErrorReporter.asInstanceOf[Object] + } + + def compile( + outDir: Path, + expressionClassName: String, + sourceFile: Path, + line: Int, + expression: String, + localNames: Set[String], + pckg: String, + testMode: Boolean + ): Try[Unit] = { + try { + val errors = Buffer.empty[String] + val configInstance = expressionCompilerConfig( + pckg, + expressionClassName, + line, + expression, + localNames.asJava, + { error => errors += error }: Consumer[String], + testMode + ) + val res = compileMethod + .invoke( + instance, + outDir, + classPath, + scalacOptions.toArray, + sourceFile, + configInstance + ) + .asInstanceOf[Boolean] + if (res) Success(()) else Failure(Errors.compilationFailure(errors.toSeq)) + } catch { + case cause: InvocationTargetException => Failure(cause.getCause()) + } + } +} + private[debugadapter] object ExpressionCompiler { def apply( scalaVersion: ScalaVersion, @@ -64,13 +149,30 @@ private[debugadapter] object ExpressionCompiler { ): Try[ExpressionCompiler] = { val className = if (scalaVersion.isScala2) "scala.tools.nsc.ExpressionCompilerBridge" - else "dotty.tools.dotc.ExpressionCompilerBridge" + else if (scalaVersion.isScala3 && scalaVersion.minor >= 7) { + "dotty.tools.debug.ExpressionCompilerBridge" + } else { + "dotty.tools.dotc.ExpressionCompilerBridge" + } try { val clazz = Class.forName(className, true, classLoader) val instance = clazz.getDeclaredConstructor().newInstance() val method = clazz.getMethods.find(_.getName == "run").get - Success(new ExpressionCompiler(instance, method, scalaVersion, scalacOptions, classPath)) + if (scalaVersion.isScala3 && scalaVersion.minor >= 7) { + Success( + new ExpressionCompilerPost37( + instance, + method, + classLoader, + scalaVersion, + scalacOptions, + classPath + ) + ) + } else { + Success(new ExpressionCompilerPre37(instance, method, scalaVersion, scalacOptions, classPath)) + } } catch { case cause: Throwable => Failure(cause) } diff --git a/modules/sbt-plugin/src/main/scala/ch/epfl/scala/debugadapter/sbtplugin/internal/SbtDebugToolsResolver.scala b/modules/sbt-plugin/src/main/scala/ch/epfl/scala/debugadapter/sbtplugin/internal/SbtDebugToolsResolver.scala index 8696308ea..33779fce8 100644 --- a/modules/sbt-plugin/src/main/scala/ch/epfl/scala/debugadapter/sbtplugin/internal/SbtDebugToolsResolver.scala +++ b/modules/sbt-plugin/src/main/scala/ch/epfl/scala/debugadapter/sbtplugin/internal/SbtDebugToolsResolver.scala @@ -25,36 +25,38 @@ class SbtDebugToolsResolver( ) extends DebugToolsResolver { override def resolveExpressionCompiler(scalaVersion: ScalaVersion): Try[ClassLoader] = { - val org = BuildInfo.organization - val artifact = s"${BuildInfo.expressionCompilerName}_$scalaVersion" - val version = BuildInfo.version - - for (report <- fetchArtifactsOf(org % artifact % version, Seq.empty)) - yield - if (scalaInstance.version == scalaVersion.value) { - val expressionCompilerJars = report - .select( - configurationFilter(Runtime.name), - moduleFilter(org, artifact, version) | moduleFilter( - "org.scala-lang.modules", - "scala-collection-compat_2.12" - ), - artifactFilter(extension = "jar", classifier = "") - ) - .map(_.toURI.toURL) - .toArray - new URLClassLoader(expressionCompilerJars, scalaInstance.loader) - } else { - val expressionCompilerJars = report - .select( - configurationFilter(Runtime.name), - moduleFilter(), - artifactFilter(extension = "jar", classifier = "") - ) - .map(_.toURI.toURL) - .toArray - new URLClassLoader(expressionCompilerJars, null) - } + if (scalaVersion.isScala3 && scalaVersion.minor >= 7) { + Success(scalaInstance.loader) + } else { + val (org, artifact, version) = + (BuildInfo.organization, s"${BuildInfo.expressionCompilerName}_$scalaVersion", BuildInfo.version) + for (report <- fetchArtifactsOf(org % artifact % version, Seq.empty)) + yield + if (scalaInstance.version == scalaVersion.value) { + val expressionCompilerJars = report + .select( + configurationFilter(Runtime.name), + moduleFilter(org, artifact, version) | moduleFilter( + "org.scala-lang.modules", + "scala-collection-compat_2.12" + ), + artifactFilter(extension = "jar", classifier = "") + ) + .map(_.toURI.toURL) + .toArray + new URLClassLoader(expressionCompilerJars, scalaInstance.loader) + } else { + val expressionCompilerJars = report + .select( + configurationFilter(Runtime.name), + moduleFilter(), + artifactFilter(extension = "jar", classifier = "") + ) + .map(_.toURI.toURL) + .toArray + new URLClassLoader(expressionCompilerJars, null) + } + } } override def resolveDecoder(scalaVersion: ScalaVersion): Try[Seq[java.nio.file.Path]] = { diff --git a/modules/tests/src/main/scala/ch/epfl/scala/debugadapter/testfmk/ScalaInstance.scala b/modules/tests/src/main/scala/ch/epfl/scala/debugadapter/testfmk/ScalaInstance.scala index 3f234116f..7ff323127 100644 --- a/modules/tests/src/main/scala/ch/epfl/scala/debugadapter/testfmk/ScalaInstance.scala +++ b/modules/tests/src/main/scala/ch/epfl/scala/debugadapter/testfmk/ScalaInstance.scala @@ -9,12 +9,17 @@ import java.io.File sealed abstract class ScalaInstance( val libraryJars: Seq[Library], compilerJars: Seq[Library], - expressionCompilerJar: Library, + expressionCompilerJar: Option[Library], val decoderJars: Seq[Library] ) { val libraryClassLoader = new URLClassLoader(libraryJars.map(_.toURL).toArray, null) val compilerClassLoader = new URLClassLoader(compilerJars.map(_.toURL).toArray, libraryClassLoader) - val expressionCompilerClassLoader = new URLClassLoader(Array(expressionCompilerJar.toURL), compilerClassLoader) + val expressionCompilerClassLoader = expressionCompilerJar match { + case Some(jar) => + new URLClassLoader(Array(jar.toURL), compilerClassLoader) + case None => + compilerClassLoader + } def compile( classDir: Path, @@ -40,7 +45,7 @@ final class Scala2Instance( libraryJars: Seq[Library], compilerJars: Seq[Library], expressionCompilerJar: Library -) extends ScalaInstance(libraryJars, compilerJars, expressionCompilerJar, Seq.empty) { +) extends ScalaInstance(libraryJars, compilerJars, Some(expressionCompilerJar), Seq.empty) { override protected def compileInternal(args: Array[String]): Unit = { val main = compilerClassLoader.loadClass("scala.tools.nsc.Main") val process = main.getMethod("process", classOf[Array[String]]) @@ -52,7 +57,7 @@ final class Scala2Instance( final class Scala3Instance( libraryJars: Seq[Library], compilerJars: Seq[Library], - expressionCompilerJar: Library, + expressionCompilerJar: Option[Library], stepFilterJars: Seq[Library] ) extends ScalaInstance(libraryJars, compilerJars, expressionCompilerJar, stepFilterJars) { override protected def compileInternal(args: Array[String]): Unit = { diff --git a/modules/tests/src/main/scala/ch/epfl/scala/debugadapter/testfmk/TestingResolver.scala b/modules/tests/src/main/scala/ch/epfl/scala/debugadapter/testfmk/TestingResolver.scala index 59f643231..efbaf4cd9 100644 --- a/modules/tests/src/main/scala/ch/epfl/scala/debugadapter/testfmk/TestingResolver.scala +++ b/modules/tests/src/main/scala/ch/epfl/scala/debugadapter/testfmk/TestingResolver.scala @@ -110,11 +110,16 @@ object TestingResolver extends DebugToolsResolver { } private def fetchScala3(scalaVersion: ScalaVersion): Scala3Instance = { - val expressionCompilerArtifact = - s"${BuildInfo.expressionCompilerName}_${scalaVersion.value}" + val builtInExpressionCompiler = scalaVersion.isScala3 && scalaVersion.minor >= 7 + val (expressionCompilerOrg, expressionCompilerArtifact, expressionCompilerVersion) = + if (builtInExpressionCompiler) { + ("org.scala-lang", s"scala3-compiler_3", scalaVersion.value) + } else { + (BuildInfo.organization, s"${BuildInfo.expressionCompilerName}_${scalaVersion.value}", BuildInfo.version) + } val expressionCompilerDep = Dependency( - coursier.Module(Organization(BuildInfo.organization), ModuleName(expressionCompilerArtifact)), - BuildInfo.version + coursier.Module(Organization(expressionCompilerOrg), ModuleName(expressionCompilerArtifact)), + expressionCompilerVersion ) val decoderDep = Dependency( @@ -131,8 +136,11 @@ object TestingResolver extends DebugToolsResolver { val decoderJars = fetch(decoderDep, tastyDep) val libraryJars = jars.filter(jar => jar.name.startsWith("scala-library") || jar.name.startsWith("scala3-library_3")) - val expressionCompilerJar = jars.find(jar => jar.name.startsWith(expressionCompilerArtifact)).get - val compilerJars = jars.filter(jar => !libraryJars.contains(jar) && jar != expressionCompilerJar) + val expressionCompilerJar = + if (builtInExpressionCompiler) None + else Some(jars.find(jar => jar.name.startsWith(expressionCompilerArtifact)).get) + val compilerJars = + jars.filter(jar => !libraryJars.contains(jar) && !expressionCompilerJar.exists(_ == jar)) new Scala3Instance(libraryJars, compilerJars, expressionCompilerJar, decoderJars) } diff --git a/modules/tests/src/test/scala/ch/epfl/scala/debugadapter/ScalaEvaluationTests.scala b/modules/tests/src/test/scala/ch/epfl/scala/debugadapter/ScalaEvaluationTests.scala index 5d05b3bd7..948608742 100644 --- a/modules/tests/src/test/scala/ch/epfl/scala/debugadapter/ScalaEvaluationTests.scala +++ b/modules/tests/src/test/scala/ch/epfl/scala/debugadapter/ScalaEvaluationTests.scala @@ -6,6 +6,7 @@ class Scala212EvaluationTests extends ScalaEvaluationTests(ScalaVersion.`2.12`) class Scala213EvaluationTests extends ScalaEvaluationTests(ScalaVersion.`2.13`) class Scala31PlusEvaluationTests extends ScalaEvaluationTests(ScalaVersion.`3.1+`) class Scala34PlusEvaluationTests extends ScalaEvaluationTests(ScalaVersion.`3.4+`) +class Scala372PlusEvaluationTests extends ScalaEvaluationTests(ScalaVersion.`3.7.2+`) abstract class ScalaEvaluationTests(scalaVersion: ScalaVersion) extends DebugTestSuite { protected override def defaultConfig: DebugConfig = diff --git a/modules/tests/src/test/scala/ch/epfl/scala/debugadapter/internal/RuntimeEvaluatorTests.scala b/modules/tests/src/test/scala/ch/epfl/scala/debugadapter/internal/RuntimeEvaluatorTests.scala index fde7f46ee..e213eb161 100644 --- a/modules/tests/src/test/scala/ch/epfl/scala/debugadapter/internal/RuntimeEvaluatorTests.scala +++ b/modules/tests/src/test/scala/ch/epfl/scala/debugadapter/internal/RuntimeEvaluatorTests.scala @@ -8,6 +8,7 @@ class Scala212RuntimeEvaluatorTests extends ScalaRuntimeEvaluatorTests(ScalaVers class Scala213RuntimeEvaluatorTests extends ScalaRuntimeEvaluatorTests(ScalaVersion.`2.13`) class Scala31PlusRuntimeEvaluatorTests extends ScalaRuntimeEvaluatorTests(ScalaVersion.`3.1+`) class Scala34PlusRuntimeEvaluatorTests extends ScalaRuntimeEvaluatorTests(ScalaVersion.`3.4+`) +class Scala372PlusRuntimeEvaluatorTests extends ScalaRuntimeEvaluatorTests(ScalaVersion.`3.7.2+`) object RuntimeEvaluatorEnvironments { val byNameFunction0 = @@ -975,7 +976,9 @@ abstract class ScalaRuntimeEvaluatorTests(val scalaVersion: ScalaVersion) extend |} """.stripMargin implicit val debuggee: TestingDebuggee = TestingDebuggee.mainClass(source, "example.Main", scalaVersion) - if (scalaVersion == ScalaVersion.`3.1+` || scalaVersion == ScalaVersion.`3.4+`) + if ( + scalaVersion == ScalaVersion.`3.1+` || scalaVersion == ScalaVersion.`3.4+` || scalaVersion == ScalaVersion.`3.7.2+` + ) check(Breakpoint(7), Evaluation.success("~~.x", 43)) check( diff --git a/project/Dependencies.scala b/project/Dependencies.scala index 540464893..a9485c534 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -6,7 +6,8 @@ object Dependencies { val scala213 = scalaEnvVersion.filter(isScala213).getOrElse("2.13.16") val scala30 = scalaEnvVersion.filter(isScala30).getOrElse("3.0.2") val scala31Plus = scalaEnvVersion.filter(isScala31Plus).getOrElse("3.3.6") - val scala34Plus = scalaEnvVersion.filter(isScala34Plus).getOrElse("3.7.2") + val scala34Plus = scalaEnvVersion.filter(isScala34Plus).getOrElse("3.7.1") + val scala372Plus = scalaEnvVersion.filter(isScala372Plus).getOrElse("3.7.2") val asmVersion = "9.8" val coursierVersion = "2.1.24" @@ -15,6 +16,9 @@ object Dependencies { def isScala30(version: String): Boolean = version.startsWith("3.0") def isScala31Plus(version: String): Boolean = SemVer.matches(version) { case (3, 1 | 2 | 3, _) => true } def isScala34Plus(version: String): Boolean = SemVer.matches(version) { case (3, min, _) => min >= 4 } + def isScala372Plus(version: String): Boolean = SemVer.matches(version) { case (3, min, patch) => + min == 7 && patch >= 2 || min > 7 + } val asm = "org.ow2.asm" % "asm" % asmVersion val asmUtil = "org.ow2.asm" % "asm-util" % asmVersion