diff --git a/modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/SourceEntrySearchTree.scala b/modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/SourceEntrySearchTree.scala deleted file mode 100644 index a2415667e..000000000 --- a/modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/SourceEntrySearchTree.scala +++ /dev/null @@ -1,29 +0,0 @@ -package ch.epfl.scala.debugadapter.internal - -import scala.collection.mutable.Map - -trait ClassSearch { - def insert(fqcn: String): Unit - def find(className: String): List[String] -} - -class SourceEntrySearchMap extends ClassSearch { - private val classes = Map[String, List[String]]().withDefaultValue(List.empty) - - private def getLastInnerType(className: String): Option[String] = { - val pattern = """(.+\$)(.+)$""".r - className match { - case pattern(_, innerType) => Some(innerType) - case _ => None - } - } - - def insert(fqcn: String) = { - val classWithOuters = fqcn.split('.').last - var className = getLastInnerType(classWithOuters).getOrElse(classWithOuters) - - classes.update(className, fqcn :: classes(className)) - } - - def find(className: String): List[String] = classes(className) -} diff --git a/modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/SourceLookUpProvider.scala b/modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/SourceLookUpProvider.scala index 750bfc6f9..488c101d4 100644 --- a/modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/SourceLookUpProvider.scala +++ b/modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/SourceLookUpProvider.scala @@ -7,14 +7,18 @@ import com.microsoft.java.debug.core.adapter.ISourceLookUpProvider import java.net.URI import scala.collection.parallel.immutable.ParVector +import ch.epfl.scala.debugadapter.internal.evaluator.NameTransformer private[debugadapter] final class SourceLookUpProvider( private[internal] val classPathEntries: Seq[ClassEntryLookUp], sourceUriToClassPathEntry: Map[URI, ClassEntryLookUp], fqcnToClassPathEntry: Map[String, ClassEntryLookUp] ) extends ISourceLookUpProvider { - private val classSearch = new SourceEntrySearchMap - for (entries <- classPathEntries; fqcn <- entries.fullyQualifiedNames) classSearch.insert(fqcn) + + val classSearch = + classPathEntries + .flatMap { _.fullyQualifiedNames.filterNot { _.contains("$$anon$") } } + .groupBy { SourceLookUpProvider.getScalaClassName } override def supportsRealtimeBreakpointVerification(): Boolean = true @@ -65,8 +69,8 @@ private[debugadapter] final class SourceLookUpProvider( classPathEntries.flatMap(_.fullyQualifiedNames) private[internal] def allOrphanClasses: Iterable[ClassFile] = classPathEntries.flatMap(_.orphanClassFiles) - private[internal] def classesByName(name: String) = - classSearch.find(name) + private[internal] def classesByName(name: String): Seq[String] = + classSearch.get(name).getOrElse(Seq.empty) private[internal] def getScalaSig(fqcn: String): Option[ScalaSig] = { for { @@ -86,6 +90,13 @@ private[debugadapter] object SourceLookUpProvider { def empty: SourceLookUpProvider = new SourceLookUpProvider(Seq.empty, Map.empty, Map.empty) + def getScalaClassName(className: String): String = { + val lastDot = className.lastIndexOf('.') + 1 + val decoded = NameTransformer.decode { className.drop(lastDot) } + val lastDollar = decoded.stripSuffix("$").lastIndexOf('$') + 1 + decoded.drop { lastDollar } + } + def apply(entries: Seq[ClassEntry], logger: Logger): SourceLookUpProvider = { val parrallelEntries = ParVector(entries*) val sourceFilesByEntry = parrallelEntries diff --git a/modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/evaluator/RuntimeDefaultValidator.scala b/modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/evaluator/RuntimeDefaultValidator.scala index e0f7572d6..6a19f2727 100644 --- a/modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/evaluator/RuntimeDefaultValidator.scala +++ b/modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/evaluator/RuntimeDefaultValidator.scala @@ -50,10 +50,13 @@ class RuntimeDefaultValidator(val frame: JdiFrame, val sourceLookUp: SourceLookU expression match { case value: Term.Name => validateName(value.value, thisTree).orElse(validateClass(value.value, thisTree)) case Term.Select(qual, name) => - for { - qual <- validateWithClass(qual) - name <- validateName(name.value, Valid(qual)).orElse(validateClass(name.value, Valid(qual))) - } yield name + validateWithClass(qual).transform { + case qual: Valid[?] => + validateName(name.value, qual) + .orElse { validateClass(name.value, qual) } + case _: Invalid => + searchClassesQCN(qual.toString + "." + name.value) + } case _ => validate(expression) } @@ -115,9 +118,11 @@ class RuntimeDefaultValidator(val frame: JdiFrame, val sourceLookUp: SourceLookU private def inCompanion(name: Option[String], moduleName: String) = name .filter(_.endsWith("$")) .map(n => loadClass(n.stripSuffix("$"))) - .exists(_.filterNot { - _.`type`.methodsByName(moduleName.stripSuffix("$")).isEmpty() - }.isValid) + .exists { + _.filterNot { + _.methodsByName(moduleName.stripSuffix("$")).isEmpty() + }.isValid + } def validateModule(name: String, of: Option[RuntimeTree]): Validation[RuntimeEvaluableTree] = { val moduleName = if (name.endsWith("$")) name else name + "$" @@ -125,11 +130,11 @@ class RuntimeDefaultValidator(val frame: JdiFrame, val sourceLookUp: SourceLookU searchClasses(moduleName, ofName).flatMap { moduleCls => val isInModule = inCompanion(ofName, moduleName) - (isInModule, moduleCls.`type`, of) match { + (isInModule, moduleCls, of) match { case (true, _, _) => CompilerRecoverable(s"Cannot access module ${name} from ${ofName}") - case (_, Module(module), _) => Valid(TopLevelModuleTree(module)) + case (_, Module(_), _) => Valid(TopLevelModuleTree(moduleCls)) case (_, cls, Some(instance: RuntimeEvaluableTree)) => - if (cls.name().startsWith(instance.`type`.name())) + if (cls.name.startsWith(instance.`type`.name())) moduleInitializer(cls, instance) else Recoverable(s"Cannot access module $moduleCls from ${instance.`type`.name()}") case _ => Recoverable(s"Cannot access module $moduleCls") @@ -139,14 +144,18 @@ class RuntimeDefaultValidator(val frame: JdiFrame, val sourceLookUp: SourceLookU def validateClass(name: String, of: Validation[RuntimeTree]): Validation[ClassTree] = searchClasses(name.stripSuffix("$"), of.map(_.`type`.name()).toOption) - .transform { cls => - (cls, of) match { - case (Valid(_), Valid(_: RuntimeEvaluableTree) | _: Invalid) => cls - case (Valid(c), Valid(ct: ClassTree)) => - if (c.`type`.isStatic()) cls - else CompilerRecoverable(s"Cannot access non-static class ${c.`type`.name} from ${ct.`type`.name()}") - case (_, Valid(value)) => validateOuter(value).flatMap(o => validateClass(name, Valid(o))) - case (_, _: Invalid) => Recoverable(s"Cannot find class $name") + .flatMap { cls => + of match { + case Valid(_: RuntimeEvaluableTree) | _: Invalid => Valid(ClassTree(cls)) + case Valid(ct: ClassTree) => + if (cls.isStatic()) Valid(ClassTree(cls)) + else CompilerRecoverable(s"Cannot access non-static class ${cls.name} from ${ct.`type`.name()}") + } + } + .orElse { + of match { + case Valid(value) => validateOuter(value).flatMap(o => validateClass(name, Valid(o))) + case _ => Recoverable(s"Cannot access class $name") } } @@ -165,16 +174,15 @@ class RuntimeDefaultValidator(val frame: JdiFrame, val sourceLookUp: SourceLookU of .flatMap { of => member - .orElse(validateModule(name, Some(of))) + .orElse(validateModule(value, Some(of))) .orElse(validateOuter(of).flatMap(o => validateName(value, Valid(o), methodFirst))) } .orElse { of match { case Valid(_: ThisTree) | _: Recoverable => localVarTreeByName(name) - case _ => Recoverable(s"${name} is not a local variable") + case _ => Recoverable(s"${value} is not a local variable") } } - .orElse { validateModule(name, None) } } /* -------------------------------------------------------------------------- */ @@ -216,19 +224,36 @@ class RuntimeDefaultValidator(val frame: JdiFrame, val sourceLookUp: SourceLookU def validateMethod(call: Call): Validation[RuntimeEvaluableTree] = { lazy val preparedCall = call.fun match { - case select: Term.Select => - PreparedCall(validateWithClass(select.qual), select.name.value) + case Term.Select(qual, name) => + val qualTree = validateWithClass(qual).orElse { + searchClassesQCN(qual.toString + "$") + } + PreparedCall(qualTree, name.value) case name: Term.Name => PreparedCall(thisTree, name.value) } - for { - args <- call.argClause.map(validate).traverse + val validatedArgs = call.argClause.map(validate).traverse + + val method = for { + args <- validatedArgs lhs <- preparedCall.qual methodTree <- PrimitiveUnaryOpTree(lhs, preparedCall.name) .orElse { PrimitiveBinaryOpTree(lhs, args, preparedCall.name) } .orElse { findMethod(lhs, preparedCall.name, args) } } yield methodTree + + call.fun match { + case Term.Select(qual, name) => + method.orElse { + for { + cls <- searchClassesQCN(qual.toString + "." + name.value) + args <- validatedArgs + m <- validateApply(cls, args) + } yield m + } + case _ => method + } } /* -------------------------------------------------------------------------- */ @@ -249,8 +274,8 @@ class RuntimeDefaultValidator(val frame: JdiFrame, val sourceLookUp: SourceLookU for { args <- argClauses.flatMap(_.map(validate(_))).traverse (outer, cls) <- validateType(tpe, thisTree.toOption)(validate) - allArgs = outer.filter(_ => needsOuter(cls.`type`)).toSeq ++ args - newInstance <- newInstanceTreeByArgs(cls.`type`, allArgs) + allArgs = outer.filter(_ => needsOuter(cls)).toSeq ++ args + newInstance <- newInstanceTreeByArgs(cls, allArgs) } yield newInstance } @@ -265,7 +290,7 @@ class RuntimeDefaultValidator(val frame: JdiFrame, val sourceLookUp: SourceLookU /* -------------------------------------------------------------------------- */ def validateIf(tree: Term.If): Validation[RuntimeEvaluableTree] = { - lazy val objType = loadClass("java.lang.Object").get.`type` + lazy val objType = loadClass("java.lang.Object").get for { cond <- validate(tree.cond) thenp <- validate(tree.thenp) diff --git a/modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/evaluator/RuntimeEvaluationHelpers.scala b/modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/evaluator/RuntimeEvaluationHelpers.scala index c610bf2e2..e4e3c3913 100644 --- a/modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/evaluator/RuntimeEvaluationHelpers.scala +++ b/modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/evaluator/RuntimeEvaluationHelpers.scala @@ -224,8 +224,13 @@ private[evaluator] class RuntimeEvaluationHelpers(frame: JdiFrame, sourceLookup: /* -------------------------------------------------------------------------- */ /* Class helpers */ /* -------------------------------------------------------------------------- */ - def loadClass(name: String): Validation[ClassTree] = - Validation.fromTry(frame.classLoader().flatMap(_.loadClass(name)).extract(c => ClassTree(c.cls))) + def loadClass(name: String): Validation[ClassType] = + Validation.fromTry { + frame + .classLoader() + .flatMap(_.loadClass(name)) + .extract(_.cls) + } def checkClassStatus(tpe: => Type) = Try(tpe) match { case Failure(e: ClassNotLoadedException) => loadClass(e.className()) @@ -248,8 +253,8 @@ private[evaluator] class RuntimeEvaluationHelpers(frame: JdiFrame, sourceLookup: // ! TO REFACTOR :sob: def resolveInnerType(qual: Type, name: String) = { - var tpe: Validation[ClassTree] = Recoverable(s"Cannot find outer class for $qual") - def loop(on: Type): Validation[ClassTree] = + var tpe: Validation[ClassType] = Recoverable(s"Cannot find outer class for $qual") + def loop(on: Type): Validation[ClassType] = on match { case _: ArrayType | _: PrimitiveType | _: VoidType => Recoverable("Cannot find outer class on non reference type") @@ -289,7 +294,7 @@ private[evaluator] class RuntimeEvaluationHelpers(frame: JdiFrame, sourceLookup: def validateType(tpe: MType, thisTree: Option[RuntimeEvaluableTree])( termValidation: Term => Validation[RuntimeEvaluableTree] - ): Validation[(Option[RuntimeEvaluableTree], ClassTree)] = + ): Validation[(Option[RuntimeEvaluableTree], ClassType)] = tpe match { case MType.Name(name) => // won't work if the class is defined in one of the outer of this @@ -299,9 +304,11 @@ private[evaluator] class RuntimeEvaluationHelpers(frame: JdiFrame, sourceLookup: qual <- termValidation(qual) tpe <- resolveInnerType(qual.`type`, name.value) } yield - if (tpe.`type`.isStatic()) (None, tpe) + if (tpe.isStatic()) (None, tpe) else (Some(qual), tpe) - cls.orElse(searchClasses(qual.toString + "." + name.value, thisTree.map(_.`type`.name)).map((None, _))) + cls.orElse { + searchClassesQCN(qual.toString + "." + name.value).map(c => (None, c.`type`.asInstanceOf[ClassType])) + } case _ => Recoverable("Type not supported at runtime") } @@ -314,24 +321,24 @@ private[evaluator] class RuntimeEvaluationHelpers(frame: JdiFrame, sourceLookup: .orElse { removeLastInnerTypeFromFQCN(tree.`type`.name()) .map(name => loadClass(name + "$")) match { - case Some(Valid(Module(mod))) => Valid(mod) + case Some(Valid(Module(mod))) => Valid(TopLevelModuleTree(mod)) case res => Recoverable(s"Cannot find $$outer for ${tree.`type`.name()}}") } } case _ => Recoverable(s"Cannot find $$outer for non-reference type ${tree.`type`.name()}}") } - def searchAllClassesFor(name: String, in: Option[String]): Validation[ClassTree] = { - def declaringTypeName = frame.current().location().declaringType().name() + def searchClasses(name: String, in: Option[String]): Validation[ClassType] = { + def baseName = in.getOrElse { frame.current().location().declaringType().name() } val candidates = sourceLookup.classesByName(name) val bestMatch = candidates.size match { case 0 | 1 => candidates case _ => - candidates.filter(_.startsWith { - in.getOrElse(declaringTypeName) - }) + candidates.filter { name => + name.contains(s".$baseName") || name.startsWith(baseName) + } } bestMatch @@ -339,30 +346,23 @@ private[evaluator] class RuntimeEvaluationHelpers(frame: JdiFrame, sourceLookup: .flatMap { loadClass } } - def searchClasses(name: String, in: Option[String]): Validation[ClassTree] = { - if (isFQCN(name)) loadClass(name) - else searchAllClassesFor(name, in) - } - - def isFQCN(name: String): Boolean = { - val regex = """([\w\.]+)\.([^\.]+)""".r - name match { - case regex(_, _) => true - case _ => false - } + def searchClassesQCN(partialClassName: String): Validation[RuntimeTree] = { + val name = SourceLookUpProvider.getScalaClassName(partialClassName) + searchClasses(name + "$", Some(partialClassName)) + .map { TopLevelModuleTree(_) } + .orElse { searchClasses(name, Some(partialClassName)).map { ClassTree(_) } } } /* -------------------------------------------------------------------------- */ /* Initialize module */ /* -------------------------------------------------------------------------- */ def moduleInitializer(modCls: ClassType, of: RuntimeEvaluableTree): Validation[NestedModuleTree] = - for { - initMethodName <- Validation.fromOption(getLastInnerType(modCls.name())) - initMethod <- of.`type` match { - case ref: ReferenceType => zeroArgMethodByName(ref, initMethodName) - case _ => Recoverable(s"Cannot find module initializer for non-reference type $modCls") - } - } yield NestedModuleTree(modCls, InstanceMethodTree(initMethod, Seq(), of)) + of.`type` match { + case ref: ReferenceType => + zeroArgMethodByName(ref, SourceLookUpProvider.getScalaClassName(modCls.name).stripSuffix("$")) + .map(m => NestedModuleTree(modCls, InstanceMethodTree(m, Seq.empty, of))) + case _ => Recoverable(s"Cannot find module initializer for non-reference type $modCls") + } def illegalAccess(x: Any, typeName: String) = Fatal { new ClassCastException(s"Cannot cast $x to $typeName") @@ -383,19 +383,13 @@ private[evaluator] class RuntimeEvaluationHelpers(frame: JdiFrame, sourceLookup: /* -------------------------------------------------------------------------- */ /* Nested types regex */ /* -------------------------------------------------------------------------- */ - def getLastInnerType(className: String): Option[String] = { - val pattern = """(.+\$)([^$]+)$""".r - className.stripSuffix("$") match { - case pattern(_, innerType) => Some(innerType) - case _ => None - } - } - def removeLastInnerTypeFromFQCN(className: String): Option[String] = { - val pattern = """(.+)\$[\w]+\${0,1}$""".r - className match { - case pattern(baseName) => Some(baseName) - case _ => None + val (packageName, clsName) = className.splitAt(className.lastIndexOf('.') + 1) + val name = NameTransformer.decode(clsName) + val lastDollar = name.stripSuffix("$").lastIndexOf('$') + lastDollar match { + case -1 => None + case _ => Some(packageName + name.dropRight(name.length - lastDollar)) } } diff --git a/modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/evaluator/RuntimePreEvaluationValidator.scala b/modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/evaluator/RuntimePreEvaluationValidator.scala index d32615a31..0415d9cad 100644 --- a/modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/evaluator/RuntimePreEvaluationValidator.scala +++ b/modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/evaluator/RuntimePreEvaluationValidator.scala @@ -26,26 +26,26 @@ class RuntimePreEvaluationValidator( super.localVarTreeByName(name).flatMap(preEvaluate) override def fieldTreeByName(of: Validation[RuntimeTree], name: String): Validation[RuntimeEvaluableTree] = - super.fieldTreeByName(of, name).flatMap { - case tree @ (_: StaticFieldTree | InstanceFieldTree(_, _: PreEvaluatedTree)) => + super.fieldTreeByName(of, name).transform { + case Valid(tree @ (_: StaticFieldTree | InstanceFieldTree(_, _: PreEvaluatedTree))) => preEvaluate(tree) - case tree @ (_: TopLevelModuleTree | NestedModuleTree(_, InstanceMethodTree(_, _, _: PreEvaluatedTree))) => + case Valid(tree @ (_: TopLevelModuleTree | NestedModuleTree(_, InstanceMethodTree(_, _, _: PreEvaluatedTree)))) => preEvaluate(tree) - case tree => Valid(tree) + case tree => tree } override def validateModule(name: String, of: Option[RuntimeTree]): Validation[RuntimeEvaluableTree] = - super.validateModule(name, of).flatMap { - case tree @ (_: TopLevelModuleTree | NestedModuleTree(_, InstanceMethodTree(_, _, _: PreEvaluatedTree))) => + super.validateModule(name, of).transform { + case Valid(tree @ (_: TopLevelModuleTree | NestedModuleTree(_, InstanceMethodTree(_, _, _: PreEvaluatedTree)))) => preEvaluate(tree) - case tree => Valid(tree) + case tree => tree } override def validateOuter(tree: RuntimeTree): Validation[RuntimeEvaluableTree] = - super.validateOuter(tree).flatMap { - case tree @ (_: FieldTree | _: TopLevelModuleTree) => + super.validateOuter(tree).transform { + case Valid(tree @ (_: FieldTree | _: TopLevelModuleTree)) => preEvaluate(tree) - case tree => Valid(tree) + case tree => tree } override def validateIf(tree: Term.If): Validation[RuntimeEvaluableTree] = diff --git a/modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/evaluator/RuntimeTree.scala b/modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/evaluator/RuntimeTree.scala index b7bc9761d..17b910e5e 100644 --- a/modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/evaluator/RuntimeTree.scala +++ b/modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/evaluator/RuntimeTree.scala @@ -246,6 +246,7 @@ case class TopLevelModuleTree( |${indent.dropRight(1)})""".stripMargin } } + case class NestedModuleTree( module: ClassType, init: InstanceMethodTree diff --git a/modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/evaluator/Validation.scala b/modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/evaluator/Validation.scala index c2a8997de..4d64a5b6c 100644 --- a/modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/evaluator/Validation.scala +++ b/modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/evaluator/Validation.scala @@ -78,6 +78,7 @@ final case class Recoverable(override val exception: Exception) extends Invalid( sealed abstract class Unrecoverable(override val exception: Exception) extends Invalid(exception) { override def orElse[B >: Nothing](f: => Validation[B]): Validation[B] = this + override def transform[B](f: Validation[Nothing] => Validation[B]): Validation[B] = this } final case class Fatal(override val exception: Exception) extends Unrecoverable(exception) diff --git a/modules/tests/src/main/scala/ch/epfl/scala/debugadapter/testfmk/TestingLoggers.scala b/modules/tests/src/main/scala/ch/epfl/scala/debugadapter/testfmk/TestingLoggers.scala index 2e87202ef..7af7f16e8 100644 --- a/modules/tests/src/main/scala/ch/epfl/scala/debugadapter/testfmk/TestingLoggers.scala +++ b/modules/tests/src/main/scala/ch/epfl/scala/debugadapter/testfmk/TestingLoggers.scala @@ -14,9 +14,9 @@ object NoopLogger extends Logger { * Can be used in DebugServer for debugging */ object PrintLogger extends Logger { - override def debug(msg: => String): Unit = println(s"[DEBUG] $msg") - override def info(msg: => String): Unit = println(s"[INFO] $msg") - override def warn(msg: => String): Unit = println(s"[WARN] $msg") - override def error(msg: => String): Unit = System.err.println(s"[ERROR] $msg") + override def debug(msg: => String): Unit = println(s"${Console.YELLOW}[DEBUG] $msg${Console.RESET}") + override def info(msg: => String): Unit = println(s"${Console.CYAN}[INFO] $msg${Console.RESET}") + override def warn(msg: => String): Unit = println(s"${Console.MAGENTA}[WARN] $msg${Console.RESET}") + override def error(msg: => String): Unit = System.err.println(s"${Console.RED}[ERROR] $msg${Console.RESET}") override def trace(t: => Throwable): Unit = t.printStackTrace() } 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 59ff148cf..8883a1fd4 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 @@ -456,7 +456,7 @@ abstract class RuntimeEvaluatorTests(val scalaVersion: ScalaVersion) extends Deb | } |} |""".stripMargin - implicit val debuggee = TestingDebuggee.mainClass(source, "example.Main", scalaVersion) + implicit val debuggee: TestingDebuggee = TestingDebuggee.mainClass(source, "example.Main", scalaVersion) check( Breakpoint(9), Evaluation.success("b.x", 42) @@ -886,6 +886,83 @@ abstract class RuntimeEvaluatorTests(val scalaVersion: ScalaVersion) extends Deb Evaluation.success("aInner.b", "b") ) } + + test("Should support names with special characters") { + val source = + """|package example + | + |class `A+B` { + | val foo = 42 + | object && { + | def x = { + | println(foo) + | 42 + | } + | } + | object ~~ { def x = foo + 1 } + |} + | + |object `A+B` { + | object || { def x = 43 } + |} + | + |object Main { + | def main(args: Array[String]): Unit = { + | val a = new `A+B` + | a.&&.x + | println("ok") + | } + |} + """.stripMargin + implicit val debuggee: TestingDebuggee = TestingDebuggee.mainClass(source, "example.Main", scalaVersion) + if (scalaVersion == ScalaVersion.`3.1+`) + check( + Breakpoint(7), + Evaluation.success("~~.x", 43) + ) + + check( + Breakpoint(22), + Evaluation.success("a.&&.x", 42), + Evaluation.success("`A+B`.||.x", 43) + ) + } + + test("Should accept partially qualifier class name") { + val source = + """|package foo.bar + | + |object Main { + | def main(args: Array[String]): Unit = { + | println("ok") + | } + |} + | + |object Baz { + | def x() = 42 + | def z = 42 + | val y = 42 + | case class Buzz(y: Int) + | object Buzz { def x = 43 } + |} + |case class Baz(y: Int) { + | case class Bizz(y: Int) + | object Bizz { def x = 43 } + |} + |""".stripMargin + implicit val debuggee: TestingDebuggee = TestingDebuggee.mainClass(source, "foo.bar.Main", scalaVersion) + check( + Breakpoint(5), + Evaluation.success("bar.Baz.x()", 42), + Evaluation.success("bar.Baz.z", 42), + Evaluation.success("bar.Baz.y", 42), + Evaluation.success("bar.Baz(42).y", 42), + Evaluation.success("bar.Baz.Buzz(43).y", 43), + Evaluation.success("bar.Baz.Buzz.x", 43), + Evaluation.success("bar.Baz(42).Bizz(43).y", 43), + Evaluation.success("bar.Baz(42).Bizz.x", 43) + ) + } } /* -------------------------------------------------------------------------- */