Skip to content

Commit 1f73016

Browse files
authored
Merge pull request #683 from adpi2/fix-663
Fix Scala 3 evaluation of capture of by-name arg
2 parents eb44b8f + 6c7c9b4 commit 1f73016

File tree

14 files changed

+183
-276
lines changed

14 files changed

+183
-276
lines changed

build.sbt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -194,8 +194,8 @@ lazy val expressionCompiler = projectMatrix
194194
},
195195
Test / unmanagedSourceDirectories ++= {
196196
val sourceDir = (Test / sourceDirectory).value
197-
CrossVersion.partialVersion(scalaVersion.value).collect {
198-
case (3, x) if x >= 3 => sourceDir / "scala-3.3+"
197+
CrossVersion.partialVersion(scalaVersion.value).collect { case (3, 3) =>
198+
sourceDir / "scala-3.3"
199199
}
200200
},
201201
Compile / doc / sources := Seq.empty,
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
package ch.epfl.scala.debugadapter.internal.evaluator
2+
3+
object JdiNull extends JdiObject(null, null)

modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/evaluator/ScalaEvaluator.scala

Lines changed: 11 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,8 @@ private[internal] class ScalaEvaluator(
5353
(names, values) <- extractValuesAndNames(classLoader)
5454
namesArray <- classLoader.createArray("java.lang.String", names)
5555
valuesArray <- classLoader.createArray("java.lang.Object", values)
56-
args = List(namesArray, valuesArray)
56+
thisObject = frame.thisObject.getOrElse(JdiNull)
57+
args = List(thisObject, namesArray, valuesArray)
5758
expressionInstance <- createExpressionInstance(classLoader, classDir, className, args)
5859
evaluatedValue <- evaluateExpression(expressionInstance)
5960
_ <- updateVariables(valuesArray)
@@ -94,10 +95,7 @@ private[internal] class ScalaEvaluator(
9495
private def extractValuesAndNames(classLoader: JdiClassLoader): Safe[(Seq[JdiString], Seq[JdiValue])] = {
9596
def extractVariablesFromFrame(): Safe[(Seq[JdiString], Seq[JdiValue])] = {
9697
val localVariables = frame.variablesAndValues().map { case (variable, value) => (variable.name, value) }
97-
// Exclude the this object if there already is a local $this variable
98-
// The Scala compiler uses `$this` in the extension methods of AnyVal classes
99-
val thisObject = frame.thisObject.filter(_ => !localVariables.contains("$this")).map("$this".->)
100-
(localVariables ++ thisObject)
98+
localVariables
10199
.map { case (name, value) =>
102100
for {
103101
name <- classLoader.mirrorOf(name)
@@ -109,26 +107,22 @@ private[internal] class ScalaEvaluator(
109107
.map(xs => (xs.map(_._1), xs.map(_._2)))
110108
}
111109
// Only useful in Scala 2
112-
def extractFields(thisObject: JdiObject): Safe[(Seq[JdiString], Seq[JdiValue])] = {
113-
val fields = thisObject.fields
114-
val names = fields.map(_._1).map(classLoader.mirrorOf).traverse
115-
val values = fields.map(_._2).map(value => classLoader.boxIfPrimitive(value)).traverse
110+
def extractThisAndFields(thisObject: JdiObject): Safe[(Seq[JdiString], Seq[JdiValue])] = {
111+
val thisAndFieds = ("$this", thisObject) +: thisObject.fields
112+
val names = thisAndFieds.map(_._1).map(classLoader.mirrorOf).traverse
113+
val values = thisAndFieds.map(_._2).map(value => classLoader.boxIfPrimitive(value)).traverse
116114
Safe.join(names, values)
117115
}
118116

119117
for {
120118
(variableNames, variableValues) <- extractVariablesFromFrame()
121-
// Currently we only need to load the fields for the Scala 2
122-
// expression evaluator.
123-
// It is dangerous because local values can shadow fields
124-
// TODO: adapt Scala 2 expression compiler
119+
// Currently we need to load the this object and the fields for the Scala 2 expression evaluator.
120+
// It is dangerous because local values can shadow them
121+
// TODO: rewrite Scala 2 expression compiler based on Scala 3
125122
(fieldNames, fieldValues) <- frame.thisObject
126123
.filter(_ => compiler.scalaVersion.isScala2)
127-
.map(extractFields)
124+
.map(extractThisAndFields)
128125
.getOrElse(Safe((Nil, Nil)))
129-
// If `this` is a value class then the breakpoint must be in
130-
// a generated method of its companion object which takes
131-
// the erased value`$this` as argument.
132126
} yield {
133127
val names = variableNames ++ fieldNames
134128
val values = variableValues ++ fieldValues

modules/expression-compiler/src/main/scala-2/scala/tools/nsc/InsertExpression.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ class InsertExpression(override val global: ExpressionGlobal) extends Transform
2323
*/
2424
private class Inserter extends Transformer {
2525
private val expressionClassSource =
26-
s"""class $expressionClassName(names: Array[String], values: Array[Object]) {
26+
s"""class $expressionClassName(thisObject: Any, names: Array[String], values: Array[Object]) {
2727
| val valuesByName = names.reverse.map(_.asInstanceOf[String]).zip(values.reverse).toMap
2828
|
2929
| def evaluate() = {

modules/expression-compiler/src/main/scala-3.0/dotty/tools/dotc/ExpressionCompiler.scala

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,18 @@ package dotty.tools.dotc
33
import dotty.tools.dotc.core.Contexts.Context
44
import dotty.tools.dotc.core.Phases.Phase
55
import dotty.tools.dotc.evaluation.*
6+
import dotty.tools.dotc.transform.ElimByName
67
import dotty.tools.dotc.util.SourceFile
78

89
class ExpressionCompiler(using ExpressionContext)(using Context) extends Compiler:
910

1011
override protected def frontendPhases: List[List[Phase]] =
1112
List(EvaluationFrontEnd()) :: super.frontendPhases.tail
1213

13-
override protected def picklerPhases: List[List[Phase]] =
14-
super.picklerPhases :+ List(ExtractExpression())
15-
1614
override protected def transformPhases: List[List[Phase]] =
17-
super.transformPhases :+ List(ResolveReflectEval())
15+
// the ExtractExpression phase should be after ElimByName and ExtensionMethods,
16+
// and before LambdaLift
17+
val transformPhases = super.transformPhases
18+
val index = transformPhases.indexWhere(_.exists(_.phaseName == ElimByName.name))
19+
val (before, after) = transformPhases.splitAt(index + 1)
20+
(before :+ List(ExtractExpression())) ++ (after :+ List(ResolveReflectEval()))

modules/expression-compiler/src/main/scala-3.3/dotty/tools/dotc/ExpressionCompiler.scala

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,18 @@ package dotty.tools.dotc
33
import dotty.tools.dotc.core.Contexts.Context
44
import dotty.tools.dotc.core.Phases.Phase
55
import dotty.tools.dotc.evaluation.*
6+
import dotty.tools.dotc.transform.ElimByName
67

78
class ExpressionCompiler(using ExpressionContext)(using Context) extends Compiler:
89

910
override protected def frontendPhases: List[List[Phase]] =
1011
val parser :: others = super.frontendPhases: @unchecked
1112
parser :: List(InsertExpression()) :: others
1213

13-
override protected def picklerPhases: List[List[Phase]] =
14-
super.picklerPhases :+ List(ExtractExpression())
15-
1614
override protected def transformPhases: List[List[Phase]] =
17-
super.transformPhases :+ List(ResolveReflectEval())
15+
// the ExtractExpression phase should be after ElimByName and ExtensionMethods,
16+
// and before LambdaLift
17+
val transformPhases = super.transformPhases
18+
val index = transformPhases.indexWhere(_.exists(_.phaseName == ElimByName.name))
19+
val (before, after) = transformPhases.splitAt(index + 1)
20+
(before :+ List(ExtractExpression())) ++ (after :+ List(ResolveReflectEval()))

modules/expression-compiler/src/main/scala-3.3/dotty/tools/dotc/ExpressionReporter.scala

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,14 @@ package dotty.tools.dotc
33
import dotty.tools.dotc.core.Contexts.*
44
import dotty.tools.dotc.reporting.AbstractReporter
55
import dotty.tools.dotc.reporting.Diagnostic
6+
import scala.util.matching.Regex
67

78
class ExpressionReporter(reportError: String => Unit) extends AbstractReporter:
89
override def doReport(dia: Diagnostic)(using Context): Unit =
9-
// println(messageAndPos(dia))
10+
// When printing trees, we trim what comes after 'def evaluate'
11+
// val message = messageAndPos(dia)
12+
// val shortMessage = message.split(Regex.quote("\u001b[33mdef\u001b[0m \u001b[36mgetLocalValue\u001b[0m"))(0)
13+
// println(shortMessage)
1014
dia match
1115
case error: Diagnostic.Error =>
1216
val newPos = error.pos.source.positionInUltimateSource(error.pos)

modules/expression-compiler/src/main/scala-3.4/dotty/tools/dotc/ExpressionCompiler.scala

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,18 @@ package dotty.tools.dotc
33
import dotty.tools.dotc.core.Contexts.Context
44
import dotty.tools.dotc.core.Phases.Phase
55
import dotty.tools.dotc.evaluation.*
6+
import dotty.tools.dotc.transform.ElimByName
67

78
class ExpressionCompiler(using ExpressionContext)(using Context) extends Compiler:
89

910
override protected def frontendPhases: List[List[Phase]] =
1011
val parser :: others = super.frontendPhases: @unchecked
1112
parser :: List(InsertExpression()) :: others
1213

13-
override protected def picklerPhases: List[List[Phase]] =
14-
super.picklerPhases :+ List(ExtractExpression())
15-
1614
override protected def transformPhases: List[List[Phase]] =
17-
super.transformPhases :+ List(ResolveReflectEval())
15+
// the ExtractExpression phase should be after ElimByName and ExtensionMethods,
16+
// and before LambdaLift
17+
val transformPhases = super.transformPhases
18+
val index = transformPhases.indexWhere(_.exists(_.phaseName == ElimByName.name))
19+
val (before, after) = transformPhases.splitAt(index + 1)
20+
(before :+ List(ExtractExpression())) ++ (after :+ List(ResolveReflectEval()))

modules/expression-compiler/src/main/scala-3/dotty/tools/dotc/ExpressionCompilerBridge.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ class ExpressionCompilerBridge:
2929
classPath,
3030
"-Yskip:pureStats"
3131
// Debugging: Print the tree after phases of the debugger
32-
// "-Vprint:typer,extract-expression,resolve-reflect-eval",
32+
// "-Vprint:elimByName,extract-expression,resolve-reflect-eval",
3333
) ++ options :+ sourceFile.toString
3434
val exprCtx =
3535
ExpressionContext(expressionClassName, line, expression, localVariables.asScala.toSet, pckg, testMode)

0 commit comments

Comments
 (0)