diff --git a/build.sbt b/build.sbt index 0919bcd29..babb8e043 100644 --- a/build.sbt +++ b/build.sbt @@ -17,7 +17,7 @@ inThisBuild( developers := Developers.list, version ~= { dynVer => if (isRelease) dynVer - else "3.2.0-SNAPSHOT" // only for local publishing + else "4.0.0-SNAPSHOT" // only for local publishing }, resolvers += Resolver.mavenLocal, compile / javacOptions ++= Seq("-source", "1.8", "-target", "1.8") 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 7f07e0ff3..a3157605f 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 @@ -174,11 +174,6 @@ object RuntimeEvaluationTree { override def prettyPrint(depth: Int): String = s"This(${`type`})" } - object This { - def apply(ths: Option[JdiObject])(implicit logger: Logger): Validation[This] = - Validation.fromOption(ths).map(ths => This(ths.reference.referenceType())) - } - case class StaticModule( `type`: jdi.ReferenceType ) extends RuntimeEvaluationTree { @@ -248,7 +243,7 @@ object RuntimeEvaluationTree { def apply(lhs: RuntimeEvaluationTree, rhs: RuntimeEvaluationTree, tpe: jdi.Type): Validation[Assign] = lhs match { case lhs: Assignable => Valid(Assign(lhs, rhs, tpe)) - case _ => CompilerRecoverable("Left hand side of an assignment must be assignable") + case _ => Recoverable("Left hand side of an assignment must be assignable") } } } diff --git a/modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/evaluator/RuntimeValidation.scala b/modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/evaluator/RuntimeValidation.scala index 26463ddb7..4adb51471 100644 --- a/modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/evaluator/RuntimeValidation.scala +++ b/modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/evaluator/RuntimeValidation.scala @@ -12,8 +12,6 @@ import scala.meta.trees.* import scala.meta.{Type => _, *} import scala.util.Failure import scala.util.Success - -import scala.collection.mutable.Buffer import scala.util.Try private[evaluator] class RuntimeValidation(frame: JdiFrame, sourceLookUp: SourceLookUpProvider, preEvaluation: Boolean)( @@ -56,7 +54,10 @@ private[evaluator] class RuntimeValidation(frame: JdiFrame, sourceLookUp: Source } private lazy val thisTree: Validation[RuntimeEvaluationTree] = - Validation.fromOption(frame.thisObject.map(ths => This(ths.reference.referenceType()))) + Validation.fromOption( + frame.thisObject.map(ths => This(ths.reference.referenceType())), + "`this` is unavailable in a static context" + ) private lazy val declaringType: Validation[jdi.ReferenceType] = Validation(frame.current().location().declaringType()) @@ -120,29 +121,10 @@ private[evaluator] class RuntimeValidation(frame: JdiFrame, sourceLookUp: Source private def validateLiteral(lit: Lit): Validation[RuntimeEvaluationTree] = classLoader.map { loader => val value = loader.mirrorOfLiteral(lit.value) - val tpe = loader - .mirrorOfLiteral(lit.value) - .map(_.value.`type`) - .extract - .get + val tpe = loader.mirrorOfLiteral(lit.value).map(_.value.`type`).extract.get Value(value, tpe) } - private def findVariable(name: String, preevaluate: Boolean = preEvaluation): Validation[RuntimeEvaluationTree] = { - val encodedName = NameTransformer.encode(name) - Validation - .fromOption(frame.variableByName(encodedName)) - .filter(_.`type`.name != "scala.Function0") - .map(v => LocalVar(encodedName, v.`type`)) - .flatMap(v => if (preevaluate) preEvaluate(v) else Valid(v)) - } - - private def findField(name: String, ref: jdi.ReferenceType): Option[jdi.Field] = { - val encodedName = NameTransformer.encode(name) - Option(ref.fieldByName(encodedName)) - .orElse(ref.visibleFields.asScala.find(_.name.endsWith("$" + encodedName))) - } - private def findField( qualifier: RuntimeEvaluationTree, name: String, @@ -150,7 +132,7 @@ private[evaluator] class RuntimeValidation(frame: JdiFrame, sourceLookUp: Source ): Validation[RuntimeEvaluationTree] = { for { qualifierTpe <- asReference(qualifier.`type`) - field <- Validation.fromOption(findField(name, qualifierTpe)) + field <- findField(name, qualifierTpe) _ = loadClassOnNeed(field) fieldTree <- asInstanceField(field, qualifier, preevaluate) } yield fieldTree @@ -162,12 +144,28 @@ private[evaluator] class RuntimeValidation(frame: JdiFrame, sourceLookUp: Source preevaluate: Boolean = preEvaluation ): Validation[RuntimeEvaluationTree] = { for { - field <- Validation.fromOption(findField(name, qualifier)) + field <- findField(name, qualifier) _ = loadClassOnNeed(field) fieldTree <- asStaticField(field, preevaluate = preevaluate) } yield fieldTree } + private def findVariable(name: String, preevaluate: Boolean = preEvaluation): Validation[RuntimeEvaluationTree] = { + val encodedName = NameTransformer.encode(name) + Validation + .fromOption(frame.variableByName(encodedName), s"$name is not a local variable") + .filter(_.`type`.name != "scala.Function0") + .map(v => LocalVar(encodedName, v.`type`)) + .flatMap(v => if (preevaluate) preEvaluate(v) else Valid(v)) + } + + private def findField(name: String, ref: jdi.ReferenceType): Validation[jdi.Field] = { + val encodedName = NameTransformer.encode(name) + def fieldOpt = Option(ref.fieldByName(encodedName)) + .orElse(ref.visibleFields.asScala.find(_.name.endsWith("$" + encodedName))) + Validation.fromOption(fieldOpt, s"$name is not a field in ${ref.name}") + } + private def findClass(name: String): Validation[jdi.ReferenceType] = thisTree .flatMap(findClassMember(name, _)) @@ -219,7 +217,7 @@ private[evaluator] class RuntimeValidation(frame: JdiFrame, sourceLookUp: Source val qualifierTypeName = qualifier.`type`.name findClassMember(moduleClassName, qualifier).flatMap { tpe => if (inCompanion(Some(qualifierTypeName), moduleClassName)) - CompilerRecoverable(s"Cannot access module $name from $qualifierTypeName") + Recoverable(s"Cannot access module $name from $qualifierTypeName") else asModule(tpe, qualifier) } } @@ -300,16 +298,18 @@ private[evaluator] class RuntimeValidation(frame: JdiFrame, sourceLookUp: Source } // ! May not be correct when dealing with an object inside a class - private def findOuter(qualifier: RuntimeEvaluationTree): Validation[RuntimeEvaluationTree] = + private def findOuter(qualifier: RuntimeEvaluationTree): Validation[RuntimeEvaluationTree] = { + val qualifierTypeName = qualifier.`type`.name asReference(qualifier.`type`) .flatMap(tpe => Validation(tpe.fieldByName("$outer"))) .flatMap(asInstanceField(_, qualifier, preevaluate = true)) .orElse { Validation - .fromOption(removeLastInnerTypeFromFQCN(qualifier.`type`.name)) + .fromOption(removeLastInnerTypeFromFQCN(qualifierTypeName), s"$qualifierTypeName is not an inner class") .flatMap(name => loadClass(name + "$")) .flatMap(asStaticModule) } + } private def validateIf(tree: Term.If): Validation[RuntimeEvaluationTree] = for { @@ -331,9 +331,9 @@ private[evaluator] class RuntimeValidation(frame: JdiFrame, sourceLookUp: Source val tpe = if (isAssignableFrom(tType, fType)) Valid(tType) else if (isAssignableFrom(fType, tType)) Valid(fType) - else Validation.fromOption(getCommonSuperClass(tType, fType)).orElse(loadClass("java.lang.Object")) + else getCommonSuperClass(tType, fType).orElse(loadClass("java.lang.Object")) tpe.flatMap(tpe => preEvaluate(If(cond, ifTrue, ifFalse, tpe))) - } else CompilerRecoverable("A predicate must be a boolean") + } else Recoverable("A predicate must be a boolean") } private def isBoolean(tpe: jdi.Type): Boolean = @@ -357,7 +357,7 @@ private[evaluator] class RuntimeValidation(frame: JdiFrame, sourceLookUp: Source findVariable(name, false) .orElse(thisTree.flatMap(findField(_, name, false))) .orElse(declaringType.flatMap(findStaticField(_, name, false))) - case _ => CompilerRecoverable("Unsupported assignment") + case _ => Recoverable("Unsupported assignment") } for { @@ -377,12 +377,7 @@ private[evaluator] class RuntimeValidation(frame: JdiFrame, sourceLookUp: Source } private def fromLitToValue(literal: Lit, classLoader: JdiClassLoader): (Safe[Any], jdi.Type) = { - val tpe = classLoader - .mirrorOfLiteral(literal.value) - .map(_.value.`type`) - .getResult - .get - + val tpe = classLoader.mirrorOfLiteral(literal.value).map(_.value.`type`).getResult.get (Safe(literal.value), tpe) } @@ -537,8 +532,8 @@ private[evaluator] class RuntimeValidation(frame: JdiFrame, sourceLookUp: Source private def isAssignableFrom(got: jdi.Type, expected: jdi.Type): Boolean = { def referenceTypesMatch(got: jdi.ReferenceType, expected: jdi.ReferenceType) = { - val assignableFrom = expected.classObject().referenceType().methodsByName("isAssignableFrom").get(0) - val params = Seq(got.classObject()).asJava + val assignableFrom = expected.classObject.referenceType.methodsByName("isAssignableFrom").get(0) + val params = Seq(got.classObject).asJava expected.classObject .invokeMethod(frame.thread, assignableFrom, params, jdi.ObjectReference.INVOKE_SINGLE_THREADED) .asInstanceOf[jdi.BooleanValue] @@ -624,7 +619,7 @@ private[evaluator] class RuntimeValidation(frame: JdiFrame, sourceLookUp: Source loop(qualifier) } - private def getCommonSuperClass(tpe1: jdi.Type, tpe2: jdi.Type): Option[jdi.Type] = { + private def getCommonSuperClass(tpe1: jdi.Type, tpe2: jdi.Type): Validation[jdi.Type] = { def getSuperClasses(of: jdi.Type): Array[jdi.ClassType] = of match { case cls: jdi.ClassType => @@ -634,7 +629,10 @@ private[evaluator] class RuntimeValidation(frame: JdiFrame, sourceLookUp: Source val superClasses1 = getSuperClasses(tpe1) val superClasses2 = getSuperClasses(tpe2) - superClasses1.find(superClasses2.contains) + Validation.fromOption( + superClasses1.find(superClasses2.contains), + s"${tpe1.name} and ${tpe2.name} do not have any common super class" + ) } private def validateType( @@ -738,7 +736,7 @@ private[evaluator] class RuntimeValidation(frame: JdiFrame, sourceLookUp: Source iter.size match { case 1 => Valid(iter.head) case 0 => Recoverable(message) - case _ => CompilerRecoverable(s"$message: multiple values found") + case _ => Recoverable(s"$message: multiple values found") } } } 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 bcc9ec626..5bc83be31 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 @@ -2,12 +2,7 @@ package ch.epfl.scala.debugadapter.internal.evaluator import java.util.NoSuchElementException -import com.sun.jdi.VMDisconnectedException -import com.sun.jdi.ObjectCollectedException -import com.sun.jdi.InvalidStackFrameException -import com.sun.jdi.AbsentInformationException -import com.sun.jdi.InvocationException -import com.sun.jdi.VMOutOfMemoryException +import com.sun.jdi.* import ch.epfl.scala.debugadapter.Logger sealed abstract class Validation[+A] { @@ -45,7 +40,8 @@ final case class Valid[+A](value: A) extends Validation[A]() { override def toOption: Option[A] = Some(value) } -sealed abstract class Invalid(val exception: Exception) extends Validation[Nothing]() { +sealed trait Invalid extends Validation[Nothing] { + def exception: Throwable override val isValid: Boolean = false override def map[B](f: Nothing => B)(implicit logger: Logger): Validation[B] = this override def flatMap[B](f: Nothing => Validation[B])(implicit logger: Logger): Validation[B] = this @@ -57,18 +53,19 @@ sealed abstract class Invalid(val exception: Exception) extends Validation[Nothi override def toOption: Option[Nothing] = None } -final case class Recoverable(override val exception: Exception) extends Invalid(exception) { - override def orElse[B >: Nothing](f: => Validation[B]): Validation[B] = f +final case class Recoverable(exception: Exception) extends Invalid { + override def orElse[B >: Nothing](f: => Validation[B]): Validation[B] = f match { + case valid: Valid[B] => valid + case _: Recoverable => this + case fatal: Fatal => fatal + } } -sealed abstract class Unrecoverable(override val exception: Exception) extends Invalid(exception) { +final case class Fatal(exception: Throwable) extends Invalid { 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) -final case class CompilerRecoverable(override val exception: Exception) extends Unrecoverable(exception) - object Validation { private def handler(t: Throwable)(implicit logger: Logger): Invalid = t match { case e @ (_: VMDisconnectedException | _: ObjectCollectedException) => Fatal(e) @@ -76,7 +73,7 @@ object Validation { case e @ (_: InvocationException | _: VMOutOfMemoryException) => Fatal(e) case e: Exception => logger.warn(s"Unexpected error while validating: $e") - CompilerRecoverable(e) + Recoverable(e) } def apply[A](input: => A)(implicit logger: Logger): Validation[A] = { @@ -89,10 +86,10 @@ object Validation { } } - def fromOption[A](value: => Option[A]): Validation[A] = { + def fromOption[A](value: => Option[A], message: String): Validation[A] = { value match { case Some(value) => Valid(value) - case None => Recoverable("Empty option") + case None => Recoverable(new NoSuchElementException(message)) } } @@ -106,23 +103,9 @@ object Validation { } object Invalid { - def unapply(invalid: Invalid): Option[Exception] = Some(invalid.exception) + def unapply(invalid: Invalid): Option[Throwable] = Some(invalid.exception) } object Recoverable { def apply(s: String) = new Recoverable(new NoSuchElementException(s)) } - -object Fatal { - def apply(t: Throwable): Unrecoverable = new Fatal(new Exception(t)) - def apply(str: String): Unrecoverable = new Fatal(new Exception(str)) -} - -object CompilerRecoverable { - def apply(t: Throwable): Unrecoverable = CompilerRecoverable( - new Exception(s"Can't validate at runtime, recovering with compiler: $t") - ) - def apply(str: String): Unrecoverable = CompilerRecoverable( - new Exception(s"Can't validate at runtime, recovering with compiler: $str") - ) -} diff --git a/modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/evaluator/package.scala b/modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/evaluator/package.scala index a3d5514ed..65416cfa9 100644 --- a/modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/evaluator/package.scala +++ b/modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/evaluator/package.scala @@ -5,8 +5,6 @@ import scala.util.Failure import scala.util.Success import ch.epfl.scala.debugadapter.Logger -import scala.jdk.CollectionConverters.* - package object evaluator { implicit class SafeSeq[A](seq: Seq[Safe[A]]) { def traverse: Safe[Seq[A]] = { @@ -36,20 +34,4 @@ package object evaluator { } } } - - implicit class SeqExtensions[A](seq: Seq[A]) { - def toValidation(message: String): Validation[A] = - seq.size match { - case 1 => Valid(seq.head) - case 0 => Recoverable(message) - case _ => CompilerRecoverable(s"$message: multiple values found") - } - - def asJavaList = seq.asJava - } - - implicit class JavaListToScala[A](list: java.util.List[A]) { - def asScalaList: List[A] = list.asScala.toList - def asScalaSeq: Seq[A] = list.asScala.toSeq - } }