diff --git a/modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/stacktrace/Scala3Unpickler.scala b/modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/stacktrace/Scala3Unpickler.scala index 8d7b3e073..b52b00815 100644 --- a/modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/stacktrace/Scala3Unpickler.scala +++ b/modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/stacktrace/Scala3Unpickler.scala @@ -63,8 +63,8 @@ object Scala3UnpicklerBridge { warnLogger, testMode: java.lang.Boolean ) - val skipMethod = cls.getMethods.find(m => m.getName == "skipMethod").get - val formatMethod = cls.getMethods.find(m => m.getName == "formatMethod").get + val skipMethod = cls.getMethod("skipMethod", classOf[Any]) + val formatMethod = cls.getMethod("formatMethod", classOf[Any]) new Scala3UnpicklerBridge(debuggee.scalaVersion, bridge, skipMethod, formatMethod, testMode) } diff --git a/modules/tests/src/main/scala/ch/epfl/scala/debugadapter/testfmk/DebugStepAssert.scala b/modules/tests/src/main/scala/ch/epfl/scala/debugadapter/testfmk/DebugStepAssert.scala index d4e47ebdf..6b6467852 100644 --- a/modules/tests/src/main/scala/ch/epfl/scala/debugadapter/testfmk/DebugStepAssert.scala +++ b/modules/tests/src/main/scala/ch/epfl/scala/debugadapter/testfmk/DebugStepAssert.scala @@ -36,13 +36,10 @@ object DebugStepAssert { )(implicit location: Location): Unit = { assertEquals(frames.head.source.path, expectedSource.toString) assertEquals(frames.head.line, expectedLine) - expectedStackTrace match { - case None => - case Some(expectedStackTrace) => - assertEquals(expectedStackTrace, frames.map(frame => frame.name)) - + expectedStackTrace.foreach { expected => + val obtained = frames.map(frame => frame.name) + assertEquals(obtained, expected) } - } def assertOnFrame(expectedName: String)(frames: List[StackFrame])(implicit loc: Location): Unit = diff --git a/modules/tests/src/main/scala/ch/epfl/scala/debugadapter/testfmk/FakeJdiLocalVariable.scala b/modules/tests/src/main/scala/ch/epfl/scala/debugadapter/testfmk/FakeJdiLocalVariable.scala deleted file mode 100644 index 56a05ef90..000000000 --- a/modules/tests/src/main/scala/ch/epfl/scala/debugadapter/testfmk/FakeJdiLocalVariable.scala +++ /dev/null @@ -1,23 +0,0 @@ -package ch.epfl.scala.debugadapter.testfmk - -import com.sun.jdi.* - -final case class FakeJdiLocalVariable( - override val name: String, - override val `type`: Type -) extends LocalVariable { - - override def virtualMachine: VirtualMachine = ??? - - override def compareTo(o: LocalVariable): Int = ??? - - override def typeName: String = ??? - - override def signature: String = ??? - - override def genericSignature: String = ??? - - override def isVisible(frame: StackFrame): Boolean = ??? - - override def isArgument: Boolean = ??? -} diff --git a/modules/tests/src/main/scala/ch/epfl/scala/debugadapter/testfmk/FakeJdiMethod.scala b/modules/tests/src/main/scala/ch/epfl/scala/debugadapter/testfmk/FakeJdiMethod.scala deleted file mode 100644 index fd1da4dd7..000000000 --- a/modules/tests/src/main/scala/ch/epfl/scala/debugadapter/testfmk/FakeJdiMethod.scala +++ /dev/null @@ -1,101 +0,0 @@ -package ch.epfl.scala.debugadapter.testfmk - -import com.sun.jdi.* - -import java.{util => ju} -import scala.jdk.CollectionConverters.* - -final case class FakeJdiMethod( - override val declaringType: ReferenceType, - override val name: String, - override val arguments: ju.List[LocalVariable], - override val returnType: Type -) extends Method { - - override def signature(): String = ??? - - override def virtualMachine(): VirtualMachine = ??? - - override def modifiers(): Int = ??? - - override def isPrivate(): Boolean = ??? - - override def isPackagePrivate(): Boolean = ??? - - override def isProtected(): Boolean = ??? - - override def isPublic(): Boolean = ??? - - override def genericSignature(): String = ??? - - override def isStatic(): Boolean = ??? - - override def isFinal(): Boolean = ??? - - override def isSynthetic(): Boolean = ??? - - override def compareTo(o: Method): Int = ??? - - override def returnTypeName(): String = returnType.name - - override def argumentTypeNames(): ju.List[String] = ??? - - override def argumentTypes(): ju.List[Type] = arguments.asScala.map(_.`type`).asJava - - override def isAbstract(): Boolean = ??? - - override def isSynchronized(): Boolean = ??? - - override def isNative(): Boolean = ??? - - override def isVarArgs(): Boolean = ??? - - override def isBridge(): Boolean = ??? - - override def isConstructor(): Boolean = ??? - - override def isStaticInitializer(): Boolean = ??? - - override def isObsolete(): Boolean = ??? - - override def allLineLocations(): ju.List[Location] = ??? - - override def allLineLocations(stratum: String, sourceName: String): ju.List[Location] = ??? - - override def locationsOfLine(lineNumber: Int): ju.List[Location] = ??? - - override def locationsOfLine(stratum: String, sourceName: String, lineNumber: Int): ju.List[Location] = ??? - - override def locationOfCodeIndex(codeIndex: Long): Location = ??? - - override def variables(): ju.List[LocalVariable] = ??? - - override def variablesByName(name: String): ju.List[LocalVariable] = ??? - - override def bytecodes(): Array[Byte] = ??? - - override def location(): Location = ??? -} - -object FakeJdiMethod { - - def apply(declaringType: String, javaSig: String): FakeJdiMethod = { - val parts = javaSig.split(Array('(', ')', ',')).filter(_.nonEmpty) - - def typeAndName(p: String): (String, String) = { - val parts = p.split(' ').filter(_.nonEmpty) - assert(parts.size == 2) - (parts(0), parts(1)) - } - - val (returnType, name) = typeAndName(parts(0)) - val args = parts - .drop(1) - .map { p => - val (tpe, name) = typeAndName(p) - FakeJdiLocalVariable(name, FakeJdiType(tpe)): LocalVariable - } - FakeJdiMethod(FakeJdiType(declaringType), name, args.toList.asJava, FakeJdiType(returnType)) - } - -} diff --git a/modules/tests/src/main/scala/ch/epfl/scala/debugadapter/testfmk/FakeJdiType.scala b/modules/tests/src/main/scala/ch/epfl/scala/debugadapter/testfmk/FakeJdiType.scala deleted file mode 100644 index 2fccf842c..000000000 --- a/modules/tests/src/main/scala/ch/epfl/scala/debugadapter/testfmk/FakeJdiType.scala +++ /dev/null @@ -1,100 +0,0 @@ -package ch.epfl.scala.debugadapter.testfmk - -import com.sun.jdi.* - -import java.{util => ju} - -final case class FakeJdiType( - override val name: String -) extends ReferenceType { - - override def virtualMachine(): VirtualMachine = ??? - - override def signature(): String = ??? - - override def compareTo(o: ReferenceType): Int = ??? - - override def modifiers(): Int = ??? - - override def isPrivate(): Boolean = ??? - - override def isPackagePrivate(): Boolean = ??? - - override def isProtected(): Boolean = ??? - - override def isPublic(): Boolean = ??? - - override def genericSignature(): String = ??? - - override def classLoader(): ClassLoaderReference = ??? - - override def sourceName(): String = ??? - - override def sourceNames(stratum: String): ju.List[String] = ??? - - override def sourcePaths(stratum: String): ju.List[String] = ??? - - override def sourceDebugExtension(): String = ??? - - override def isStatic(): Boolean = ??? - - override def isAbstract(): Boolean = ??? - - override def isFinal(): Boolean = ??? - - override def isPrepared(): Boolean = ??? - - override def isVerified(): Boolean = ??? - - override def isInitialized(): Boolean = ??? - - override def failedToInitialize(): Boolean = ??? - - override def fields(): ju.List[Field] = ??? - - override def visibleFields(): ju.List[Field] = ??? - - override def allFields(): ju.List[Field] = ??? - - override def fieldByName(fieldName: String): Field = ??? - - override def methods(): ju.List[Method] = ??? - - override def visibleMethods(): ju.List[Method] = ??? - - override def allMethods(): ju.List[Method] = ??? - - override def methodsByName(name: String): ju.List[Method] = ??? - - override def methodsByName(name: String, signature: String): ju.List[Method] = ??? - - override def nestedTypes(): ju.List[ReferenceType] = ??? - - override def getValue(field: Field): Value = ??? - - override def getValues(fields: ju.List[_ <: Field]): ju.Map[Field, Value] = ??? - - override def classObject(): ClassObjectReference = ??? - - override def allLineLocations(): ju.List[Location] = ??? - - override def allLineLocations(stratum: String, sourceName: String): ju.List[Location] = ??? - - override def locationsOfLine(lineNumber: Int): ju.List[Location] = ??? - - override def locationsOfLine(stratum: String, sourceName: String, lineNumber: Int): ju.List[Location] = ??? - - override def availableStrata(): ju.List[String] = ??? - - override def defaultStratum(): String = ??? - - override def instances(maxInstances: Long): ju.List[ObjectReference] = ??? - - override def majorVersion(): Int = ??? - - override def minorVersion(): Int = ??? - - override def constantPoolCount(): Int = ??? - - override def constantPool(): Array[Byte] = ??? -} diff --git a/modules/tests/src/main/scala/ch/epfl/scala/debugadapter/testfmk/TestingDebuggee.scala b/modules/tests/src/main/scala/ch/epfl/scala/debugadapter/testfmk/TestingDebuggee.scala index 02f5d81bd..a52ff1bf6 100644 --- a/modules/tests/src/main/scala/ch/epfl/scala/debugadapter/testfmk/TestingDebuggee.scala +++ b/modules/tests/src/main/scala/ch/epfl/scala/debugadapter/testfmk/TestingDebuggee.scala @@ -1,24 +1,31 @@ package ch.epfl.scala.debugadapter.testfmk -import ch.epfl.scala.debugadapter.testfmk.TestingDebuggee._ - -import java.io.{BufferedReader, File, InputStream, InputStreamReader} -import java.nio.file.{Files, Path, Paths} -import scala.concurrent.{Future, Promise} -import scala.util.Properties -import scala.util.control.NonFatal -import java.net.InetSocketAddress -import ch.epfl.scala.debugadapter.ScalaVersion -import ch.epfl.scala.debugadapter.Module -import ch.epfl.scala.debugadapter.ManagedEntry +import ch.epfl.scala.debugadapter.CancelableFuture import ch.epfl.scala.debugadapter.Debuggee -import ch.epfl.scala.debugadapter.Library -import ch.epfl.scala.debugadapter.UnmanagedEntry import ch.epfl.scala.debugadapter.DebuggeeListener -import ch.epfl.scala.debugadapter.CancelableFuture import ch.epfl.scala.debugadapter.JavaRuntime -import ch.epfl.scala.debugadapter.StandaloneSourceFile +import ch.epfl.scala.debugadapter.Library +import ch.epfl.scala.debugadapter.ManagedEntry +import ch.epfl.scala.debugadapter.Module +import ch.epfl.scala.debugadapter.ScalaVersion import ch.epfl.scala.debugadapter.SourceDirectory +import ch.epfl.scala.debugadapter.StandaloneSourceFile +import ch.epfl.scala.debugadapter.UnmanagedEntry +import ch.epfl.scala.debugadapter.testfmk.TestingDebuggee._ + +import java.io.BufferedReader +import java.io.File +import java.io.InputStream +import java.io.InputStreamReader +import java.net.InetSocketAddress +import java.net.URLClassLoader +import java.nio.file.Files +import java.nio.file.Path +import java.nio.file.Paths +import scala.concurrent.Future +import scala.concurrent.Promise +import scala.util.Properties +import scala.util.control.NonFatal case class TestingDebuggee( scalaVersion: ScalaVersion, @@ -43,6 +50,9 @@ case class TestingDebuggee( val process = builder.start() new MainProcess(process, listener) } + + lazy val classLoader: ClassLoader = + new URLClassLoader(classPathEntries.map(_.absolutePath.toUri.toURL).toArray) } object TestingDebuggee { 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 3e1366f8e..d3e05bf58 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 @@ -947,27 +947,27 @@ abstract class ScalaEvaluationTests(scalaVersion: ScalaVersion) extends DebugTes |object Main { | def main(args: Array[String]): Unit = { | val x = "x1" - | def m(): String = { + | def m1(): String = { | println(x) // captures x = "x1" | val y = { | val x = "x2" | val z = { | val x = "x3" - | def m(): String = { + | def m2(): String = { | x // captures x = "x3" | } - | m() + | m2() | } | z | } | y | } - | println(m()) + | println(m1()) | } |} |""".stripMargin implicit val debuggee: TestingDebuggee = TestingDebuggee.mainClass(source, "example.Main", scalaVersion) - check(Breakpoint(15), Evaluation.success("x", "x3"), Evaluation.success("m()", "x3")) + check(Breakpoint(15), Evaluation.success("x", "x3"), Evaluation.success("m2()", "x3")) } test("read and write mutable variables whose type is a value class") { diff --git a/modules/tests/src/test/scala/ch/epfl/scala/debugadapter/ScalaStackTraceTests.scala b/modules/tests/src/test/scala/ch/epfl/scala/debugadapter/ScalaStackTraceTests.scala index 0da8d2bb2..cd7061d91 100644 --- a/modules/tests/src/test/scala/ch/epfl/scala/debugadapter/ScalaStackTraceTests.scala +++ b/modules/tests/src/test/scala/ch/epfl/scala/debugadapter/ScalaStackTraceTests.scala @@ -121,22 +121,17 @@ class ScalaStackTraceTests extends DebugTestSuite { ) } - test("lazy val") { + test("local method and lazy val") { val source = """|package example |object Main { | def main(args: Array[String]): Unit = { - | - | def m(t: Int) = { - | lazy val m1 : Int = { - | def m(t: Int): Int = { - | t + 1 - | } - | m(2) - | } - | m1 + | val x = 1 + | lazy val y : Int = { + | x + 1 | } - | m(4) + | def m(z: Int) = x + y + z + | m(1) | } |} |""".stripMargin @@ -144,11 +139,11 @@ class ScalaStackTraceTests extends DebugTestSuite { check( Breakpoint( - 8, + 6, List( - "Main.main.m.m1.m(t: Int): Int", - "Main.main.m.m1: Int", - "Main.main.m(t: Int): Int", + "Main.main.y: Int", // initializer + "Main.main.y: Int", // getter + "Main.main.m(z: Int): Int", "Main.main(args: Array[String]): Unit" ) ) 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 f55ab588c..1424846ef 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 @@ -1030,22 +1030,22 @@ class Scala31RuntimeEvaluatorTests extends RuntimeEvaluatorTests(ScalaVersion.`3 |object Main { | def main(args: Array[String]): Unit = { | val x = "x1" - | def m(): String = { + | def m1(): String = { | println(x) // captures x = "x1" | val y = { | val x = "x2" | val z = { | val x = "x3" - | def m(): String = { + | def m2(): String = { | x // captures x = "x3" | } - | m() + | m2() | } | z | } | y | } - | println(m()) + | println(m1()) | } |} |""".stripMargin diff --git a/modules/unpickler/src/main/scala/ch/epfl/scala/debugadapter/internal/jdi/ReferenceType.scala b/modules/unpickler/src/main/scala/ch/epfl/scala/debugadapter/internal/binary/ClassType.scala similarity index 51% rename from modules/unpickler/src/main/scala/ch/epfl/scala/debugadapter/internal/jdi/ReferenceType.scala rename to modules/unpickler/src/main/scala/ch/epfl/scala/debugadapter/internal/binary/ClassType.scala index 0a68c0542..66d2d2afd 100644 --- a/modules/unpickler/src/main/scala/ch/epfl/scala/debugadapter/internal/jdi/ReferenceType.scala +++ b/modules/unpickler/src/main/scala/ch/epfl/scala/debugadapter/internal/binary/ClassType.scala @@ -1,5 +1,7 @@ -package ch.epfl.scala.debugadapter.internal.jdi +package ch.epfl.scala.debugadapter.internal.binary + +trait ClassType extends Type: + def name: String -class ReferenceType(obj: Any) extends Type(obj, "com.sun.jdi.ReferenceType"): def isObject = isPackageObject || name.endsWith("$") def isPackageObject = name.endsWith(".package") || name.endsWith("$package") diff --git a/modules/unpickler/src/main/scala/ch/epfl/scala/debugadapter/internal/binary/Method.scala b/modules/unpickler/src/main/scala/ch/epfl/scala/debugadapter/internal/binary/Method.scala new file mode 100644 index 000000000..22d8e08d4 --- /dev/null +++ b/modules/unpickler/src/main/scala/ch/epfl/scala/debugadapter/internal/binary/Method.scala @@ -0,0 +1,21 @@ +package ch.epfl.scala.debugadapter.internal.binary + +trait Method: + def name: String + def declaringClass: ClassType + def allParameters: Seq[Parameter] + // return None if the class of the return type is not yet loaded + def returnType: Option[Type] + def returnTypeName: String + + def isExtensionMethod: Boolean = name.endsWith("$extension") + def isTraitInitializer: Boolean = name == "$init$" + def isClassInitializer: Boolean = name == "" + def isLocalMethod: Boolean = name.matches(".*\\$\\d+") + def declaredParams: Seq[Parameter] = + if isExtensionMethod && allParameters.headOption.exists(_.isThis) then allParameters.tail + else if isClassInitializer && allParameters.headOption.exists(_.isOuter) then allParameters.tail + else if isLocalMethod then allParameters.dropWhile(_.isCapture) + else allParameters + def declaredReturnType: Option[Type] = + if isClassInitializer then Some(declaringClass) else returnType diff --git a/modules/unpickler/src/main/scala/ch/epfl/scala/debugadapter/internal/binary/Parameter.scala b/modules/unpickler/src/main/scala/ch/epfl/scala/debugadapter/internal/binary/Parameter.scala new file mode 100644 index 000000000..e11b47eb3 --- /dev/null +++ b/modules/unpickler/src/main/scala/ch/epfl/scala/debugadapter/internal/binary/Parameter.scala @@ -0,0 +1,9 @@ +package ch.epfl.scala.debugadapter.internal.binary + +trait Parameter: + def name: String + def `type`: Type + + def isThis: Boolean = name == "$this" + def isOuter: Boolean = name == "$outer" + def isCapture: Boolean = name.matches(".*\\$\\d+") diff --git a/modules/unpickler/src/main/scala/ch/epfl/scala/debugadapter/internal/binary/Type.scala b/modules/unpickler/src/main/scala/ch/epfl/scala/debugadapter/internal/binary/Type.scala new file mode 100644 index 000000000..954e633e8 --- /dev/null +++ b/modules/unpickler/src/main/scala/ch/epfl/scala/debugadapter/internal/binary/Type.scala @@ -0,0 +1,4 @@ +package ch.epfl.scala.debugadapter.internal.binary + +trait Type: + def name: String diff --git a/modules/unpickler/src/main/scala/ch/epfl/scala/debugadapter/internal/javareflect/JavaReflectClass.scala b/modules/unpickler/src/main/scala/ch/epfl/scala/debugadapter/internal/javareflect/JavaReflectClass.scala new file mode 100644 index 000000000..6a60def4b --- /dev/null +++ b/modules/unpickler/src/main/scala/ch/epfl/scala/debugadapter/internal/javareflect/JavaReflectClass.scala @@ -0,0 +1,9 @@ +package ch.epfl.scala.debugadapter.internal.javareflect + +import ch.epfl.scala.debugadapter.internal.binary +import scala.util.matching.Regex + +class JavaReflectClass(cls: Class[?]) extends binary.ClassType: + override def name: String = cls.getTypeName + + override def toString: String = cls.toString diff --git a/modules/unpickler/src/main/scala/ch/epfl/scala/debugadapter/internal/javareflect/JavaReflectConstructor.scala b/modules/unpickler/src/main/scala/ch/epfl/scala/debugadapter/internal/javareflect/JavaReflectConstructor.scala new file mode 100644 index 000000000..3a56dc671 --- /dev/null +++ b/modules/unpickler/src/main/scala/ch/epfl/scala/debugadapter/internal/javareflect/JavaReflectConstructor.scala @@ -0,0 +1,23 @@ +package ch.epfl.scala.debugadapter.internal.javareflect + +import ch.epfl.scala.debugadapter.internal.binary + +import java.lang.reflect.Constructor +import java.lang.reflect.Method + +class JavaReflectConstructor(constructor: Constructor[?]) extends binary.Method: + + override def returnType: Option[binary.Type] = + Some(JavaReflectClass(classOf[Unit])) + + override def returnTypeName: String = "void" + + override def declaringClass: binary.ClassType = + JavaReflectClass(constructor.getDeclaringClass) + + override def allParameters: Seq[binary.Parameter] = + constructor.getParameters.map(JavaReflectParameter.apply(_)) + + override def name: String = "" + + override def toString: String = constructor.toString diff --git a/modules/unpickler/src/main/scala/ch/epfl/scala/debugadapter/internal/javareflect/JavaReflectMethod.scala b/modules/unpickler/src/main/scala/ch/epfl/scala/debugadapter/internal/javareflect/JavaReflectMethod.scala new file mode 100644 index 000000000..6727360e4 --- /dev/null +++ b/modules/unpickler/src/main/scala/ch/epfl/scala/debugadapter/internal/javareflect/JavaReflectMethod.scala @@ -0,0 +1,22 @@ +package ch.epfl.scala.debugadapter.internal.javareflect + +import ch.epfl.scala.debugadapter.internal.binary + +import java.lang.reflect.Method + +class JavaReflectMethod(method: Method) extends binary.Method: + + override def returnType: Option[binary.Type] = + Option(method.getReturnType).map(JavaReflectClass.apply(_)) + + override def returnTypeName: String = method.getReturnType.getName + + override def declaringClass: binary.ClassType = + JavaReflectClass(method.getDeclaringClass) + + override def allParameters: Seq[binary.Parameter] = + method.getParameters.map(JavaReflectParameter.apply(_)) + + override def name: String = method.getName + + override def toString: String = method.toString diff --git a/modules/unpickler/src/main/scala/ch/epfl/scala/debugadapter/internal/javareflect/JavaReflectParameter.scala b/modules/unpickler/src/main/scala/ch/epfl/scala/debugadapter/internal/javareflect/JavaReflectParameter.scala new file mode 100644 index 000000000..56b0d9478 --- /dev/null +++ b/modules/unpickler/src/main/scala/ch/epfl/scala/debugadapter/internal/javareflect/JavaReflectParameter.scala @@ -0,0 +1,13 @@ +package ch.epfl.scala.debugadapter.internal.javareflect + +import ch.epfl.scala.debugadapter.internal.binary + +import java.lang.reflect.Parameter + +class JavaReflectParameter(parameter: Parameter) extends binary.Parameter: + + override def name: String = parameter.getName + + override def `type`: binary.Type = JavaReflectClass(parameter.getType) + + override def toString: String = parameter.toString diff --git a/modules/unpickler/src/main/scala/ch/epfl/scala/debugadapter/internal/jdi/JavaReflection.scala b/modules/unpickler/src/main/scala/ch/epfl/scala/debugadapter/internal/jdi/JavaReflection.scala index 18cc3d1d9..0dbac8353 100644 --- a/modules/unpickler/src/main/scala/ch/epfl/scala/debugadapter/internal/jdi/JavaReflection.scala +++ b/modules/unpickler/src/main/scala/ch/epfl/scala/debugadapter/internal/jdi/JavaReflection.scala @@ -8,3 +8,5 @@ private[jdi] class JavaReflection(obj: Any, className: String): protected def invokeMethod[T](name: String): T = val method = cls.getMethod(name) method.invoke(obj).asInstanceOf[T] + + override def toString: String = obj.toString diff --git a/modules/unpickler/src/main/scala/ch/epfl/scala/debugadapter/internal/jdi/JdiLocalVariable.scala b/modules/unpickler/src/main/scala/ch/epfl/scala/debugadapter/internal/jdi/JdiLocalVariable.scala new file mode 100644 index 000000000..77b04465d --- /dev/null +++ b/modules/unpickler/src/main/scala/ch/epfl/scala/debugadapter/internal/jdi/JdiLocalVariable.scala @@ -0,0 +1,7 @@ +package ch.epfl.scala.debugadapter.internal.jdi + +import ch.epfl.scala.debugadapter.internal.binary.* + +class JdiLocalVariable(obj: Any) extends JavaReflection(obj, "com.sun.jdi.LocalVariable") with Parameter: + override def name: String = invokeMethod("name") + override def `type`: Type = JdiType(invokeMethod("type")) diff --git a/modules/unpickler/src/main/scala/ch/epfl/scala/debugadapter/internal/jdi/JdiMethod.scala b/modules/unpickler/src/main/scala/ch/epfl/scala/debugadapter/internal/jdi/JdiMethod.scala new file mode 100644 index 000000000..8108f49fe --- /dev/null +++ b/modules/unpickler/src/main/scala/ch/epfl/scala/debugadapter/internal/jdi/JdiMethod.scala @@ -0,0 +1,23 @@ +package ch.epfl.scala.debugadapter.internal.jdi + +import ch.epfl.scala.debugadapter.internal.binary.* + +import java.lang.reflect.InvocationTargetException +import scala.jdk.CollectionConverters.* + +class JdiMethod(val obj: Any) extends JavaReflection(obj, "com.sun.jdi.Method") with Method: + override def name: String = invokeMethod("name") + + override def declaringClass: ClassType = + JdiReferenceType(invokeMethod("declaringType")) + + override def allParameters: Seq[Parameter] = + invokeMethod[java.util.List[Object]]("arguments").asScala.toSeq.map(JdiLocalVariable.apply(_)) + + override def returnType: Option[Type] = + try Some(JdiType(invokeMethod("returnType"))) + catch + case e: InvocationTargetException if e.getCause.getClass.getName == "com.sun.jdi.ClassNotLoadedException" => + None + + override def returnTypeName: String = invokeMethod("returnTypeName") diff --git a/modules/unpickler/src/main/scala/ch/epfl/scala/debugadapter/internal/jdi/JdiReferenceType.scala b/modules/unpickler/src/main/scala/ch/epfl/scala/debugadapter/internal/jdi/JdiReferenceType.scala new file mode 100644 index 000000000..7579ac412 --- /dev/null +++ b/modules/unpickler/src/main/scala/ch/epfl/scala/debugadapter/internal/jdi/JdiReferenceType.scala @@ -0,0 +1,5 @@ +package ch.epfl.scala.debugadapter.internal.jdi + +import ch.epfl.scala.debugadapter.internal.binary.* + +class JdiReferenceType(obj: Any) extends JdiType(obj, "com.sun.jdi.ReferenceType") with ClassType diff --git a/modules/unpickler/src/main/scala/ch/epfl/scala/debugadapter/internal/jdi/JdiType.scala b/modules/unpickler/src/main/scala/ch/epfl/scala/debugadapter/internal/jdi/JdiType.scala new file mode 100644 index 000000000..7a0237246 --- /dev/null +++ b/modules/unpickler/src/main/scala/ch/epfl/scala/debugadapter/internal/jdi/JdiType.scala @@ -0,0 +1,6 @@ +package ch.epfl.scala.debugadapter.internal.jdi + +import ch.epfl.scala.debugadapter.internal.binary.* + +class JdiType(obj: Any, className: String = "com.sun.jdi.Type") extends JavaReflection(obj, className) with Type: + override def name: String = invokeMethod("name") diff --git a/modules/unpickler/src/main/scala/ch/epfl/scala/debugadapter/internal/jdi/LocalVariable.scala b/modules/unpickler/src/main/scala/ch/epfl/scala/debugadapter/internal/jdi/LocalVariable.scala deleted file mode 100644 index 5cfe88969..000000000 --- a/modules/unpickler/src/main/scala/ch/epfl/scala/debugadapter/internal/jdi/LocalVariable.scala +++ /dev/null @@ -1,6 +0,0 @@ -package ch.epfl.scala.debugadapter.internal.jdi - -class LocalVariable(obj: Any) extends JavaReflection(obj, "com.sun.jdi.LocalVariable"): - def name: String = invokeMethod("name") - def `type`: Type = - Type(invokeMethod("type")) diff --git a/modules/unpickler/src/main/scala/ch/epfl/scala/debugadapter/internal/jdi/Method.scala b/modules/unpickler/src/main/scala/ch/epfl/scala/debugadapter/internal/jdi/Method.scala deleted file mode 100644 index 4b69c12de..000000000 --- a/modules/unpickler/src/main/scala/ch/epfl/scala/debugadapter/internal/jdi/Method.scala +++ /dev/null @@ -1,32 +0,0 @@ -package ch.epfl.scala.debugadapter.internal.jdi - -import scala.jdk.CollectionConverters.* -import java.lang.reflect.InvocationTargetException - -class Method(val obj: Any) extends JavaReflection(obj, "com.sun.jdi.Method"): - def name: String = invokeMethod("name") - - def declaringType: ReferenceType = - ReferenceType(invokeMethod("declaringType")) - - def arguments: Seq[LocalVariable] = - invokeMethod[java.util.List[Object]]("arguments").asScala.toSeq.map(LocalVariable.apply(_)) - - def argumentTypes: Seq[Type] = - invokeMethod[java.util.List[Object]]("argumentTypes").asScala.toSeq.map(Type.apply(_)) - - def returnType: Option[Type] = - try Some(Type(invokeMethod("returnType"))) - catch - case e: InvocationTargetException if e.getCause.getClass.getName == "com.sun.jdi.ClassNotLoadedException" => - None - - def returnTypeName: String = invokeMethod("returnTypeName") - - def isExtensionMethod: Boolean = name.endsWith("$extension") - - def isTraitInitializer: Boolean = name == "$init$" - - def isClassInitializer: Boolean = name == "" - - override def toString: String = invokeMethod("toString") diff --git a/modules/unpickler/src/main/scala/ch/epfl/scala/debugadapter/internal/jdi/Type.scala b/modules/unpickler/src/main/scala/ch/epfl/scala/debugadapter/internal/jdi/Type.scala deleted file mode 100644 index 6212068a3..000000000 --- a/modules/unpickler/src/main/scala/ch/epfl/scala/debugadapter/internal/jdi/Type.scala +++ /dev/null @@ -1,4 +0,0 @@ -package ch.epfl.scala.debugadapter.internal.jdi - -class Type(obj: Any, className: String = "com.sun.jdi.Type") extends JavaReflection(obj, className): - def name: String = invokeMethod("name") diff --git a/modules/unpickler/src/main/scala/ch/epfl/scala/debugadapter/internal/stacktrace/Scala3Unpickler.scala b/modules/unpickler/src/main/scala/ch/epfl/scala/debugadapter/internal/stacktrace/Scala3Unpickler.scala index 3cde8f41e..c3f1b1b4d 100644 --- a/modules/unpickler/src/main/scala/ch/epfl/scala/debugadapter/internal/stacktrace/Scala3Unpickler.scala +++ b/modules/unpickler/src/main/scala/ch/epfl/scala/debugadapter/internal/stacktrace/Scala3Unpickler.scala @@ -1,23 +1,19 @@ package ch.epfl.scala.debugadapter.internal.stacktrace -import ch.epfl.scala.debugadapter.internal.jdi +import ch.epfl.scala.debugadapter.internal.binary +import ch.epfl.scala.debugadapter.internal.jdi.JdiMethod import tastyquery.Contexts import tastyquery.Contexts.Context import tastyquery.Definitions import tastyquery.Flags import tastyquery.Names.* import tastyquery.Signatures.* -import tastyquery.Signatures.* import tastyquery.Symbols.* -import tastyquery.Trees.DefDef -import tastyquery.Trees.Tree -import tastyquery.Trees.ValDef -import tastyquery.Types.* +import tastyquery.Trees.* import tastyquery.Types.* import tastyquery.jdk.ClasspathLoaders import tastyquery.jdk.ClasspathLoaders.FileKind -import java.lang.reflect.Method import java.nio.file.Path import java.util.Optional import java.util.function.Consumer @@ -47,38 +43,54 @@ class Scala3Unpickler( else exception.getMessage def skipMethod(obj: Any): Boolean = - findSymbol(obj).forall(skip) + skipMethod(JdiMethod(obj): binary.Method) + + def skipMethod(method: binary.Method): Boolean = + findSymbol(method).forall(skip) def formatMethod(obj: Any): Optional[String] = - findSymbol(obj) match - case None => Optional.empty - case Some(symbol) => - val sep = if !symbol.declaredType.isInstanceOf[MethodicType] then ": " else "" - Optional.of(s"${formatSymbol(symbol)}$sep${formatType(symbol.declaredType)}") + formatMethod(JdiMethod(obj)).toJava + + def formatMethod(method: binary.Method): Option[String] = + findSymbol(method).map { symbol => + val sep = if !symbol.declaredType.isInstanceOf[MethodicType] then ": " else "" + s"${formatSymbol(symbol)}$sep${formatType(symbol.declaredType)}" + } private[stacktrace] def findSymbol(obj: Any): Option[TermSymbol] = - val method = jdi.Method(obj) + findSymbol(JdiMethod(obj)) + + private[stacktrace] def findSymbol(method: binary.Method): Option[TermSymbol] = findDeclaringClass(method) match - case None => throw new Exception(s"Cannot find Scala symbol of ${method.declaringType.name}") + case None => throw new Exception(s"Cannot find Scala symbol of ${method.declaringClass.name}") case Some(declaringClass) => matchesLocalMethodOrLazyVal(method) match - case Some((name, index)) => - Some(findLocalMethodOrLazyVal(declaringClass, name, index)) + case Some((name, _)) => + localMethodsAndLazyVals(declaringClass, name) + .filter(matchSignature(method, _)) + .singleOrThrow(method) case None => - val matchingSymbols = declaringClass.declarations + declaringClass.declarations .collect { case sym: TermSymbol if sym.isTerm => sym } .filter(matchSymbol(method, _)) - if matchingSymbols.size > 1 then - val message = s"Found ${matchingSymbols.size} matching symbols for $method:" + - matchingSymbols.mkString("\n") - throw new Exception(message) - else matchingSymbols.headOption + .singleOrThrow(method) + + extension (symbols: Seq[TermSymbol]) + def singleOrThrow(method: binary.Method): Option[TermSymbol] = + if symbols.size > 1 then + val message = s"Found ${symbols.size} matching symbols for $method:" + + symbols.mkString("\n") + throw new Exception(message) + else symbols.headOption + + def localMethodsAndLazyVals(declaringClass: ClassSymbol, name: String): Seq[TermSymbol] = + def matchName(symbol: Symbol): Boolean = symbol.name.toString == name + def isLocal(symbol: Symbol): Boolean = symbol.owner.isTerm - def findLocalMethodOrLazyVal(declaringClass: ClassSymbol, name: String, index: Int): TermSymbol = def findLocalSymbol(tree: Tree): Seq[TermSymbol] = tree.walkTree { - case DefDef(_, _, _, _, symbol) if matchTargetName(name, symbol) => Seq(symbol) - case ValDef(_, _, _, symbol) if matchTargetName(name, symbol) => Seq(symbol) + case DefDef(_, _, _, _, symbol) if matchName(symbol) && isLocal(symbol) => Seq(symbol) + case ValDef(_, _, _, symbol) if matchName(symbol) && isLocal(symbol) => Seq(symbol) case _ => Seq.empty }(_ ++ _, Seq.empty) @@ -87,18 +99,12 @@ class Scala3Unpickler( Seq(declaringClass, companionClass) case _ => Seq(declaringClass) - val matchingSymbols = - for - declaringSym <- declaringClasses - decl <- declaringSym.declarations - tree <- decl.tree.toSeq - localSym <- findLocalSymbol(tree) - yield localSym - if matchingSymbols.size < index - then - // TODO we cannot find the local symbol of Scala 2.13 classes, it should not throw - throw new Exception(s"Cannot find local symbol $name$$$index in ${declaringClass.name}") - matchingSymbols(index - 1) + for + declaringSym <- declaringClasses + decl <- declaringSym.declarations + tree <- decl.tree.toSeq + localSym <- findLocalSymbol(tree) + yield localSym def formatType(t: TermType | TypeOrWildcard): String = t match @@ -240,8 +246,8 @@ class Scala3Unpickler( case p: PackageRef => p.fullyQualifiedName.toString == "scala" case _ => false - private def findDeclaringClass(method: jdi.Method): Option[ClassSymbol] = - val javaParts = method.declaringType.name.split('.') + private def findDeclaringClass(method: binary.Method): Option[ClassSymbol] = + val javaParts = method.declaringClass.name.split('.') val packageNames = javaParts.dropRight(1).toList.map(SimpleName.apply) val packageSym = if packageNames.nonEmpty @@ -252,7 +258,7 @@ class Scala3Unpickler( val obj = clsSymbols.filter(_.isModuleClass) val cls = clsSymbols.filter(!_.isModuleClass) assert(obj.size <= 1 && cls.size <= 1) - if method.declaringType.isObject && !method.isExtensionMethod then obj.headOption else cls.headOption + if method.declaringClass.isObject && !method.isExtensionMethod then obj.headOption else cls.headOption private def findSymbolsRecursively(owner: DeclaringSymbol, encodedName: String): Seq[ClassSymbol] = owner.declarations @@ -267,20 +273,21 @@ class Scala3Unpickler( case _ => None } - private def matchSymbol(method: jdi.Method, symbol: TermSymbol): Boolean = + private def matchSymbol(method: binary.Method, symbol: TermSymbol): Boolean = matchTargetName(method, symbol) && (method.isTraitInitializer || matchSignature(method, symbol)) - private def matchesLocalMethodOrLazyVal(method: jdi.Method): Option[(String, Int)] = - val javaPrefix = method.declaringType.name.replace('.', '$') + "$$" + private def matchesLocalMethodOrLazyVal(method: binary.Method): Option[(String, Int)] = + val javaPrefix = method.declaringClass.name.replace('.', '$') + "$$" val expectedName = method.name.stripPrefix(javaPrefix) - val pattern = """^(.+)[$](\d+)$""".r + val localMethod = "(.+)\\$(\\d+)".r + val lazyInit = "(.+)\\$lzyINIT\\d+\\$(\\d+)".r expectedName match - case pattern(stringPart, numberPart) if (!stringPart.endsWith("$lzyINIT1") && !stringPart.endsWith("$default")) => - Some((stringPart, numberPart.toInt)) + case lazyInit(name, index) => Some((name, index.toInt)) + case localMethod(name, index) if !name.endsWith("$default") => Some((name, index.toInt)) case _ => None - private def matchTargetName(method: jdi.Method, symbol: TermSymbol): Boolean = - val javaPrefix = method.declaringType.name.replace('.', '$') + "$$" + private def matchTargetName(method: binary.Method, symbol: TermSymbol): Boolean = + val javaPrefix = method.declaringClass.name.replace('.', '$') + "$$" // if an inner accesses a private method, the backend makes the method public // and prefixes its name with the full class name. // Example: method foo in class example.Inner becomes example$Inner$$foo @@ -293,32 +300,19 @@ class Scala3Unpickler( if method.isExtensionMethod then encodedScalaName == expectedName.stripSuffix("$extension") else encodedScalaName == expectedName - private def matchTargetName(expectedName: String, symbol: TermSymbol): Boolean = - val symbolName = symbol.targetName.toString - expectedName == NameTransformer.encode(symbolName) - - private def matchSignature(method: jdi.Method, symbol: TermSymbol): Boolean = + private def matchSignature(method: binary.Method, symbol: TermSymbol): Boolean = symbol.signedName match case SignedName(_, sig, _) => - val javaArgs = method.arguments.headOption.map(_.name) match - case Some("$this") if method.isExtensionMethod => method.arguments.tail - case Some("$outer") if method.isClassInitializer => method.arguments.tail - case _ => method.arguments - matchArguments(sig.paramsSig, javaArgs) && - method.returnType.forall { returnType => - val javaRetType = - if method.isClassInitializer then method.declaringType else returnType - matchType(sig.resSig, javaRetType) - } + matchArguments(sig.paramsSig, method.declaredParams) + && method.declaredReturnType.forall(matchType(sig.resSig, _)) case _ => - method.arguments.isEmpty || (method.arguments.size == 1 && method.argumentTypes.head.name == "scala.runtime.LazyRef") - - // TODO compare symbol.declaredType + // TODO compare symbol.declaredType + method.declaredParams.isEmpty - private def matchArguments(scalaArgs: Seq[ParamSig], javaArgs: Seq[jdi.LocalVariable]): Boolean = - scalaArgs + private def matchArguments(scalaParams: Seq[ParamSig], javaParams: Seq[binary.Parameter]): Boolean = + scalaParams .collect { case termSig: ParamSig.Term => termSig } - .corresponds(javaArgs)((scalaArg, javaArg) => matchType(scalaArg.typ, javaArg.`type`)) + .corresponds(javaParams)((scalaParam, javaParam) => matchType(scalaParam.typ, javaParam.`type`)) private val javaToScala: Map[String, String] = Map( "scala.Boolean" -> "boolean", @@ -337,7 +331,7 @@ class Scala3Unpickler( private def matchType( scalaType: FullyQualifiedName, - javaType: jdi.Type + javaType: binary.Type ): Boolean = def rec(scalaType: String, javaType: String): Boolean = scalaType match diff --git a/modules/unpickler/src/test/scala/ch/epfl/scala/debugadapter/internal/stacktrace/Scala3UnpicklerTests.scala b/modules/unpickler/src/test/scala/ch/epfl/scala/debugadapter/internal/stacktrace/Scala3UnpicklerTests.scala index 52cb5a532..d4b86b1be 100644 --- a/modules/unpickler/src/test/scala/ch/epfl/scala/debugadapter/internal/stacktrace/Scala3UnpicklerTests.scala +++ b/modules/unpickler/src/test/scala/ch/epfl/scala/debugadapter/internal/stacktrace/Scala3UnpicklerTests.scala @@ -4,7 +4,9 @@ import ch.epfl.scala.debugadapter.Debuggee import ch.epfl.scala.debugadapter.Java8 import ch.epfl.scala.debugadapter.Java9OrAbove import ch.epfl.scala.debugadapter.ScalaVersion -import ch.epfl.scala.debugadapter.testfmk.FakeJdiMethod +import ch.epfl.scala.debugadapter.internal.binary +import ch.epfl.scala.debugadapter.internal.javareflect.JavaReflectConstructor +import ch.epfl.scala.debugadapter.internal.javareflect.JavaReflectMethod import ch.epfl.scala.debugadapter.testfmk.TestingDebuggee import com.sun.jdi.* import munit.FunSuite @@ -12,14 +14,18 @@ import tastyquery.Contexts.Context import tastyquery.Flags import tastyquery.Names.* import tastyquery.Symbols.TermSymbol -import scala.jdk.OptionConverters.* +import java.lang.reflect.Parameter import java.util as ju +import scala.jdk.OptionConverters.* +import java.net.URLClassLoader class Scala30UnpicklerTests extends Scala3UnpicklerTests(ScalaVersion.`3.0`) class Scala31PlusUnpicklerTests extends Scala3UnpicklerTests(ScalaVersion.`3.1+`) abstract class Scala3UnpicklerTests(val scalaVersion: ScalaVersion) extends FunSuite: + def isScala30 = scalaVersion.isScala30 + test("mixin-forwarders") { val source = """|package example @@ -58,21 +64,18 @@ abstract class Scala3UnpicklerTests(val scalaVersion: ScalaVersion) extends FunS |object F extends A |""".stripMargin val debuggee = TestingDebuggee.mainClass(source, "example.Main", scalaVersion) - val unpickler = getUnpickler(debuggee) - val javaSig = "java.lang.String m()" - unpickler.assertFormat("example.A", javaSig, "A.m(): String") - unpickler.assertNotFound("example.B", javaSig) - unpickler.assertNotFound("example.C", javaSig) - unpickler.assertFormat("example.D", javaSig, "D.m(): String") - unpickler.assertNotFound("example.E", javaSig) - unpickler.assertNotFound("example.F$", javaSig) - unpickler.assertFormat("example.Main$G", javaSig, "Main.G.m(): String") - unpickler.assertNotFound("example.Main$H", javaSig) - unpickler.assertFailure("example.Main$$anon$1", javaSig) + debuggee.assertFormat("example.A", javaSig, "A.m(): String") + debuggee.assertNotFound("example.B", javaSig) + debuggee.assertNotFound("example.C", javaSig) + debuggee.assertFormat("example.D", javaSig, "D.m(): String") + debuggee.assertNotFound("example.F$", javaSig) + debuggee.assertFormat("example.Main$G", javaSig, "Main.G.m(): String") + debuggee.assertNotFound("example.Main$H", javaSig) + debuggee.assertFailure("example.Main$$anon$1", javaSig) // TODO fix: we could find it by traversing the tree of `Main` - unpickler.assertFailure("example.Main$$anon$2", javaSig) + debuggee.assertFailure("example.Main$$anon$2", javaSig) } test("local classes or local objects") { @@ -95,9 +98,9 @@ abstract class Scala3UnpicklerTests(val scalaVersion: ScalaVersion) extends FunS |} |""".stripMargin val debuggee = TestingDebuggee.mainClass(source, "example.Main", scalaVersion) - val unpickler = getUnpickler(debuggee) - unpickler.assertFailure("example.Main$A$1", "void m()") - unpickler.assertFailure("example.Main$B$2$", "void m()") + debuggee.assertFailure("example.Main$A$1", "void m()") + if isScala30 then debuggee.assertFailure("example.Main$B$1$", "void m()") + else debuggee.assertFailure("example.Main$B$2$", "void m()") } test("local methods with same name") { @@ -105,27 +108,38 @@ abstract class Scala3UnpicklerTests(val scalaVersion: ScalaVersion) extends FunS """|package example | |class A { + | val x = "x" | def m1: Unit = { - | def m: Unit = { // m$1 - | println(1) - | def m(x: String): Unit = // m$2 - | println(2) - | m("hello") + | val y = "y" + | def m: Unit = { + | def m(z: String): Unit = + | println(x + y + z) + | m("z") | } | m | } | | def m2: Unit = { - | def m(x: Int): Unit = println(3) // m$3 + | def m(i: Int): Unit = println(i) | m(1) | } + | + | def m3: Unit = { + | def m(i: Int): Unit = println(i) + | m(2) + | } |} |""".stripMargin val debuggee = TestingDebuggee.mainClass(source, "example", scalaVersion) - val unpickler = getUnpickler(debuggee) - unpickler.assertFormat("example.A", "void m$1()", "A.m1.m: Unit") - unpickler.assertFormat("example.A", "void m$2(java.lang.String x)", "A.m1.m.m(x: String): Unit") - unpickler.assertFormat("example.A", "void m$3(int x)", "A.m2.m(x: Int): Unit") + debuggee.assertFormat("example.A", "void m$1(java.lang.String y$1)", "A.m1.m: Unit") + debuggee.assertFormat( + "example.A", + if isScala30 then "void m$4(java.lang.String y$2, java.lang.String z)" + else "void m$2(java.lang.String y$2, java.lang.String z)", + "A.m1.m.m(z: String): Unit" + ) + // TODO fix + debuggee.assertFailure("example.A", if isScala30 then "void m$2(int i)" else "void m$3(int i)") } test("getters and setters") { @@ -134,9 +148,7 @@ abstract class Scala3UnpicklerTests(val scalaVersion: ScalaVersion) extends FunS | |object Main { | val x1 = "x1" - | private val x2 = "x2" - | var x3 = "x3" - | private var x4 = "x4" + | var x2 = "x2" |} | |trait A { @@ -149,41 +161,31 @@ abstract class Scala3UnpicklerTests(val scalaVersion: ScalaVersion) extends FunS | protected val b2: String = "b2" |} | - |class C(val c1: String, c2: String) extends B with A { + |class C(val c1: String) extends B with A { | override val a1: String = "a1" | override val a2: String = "a2" - | private val c3: String = "c3" |} | |case class D(d1: String) | |""".stripMargin val debuggee = TestingDebuggee.mainClass(source, "example.Main", scalaVersion) - val unpickler = getUnpickler(debuggee) def getter(field: String): String = s"java.lang.String $field()" - def setter(field: String): String = s"java.lang.String $field$$eq(java.lang.String x$$1)" + def setter(field: String): String = s"void ${field}_$$eq(java.lang.String x$$1)" // When looking for a getter we find the symbol of the field - unpickler.assertFind("example.Main$", getter("x1")) - unpickler.assertFormat("example.Main$", getter("x1"), "Main.x1: String") - - unpickler.assertFind("example.Main$", getter("x2")) - unpickler.assertFind("example.Main$", getter("x3")) - unpickler.assertFind("example.Main$", getter("x4")) - unpickler.assertFind("example.A", getter("a1")) - unpickler.assertFind("example.A", getter("a2")) - unpickler.assertFind("example.B", getter("b1")) - unpickler.assertFind("example.B", getter("b2")) - unpickler.assertFind("example.C", getter("c1")) - unpickler.assertFind("example.C", getter("c2")) - unpickler.assertFind("example.C", getter("c3")) - unpickler.assertFind("example.D", getter("d1")) - - // there is no corresponding symbol in TASTy query - // should we return the field symbol? - unpickler.assertNotFound("example.Main$", setter("x3")) - unpickler.assertNotFound("example.Main$", setter("x4")) + debuggee.assertFind("example.Main$", getter("x1")) + debuggee.assertFormat("example.Main$", getter("x1"), "Main.x1: String") + debuggee.assertFind("example.Main$", getter("x2")) + debuggee.assertFind("example.A", getter("a1")) + debuggee.assertFind("example.A", getter("a2")) + debuggee.assertFind("example.B", getter("b1")) + debuggee.assertFind("example.B", getter("b2")) + debuggee.assertFind("example.C", getter("c1")) + debuggee.assertFind("example.D", getter("d1")) + + debuggee.assertFormat("example.Main$", setter("x2"), "Main.x2_=(String): Unit") } test("bridges") { @@ -199,13 +201,12 @@ abstract class Scala3UnpicklerTests(val scalaVersion: ScalaVersion) extends FunS |} |""".stripMargin val debuggee = TestingDebuggee.mainClass(source, "example.Main", scalaVersion) - val unpickler = getUnpickler(debuggee) def javaSig(returnType: String): String = s"$returnType m()" - unpickler.assertFormat("example.A", javaSig("java.lang.Object"), "A.m(): Object") - unpickler.assertNotFound("example.B", javaSig("java.lang.Object")) - unpickler.assertFormat("example.B", javaSig("java.lang.String"), "B.m(): String") + debuggee.assertFormat("example.A", javaSig("java.lang.Object"), "A.m(): Object") + debuggee.assertNotFound("example.B", javaSig("java.lang.Object")) + debuggee.assertFormat("example.B", javaSig("java.lang.String"), "B.m(): String") } test("using and implicit parameters") { @@ -218,10 +219,9 @@ abstract class Scala3UnpicklerTests(val scalaVersion: ScalaVersion) extends FunS |} |""".stripMargin val debuggee = TestingDebuggee.mainClass(source, "example.Main", scalaVersion) - val unpickler = getUnpickler(debuggee) - unpickler.assertFormat("example.Main$", "int m1(int x, int y)", "Main.m1(using x: Int, y: Int): Int") - unpickler.assertFormat("example.Main$", "int m2(int x)", "Main.m2(implicit x: Int): Int") - unpickler.assertFormat( + debuggee.assertFormat("example.Main$", "int m1(int x, int y)", "Main.m1(using x: Int, y: Int): Int") + debuggee.assertFormat("example.Main$", "int m2(int x)", "Main.m2(implicit x: Int): Int") + debuggee.assertFormat( "example.Main$", "void m3(java.lang.String x$1, int x$2)", "Main.m3(using String, Int): Unit" @@ -240,8 +240,7 @@ abstract class Scala3UnpicklerTests(val scalaVersion: ScalaVersion) extends FunS |} |""".stripMargin val debuggee = TestingDebuggee.mainClass(source, "example.Main", scalaVersion) - val unpickler = getUnpickler(debuggee) - unpickler.assertFormat("example.A$", "java.lang.String m$extension(java.lang.String $this)", "A.m(): String") + debuggee.assertFormat("example.A$", "java.lang.String m$extension(java.lang.String $this)", "A.m(): String") } test("local method inside a value class") { @@ -249,27 +248,24 @@ abstract class Scala3UnpicklerTests(val scalaVersion: ScalaVersion) extends FunS """|package example | |class A(val x: String) extends AnyVal { - | def m(): String = { - | def m1(t : String) : String = { + | def m: String = { + | def m(t : String) : String = { | t | } - | m1("") + | m("") | } |} | |object A { - | def m(): String = { - | def m1 : String = { - | "m1" - | } - | m1 + | def m: String = { + | def m : String = "m" + | m | } |} |""".stripMargin val debuggee = TestingDebuggee.mainClass(source, "example.Main", scalaVersion) - val unpickler = getUnpickler(debuggee) - unpickler.assertFormat("example.A$", "java.lang.String m1$2(java.lang.String t)", "A.m.m1(t: String): String") - unpickler.assertFormat("example.A$", "java.lang.String m1$1()", "A.m.m1: String") + debuggee.assertFormat("example.A$", "java.lang.String m$2(java.lang.String t)", "A.m.m(t: String): String") + debuggee.assertFormat("example.A$", "java.lang.String m$1()", "A.m.m: String") } test("multi parameter lists") { @@ -285,8 +281,7 @@ abstract class Scala3UnpicklerTests(val scalaVersion: ScalaVersion) extends FunS |class A |""".stripMargin val debuggee = TestingDebuggee.mainClass(source, "example.Main", scalaVersion) - val unpickler = getUnpickler(debuggee) - unpickler.assertFormat("example.Main$", "java.lang.String m(example.A a)", "Main.m()(a: A): String") + debuggee.assertFormat("example.Main$", "java.lang.String m(example.A a)", "Main.m()(a: A): String") } test("lazy initializer") { @@ -307,11 +302,10 @@ abstract class Scala3UnpicklerTests(val scalaVersion: ScalaVersion) extends FunS |""".stripMargin val debuggee = TestingDebuggee.mainClass(source, "example.Main", scalaVersion) - val unpickler = getUnpickler(debuggee) - unpickler.assertFormat("example.A$", "java.lang.String a()", "A.a: String") - unpickler.assertNotFound("example.A$", "java.lang.String b()") - unpickler.assertFormat("example.B", "java.lang.String b()", "B.b: String") + debuggee.assertFormat("example.A$", "java.lang.String a()", "A.a: String") + debuggee.assertNotFound("example.A$", "java.lang.String b()") + debuggee.assertFormat("example.B", "java.lang.String b()", "B.b: String") } test("synthetic methods of case class") { @@ -321,19 +315,18 @@ abstract class Scala3UnpicklerTests(val scalaVersion: ScalaVersion) extends FunS |case class A(a: String) |""".stripMargin val debuggee = TestingDebuggee.mainClass(source, "example.Main", scalaVersion) - val unpickler = getUnpickler(debuggee) - unpickler.assertFormat("example.A", "java.lang.String toString()", "A.toString(): String") - unpickler.assertFormat("example.A", "example.A copy(java.lang.String a)", "A.copy(a: String): A") - unpickler.assertFormat("example.A", "int hashCode()", "A.hashCode(): Int") - unpickler.assertFormat("example.A", "boolean equals(java.lang.Object x$0)", "A.equals(Any): Boolean") - unpickler.assertFormat("example.A", "int productArity()", "A.productArity: Int") - unpickler.assertFormat("example.A", "java.lang.String productPrefix()", "A.productPrefix: String") - unpickler.assertFormat("example.A", "java.lang.Object productElement(int n)", "A.productElement(n: Int): Any") - unpickler.assertNotFound("example.A", "scala.collection.Iterator productIterator()") // it is a bridge + debuggee.assertFormat("example.A", "java.lang.String toString()", "A.toString(): String") + debuggee.assertFormat("example.A", "example.A copy(java.lang.String a)", "A.copy(a: String): A") + debuggee.assertFormat("example.A", "int hashCode()", "A.hashCode(): Int") + debuggee.assertFormat("example.A", "boolean equals(java.lang.Object x$0)", "A.equals(Any): Boolean") + debuggee.assertFormat("example.A", "int productArity()", "A.productArity: Int") + debuggee.assertFormat("example.A", "java.lang.String productPrefix()", "A.productPrefix: String") + debuggee.assertFormat("example.A", "java.lang.Object productElement(int n)", "A.productElement(n: Int): Any") + debuggee.assertNotFound("example.A", "scala.collection.Iterator productIterator()") // it is a bridge - unpickler.assertFormat("example.A$", "example.A apply(java.lang.String a)", "A.apply(a: String): A") - unpickler.assertFormat("example.A$", "example.A unapply(example.A x$1)", "A.unapply(A): A") + debuggee.assertFormat("example.A$", "example.A apply(java.lang.String a)", "A.apply(a: String): A") + debuggee.assertFormat("example.A$", "example.A unapply(example.A x$1)", "A.unapply(A): A") } test("anonymous function") { @@ -348,9 +341,8 @@ abstract class Scala3UnpicklerTests(val scalaVersion: ScalaVersion) extends FunS |} |""".stripMargin val debuggee = TestingDebuggee.mainClass(source, "example.Main", scalaVersion) - val unpickler = getUnpickler(debuggee) // TODO fix: it should find the symbol f by traversing the tree of object Main - unpickler.assertFormat("example.Main$", "int $anonfun$1(int x)", "Main.main.f.$anonfun(x: Int): Int") + debuggee.assertFormat("example.Main$", "int $anonfun$1(int x)", "Main.main.f.$anonfun(x: Int): Int") } test("this.type") { @@ -363,8 +355,7 @@ abstract class Scala3UnpicklerTests(val scalaVersion: ScalaVersion) extends FunS |""".stripMargin val debuggee = TestingDebuggee.mainClass(source, "example.Main", scalaVersion) - val unpickler = getUnpickler(debuggee) - unpickler.assertFormat("example.A", "example.A m()", "A.m(): A") + debuggee.assertFormat("example.A", "example.A m()", "A.m(): A") } test("default values") { @@ -378,16 +369,15 @@ abstract class Scala3UnpicklerTests(val scalaVersion: ScalaVersion) extends FunS |} |""".stripMargin val debuggee = TestingDebuggee.mainClass(source, "example.Main", scalaVersion) - val unpickler = getUnpickler(debuggee) - unpickler.assertFormat("example.A", "java.lang.String m$default$1()", "A.m.: String") - unpickler.assertFormat("example.A", "int m$default$2()", "A.m.: Int") - unpickler.assertFormat( + debuggee.assertFormat("example.A", "java.lang.String m$default$1()", "A.m.: String") + debuggee.assertFormat("example.A", "int m$default$2()", "A.m.: Int") + debuggee.assertFormat( "example.A$", "java.lang.String $lessinit$greater$default$1()", "A..: String" ) - unpickler.assertFormat("example.A$", "int $lessinit$greater$default$2()", "A..: Int") + debuggee.assertFormat("example.A$", "int $lessinit$greater$default$2()", "A..: Int") } test("matches on return types") { @@ -403,11 +393,10 @@ abstract class Scala3UnpicklerTests(val scalaVersion: ScalaVersion) extends FunS |} |""".stripMargin val debuggee = TestingDebuggee.mainClass(source, "example.Main", scalaVersion) - val unpickler = getUnpickler(debuggee) - unpickler.assertFormat("example.A", "int m(scala.collection.immutable.List xs)", "A.m(xs: List[Int]): Int") - unpickler.assertNotFound("example.B", "int m(scala.collection.immutable.List xs)") - unpickler.assertFormat( + debuggee.assertFormat("example.A", "int m(scala.collection.immutable.List xs)", "A.m(xs: List[Int]): Int") + debuggee.assertNotFound("example.B", "int m(scala.collection.immutable.List xs)") + debuggee.assertFormat( "example.B", "java.lang.String m(scala.collection.immutable.List xs)", "B.m(xs: List[String]): String" @@ -450,10 +439,9 @@ abstract class Scala3UnpicklerTests(val scalaVersion: ScalaVersion) extends FunS |""".stripMargin val debuggee = TestingDebuggee.mainClass(source, "example.Main", scalaVersion) - val unpickler = getUnpickler(debuggee) def assertFormat(javaSig: String, expected: String)(using munit.Location): Unit = - unpickler.assertFormat("example.Main$", javaSig, expected) + debuggee.assertFormat("example.Main$", javaSig, expected) assertFormat("example.A m(example.A a)", "Main.m(a: A): A") assertFormat("example.A$B mbis(example.A$B b)", "Main.mbis(b: A.B): A.B") @@ -484,8 +472,7 @@ abstract class Scala3UnpicklerTests(val scalaVersion: ScalaVersion) extends FunS |} |""".stripMargin val debuggee = TestingDebuggee.mainClass(source, "example.Main", scalaVersion) - val unpickler = getUnpickler(debuggee) - unpickler.assertFormat("example.A", "int m1(java.lang.String x)", "A.m1(x: \"a\"): 1") + debuggee.assertFormat("example.A", "int m1(java.lang.String x)", "A.m1(x: \"a\"): 1") } test("type aliases") { @@ -501,8 +488,7 @@ abstract class Scala3UnpicklerTests(val scalaVersion: ScalaVersion) extends FunS |} |""".stripMargin val debuggee = TestingDebuggee.mainClass(source, "example.Main", scalaVersion) - val unpickler = getUnpickler(debuggee) - unpickler.assertFormat("example.Main$", "java.lang.String m(example.A m)", "Main.m(x: Foo): Bar") + debuggee.assertFormat("example.Main$", "java.lang.String m(example.A x)", "Main.m(x: Foo): Bar") } test("refined types") { @@ -523,9 +509,8 @@ abstract class Scala3UnpicklerTests(val scalaVersion: ScalaVersion) extends FunS |} |""".stripMargin val debuggee = TestingDebuggee.mainClass(source, "example.Main", scalaVersion) - val unpickler = getUnpickler(debuggee) - unpickler.assertFormat("example.Main$", "example.B m1()", "Main.m1(): A & B {...}") - unpickler.assertFormat("example.Main$", "java.lang.Object m2()", "Main.m2(): Object {...}") + debuggee.assertFormat("example.Main$", "example.B m1()", "Main.m1(): A & B {...}") + debuggee.assertFormat("example.Main$", "java.lang.Object m2()", "Main.m2(): Object {...}") } test("type parameters") { @@ -542,9 +527,8 @@ abstract class Scala3UnpicklerTests(val scalaVersion: ScalaVersion) extends FunS |} |""".stripMargin val debuggee = TestingDebuggee.mainClass(source, "example.Main", scalaVersion) - val unpickler = getUnpickler(debuggee) - unpickler.assertFormat("example.B", "example.A m1(example.A x)", "B.m1(x: X): X") - unpickler.assertFormat("example.B", "example.A m2(example.A x)", "B.m2[T](x: T): T") + debuggee.assertFormat("example.B", "example.A m1(example.A x)", "B.m1(x: X): X") + debuggee.assertFormat("example.B", "example.A m2(example.A x)", "B.m2[T](x: T): T") } test("nested classes") { @@ -561,8 +545,7 @@ abstract class Scala3UnpicklerTests(val scalaVersion: ScalaVersion) extends FunS |} |""".stripMargin val debuggee = TestingDebuggee.mainClass(source, "example.Main", scalaVersion) - val unpickler = getUnpickler(debuggee) - unpickler.assertFormat("example.Main$", "scala.Enumeration$Value today()", "Main.today(): Enumeration.Value") + debuggee.assertFormat("example.Main$", "scala.Enumeration$Value today()", "Main.today(): Enumeration.Value") } test("matches Null and Nothing") { @@ -575,13 +558,12 @@ abstract class Scala3UnpicklerTests(val scalaVersion: ScalaVersion) extends FunS |} |""".stripMargin val debuggee = TestingDebuggee.mainClass(source, "example.Main", scalaVersion) - val unpickler = getUnpickler(debuggee) - unpickler.assertFormat("example.Main$", "scala.runtime.Nothing$ m(int[] xs)", "Main.m(xs: Array[Int]): Nothing") - unpickler.assertFormat( - "example.Main$", - "scala.runtime.Null$ m(java.lang.String[] xs)", - "Main.m(xs: Array[String]): Null" - ) + debuggee.assertFormat("example.Main$", "scala.runtime.Nothing$ m(int[] xs)", "Main.m(xs: Array[Int]): Nothing") + // debuggee.assertFormat( + // "example.Main$", + // "scala.runtime.Null$ m(java.lang.String[] xs)", + // "Main.m(xs: Array[String]): Null" + // ) } test("matches Array whose erasure is Object") { @@ -593,8 +575,7 @@ abstract class Scala3UnpicklerTests(val scalaVersion: ScalaVersion) extends FunS |} |""".stripMargin val debuggee = TestingDebuggee.mainClass(source, "example.Main", scalaVersion) - val unpickler = getUnpickler(debuggee) - unpickler.assertFormat( + debuggee.assertFormat( "example.Main$", "java.lang.Object m(java.lang.Object xs)", "Main.m[T](xs: Array[T]): Array[T]" @@ -610,8 +591,7 @@ abstract class Scala3UnpicklerTests(val scalaVersion: ScalaVersion) extends FunS |} |""".stripMargin val debuggee = TestingDebuggee.mainClass(source, "example.Main", scalaVersion) - val unpickler = getUnpickler(debuggee) - unpickler.assertFormat("example.A", "java.lang.Object m(java.lang.Object x)", "A.m[T](x: B[T]): B[T]") + debuggee.assertFormat("example.A", "java.lang.Object m(java.lang.Object x)", "A.m[T](x: B[T]): B[T]") } test("trait initializers") { @@ -625,10 +605,8 @@ abstract class Scala3UnpicklerTests(val scalaVersion: ScalaVersion) extends FunS |class B extends A |""".stripMargin val debuggee = TestingDebuggee.mainClass(source, "example.Main", scalaVersion) - val unpickler = getUnpickler(debuggee) - unpickler.assertFormat("example.A", "void $init$(example.A $this)", "A.(): Unit") - unpickler.assertFormat("example.B", "example.B ()", "B.(): Unit") - + debuggee.assertFormat("example.A", "void $init$(example.A $this)", "A.(): Unit") + debuggee.assertFormat("example.B", "example.B ()", "B.(): Unit") } test("vararg type") { @@ -640,8 +618,7 @@ abstract class Scala3UnpicklerTests(val scalaVersion: ScalaVersion) extends FunS |} |""".stripMargin val debuggee = TestingDebuggee.mainClass(source, "example.Main", scalaVersion) - val unpickler = getUnpickler(debuggee) - unpickler.assertFormat( + debuggee.assertFormat( "example.A", "java.lang.String m(scala.collection.immutable.Seq as)", "A.m(as: String*): String" @@ -662,9 +639,8 @@ abstract class Scala3UnpicklerTests(val scalaVersion: ScalaVersion) extends FunS |""".stripMargin val debuggee = TestingDebuggee.mainClass(source, "example.Main", scalaVersion) - val unpickler = getUnpickler(debuggee) - unpickler.assertFormat("example.Main$", "java.lang.String $amp(example.$less$greater x)", "Main.&(x: <>): String") - unpickler.assertFormat("example.$less$greater", "example.$less$greater m()", "<>.m: <>") + debuggee.assertFormat("example.Main$", "java.lang.String $amp(example.$less$greater x)", "Main.&(x: <>): String") + debuggee.assertFormat("example.$less$greater", "example.$less$greater m()", "<>.m: <>") } test("local recursive method") { @@ -683,34 +659,35 @@ abstract class Scala3UnpicklerTests(val scalaVersion: ScalaVersion) extends FunS |""".stripMargin val debuggee = TestingDebuggee.mainClass(source, "example.Main", scalaVersion) - val unpickler = getUnpickler(debuggee) // TODO fix: find rec by traversing the tree of object Main - unpickler.assertFormat("example.Main$", "int rec$1(int x, int acc)", "Main.fac.rec(x: Int, acc: Int): Int") + debuggee.assertFormat("example.Main$", "int rec$1(int x, int acc)", "Main.fac.rec(x: Int, acc: Int): Int") } test("local lazy initializer") { val source = """|package example | - |object Main { - | def main(args: Array[String]): Unit = { - | lazy val foo = { - | println("foo") - | "foo" + |class A { + | def m: Unit = { + | val x: String = "x" + | lazy val y = { + | x + "y" | } - | println(foo) + | println(y) | } |} |""".stripMargin val debuggee = TestingDebuggee.mainClass(source, "example.Main", scalaVersion) - val unpickler = getUnpickler(debuggee) - // TODO fix: find foo by traversing the tree of object Main - unpickler.assertNotFound("example.Main$", "java.lang.String foo$lzyINIT1$1(scala.runtime.LazyRef foo$lzy1$1)") - unpickler.assertFormat( - "example.Main$", - "java.lang.String foo$1(scala.runtime.LazyRef foo$lzy1$2)", - "Main.main.foo: String" + debuggee.assertFormat( + "example.A", + "java.lang.String y$1(java.lang.String x$2, scala.runtime.LazyRef y$lzy1$2)", + "A.m.y: String" + ) + debuggee.assertFormat( + "example.A", + "java.lang.String y$lzyINIT1$1(java.lang.String x$1, scala.runtime.LazyRef y$lzy1$1)", + "A.m.y: String" ) } @@ -736,9 +713,8 @@ abstract class Scala3UnpicklerTests(val scalaVersion: ScalaVersion) extends FunS |} |""".stripMargin val debuggee = TestingDebuggee.mainClass(source, "example.Main", scalaVersion) - val unpickler = getUnpickler(debuggee) - unpickler.assertFormat("example.Outer", "java.lang.String example$Outer$$foo()", "Outer.foo: String") - unpickler.assertFormat("example.A$", "int example$A$$$m()", "A.m: Int") + debuggee.assertFormat("example.Outer", "java.lang.String example$Outer$$foo()", "Outer.foo: String") + debuggee.assertFormat("example.A$", "int example$A$$$m()", "A.m: Int") } test("type lambda") { @@ -752,8 +728,7 @@ abstract class Scala3UnpicklerTests(val scalaVersion: ScalaVersion) extends FunS |""".stripMargin val debuggee = TestingDebuggee.mainClass(source, "example.Main", scalaVersion) - val unpickler = getUnpickler(debuggee) - unpickler.assertFormat("example.Main$", "example.Foo foo()", "Main.foo: Foo[[X] =>> Either[X, Int]]") + debuggee.assertFormat("example.Main$", "example.Foo foo()", "Main.foo: Foo[[X] =>> Either[X, Int]]") } test("package object") { @@ -763,8 +738,7 @@ abstract class Scala3UnpicklerTests(val scalaVersion: ScalaVersion) extends FunS |} |""".stripMargin val debuggee = TestingDebuggee.mainClass(source, "example", scalaVersion) - val unpickler = getUnpickler(debuggee) - unpickler.assertFormat("example.package", "java.lang.String foo()", "example.foo: String") + debuggee.assertFormat("example.package", "java.lang.String foo()", "example.foo: String") } test("top-level definition") { @@ -774,8 +748,7 @@ abstract class Scala3UnpicklerTests(val scalaVersion: ScalaVersion) extends FunS |def foo: String = ??? |""".stripMargin val debuggee = TestingDebuggee.mainClass(source, "example", scalaVersion) - val unpickler = getUnpickler(debuggee) - unpickler.assertFormat("example.example$package", "java.lang.String foo()", "example.foo: String") + debuggee.assertFormat("example.example$package", "java.lang.String foo()", "example.foo: String") } test("i491") { @@ -788,33 +761,59 @@ abstract class Scala3UnpicklerTests(val scalaVersion: ScalaVersion) extends FunS |} |""".stripMargin val debuggee = TestingDebuggee.mainClass(source, "example", scalaVersion) - val unpickler = getUnpickler(debuggee) - unpickler.assertFormat("example.A", "java.lang.String m()", "A.m: String") - unpickler.assertFormat("example.A", "java.lang.String m(java.lang.String x)", "A.m(x: String): String") - } - - private def getUnpickler(debuggee: Debuggee): Scala3Unpickler = - val javaRuntimeJars = debuggee.javaRuntime.toSeq.flatMap { - case Java8(_, classJars, _) => classJars - case java9OrAbove: Java9OrAbove => - java9OrAbove.classSystems.map(_.fileSystem.getPath("/modules", "java.base")) - } - val debuggeeClasspath = debuggee.classPath.toArray ++ javaRuntimeJars - new Scala3Unpickler(debuggeeClasspath, println, testMode = true) + debuggee.assertFormat("example.A", "java.lang.String m()", "A.m: String") + debuggee.assertFormat("example.A", "java.lang.String m(java.lang.String x)", "A.m(x: String): String") + } + + extension (debuggee: TestingDebuggee) + private def unpickler: Scala3Unpickler = + val javaRuntimeJars = debuggee.javaRuntime.toSeq.flatMap { + case Java8(_, classJars, _) => classJars + case java9OrAbove: Java9OrAbove => + java9OrAbove.classSystems.map(_.fileSystem.getPath("/modules", "java.base")) + } + val debuggeeClasspath = debuggee.classPath.toArray ++ javaRuntimeJars + new Scala3Unpickler(debuggeeClasspath, println, testMode = true) + + private def getMethod(declaringType: String, javaSig: String)(using munit.Location): binary.Method = + def typeAndName(p: String): (String, String) = + val parts = p.split(' ').filter(_.nonEmpty) + assert(parts.size == 2) + (parts(0), parts(1)) + + val parts = javaSig.split(Array('(', ')', ',')).filter(_.nonEmpty) + val (returnType, name) = typeAndName(parts(0)) + val params = parts.drop(1).map(typeAndName) + + def matchParams(javaParams: Array[Parameter]): Boolean = + javaParams.map(p => (p.getType.getTypeName, p.getName)).toSeq == params.toSeq + + val cls = debuggee.classLoader.loadClass(declaringType) + if name == "" then + val constructor = cls.getDeclaredConstructors.find(m => matchParams(m.getParameters)) + assert(constructor.isDefined) + JavaReflectConstructor(constructor.get) + else + val method = cls.getDeclaredMethods + .find { m => + // println(s"${m.getReturnType.getName} ${m.getName}(${m.getParameters.map(p => p.getType.getTypeName + " " + p.getName).mkString(", ")})") + m.getName == name && m.getReturnType.getName == returnType && matchParams(m.getParameters) + } + assert(method.isDefined) + JavaReflectMethod(method.get) - extension (unpickler: Scala3Unpickler) private def assertFind(declaringType: String, javaSig: String)(using munit.Location): Unit = - val m = FakeJdiMethod(declaringType, javaSig) + val m = getMethod(declaringType, javaSig) assert(unpickler.findSymbol(m).isDefined) private def assertNotFound(declaringType: String, javaSig: String)(using munit.Location): Unit = - val m = FakeJdiMethod(declaringType, javaSig) + val m = getMethod(declaringType, javaSig) assert(unpickler.findSymbol(m).isEmpty) private def assertFailure(declaringType: String, javaSig: String)(using munit.Location): Unit = - val m = FakeJdiMethod(declaringType, javaSig) + val m = getMethod(declaringType, javaSig) intercept[Exception](unpickler.findSymbol(m)) private def assertFormat(declaringType: String, javaSig: String, expected: String)(using munit.Location): Unit = - val m = FakeJdiMethod(declaringType, javaSig) - assertEquals(unpickler.formatMethod(m).asScala, Some(expected)) + val m = getMethod(declaringType, javaSig) + assertEquals(unpickler.formatMethod(m), Some(expected))