Skip to content

Commit f14f73b

Browse files
committed
Add eval-static-fields test
1 parent 9354f4e commit f14f73b

File tree

5 files changed

+127
-46
lines changed

5 files changed

+127
-46
lines changed

compiler/src/dotty/tools/debug/ResolveReflectEval.scala

Lines changed: 50 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -57,15 +57,15 @@ private class ResolveReflectEval(config: ExpressionCompilerConfig, expressionSto
5757
JavaEncoding.encode(typeSymbol) match
5858
case s"scala.runtime.${_}Ref" =>
5959
val elemField = typeSymbol.info.decl(termName("elem")).symbol
60-
gen.setField(
60+
gen.setField(tree)(
6161
gen.getLocalValue(variableName),
6262
elemField.asTerm,
6363
value
6464
)
6565
case _ => gen.setLocalValue(variableName, value)
6666
case ReflectEvalStrategy.ClassCapture(variable, cls, isByName) =>
6767
val rawCapture = gen
68-
.getClassCapture(qualifier, variable.name, cls)
68+
.getClassCapture(tree)(qualifier, variable.name, cls)
6969
.getOrElse {
7070
report.error(s"No capture found for $variable in $cls", tree.srcPos)
7171
ref(defn.Predef_undefined)
@@ -75,15 +75,15 @@ private class ResolveReflectEval(config: ExpressionCompilerConfig, expressionSto
7575
gen.boxIfValueClass(variable, capturedValue)
7676
case ReflectEvalStrategy.ClassCaptureAssign(variable, cls) =>
7777
val capture = gen
78-
.getClassCapture(qualifier, variable.name, cls)
78+
.getClassCapture(tree)(qualifier, variable.name, cls)
7979
.getOrElse {
8080
report.error(s"No capture found for $variable in $cls", tree.srcPos)
8181
ref(defn.Predef_undefined)
8282
}
8383
val value = gen.unboxIfValueClass(variable, args.head)
8484
val typeSymbol = variable.info.typeSymbol
8585
val elemField = typeSymbol.info.decl(termName("elem")).symbol
86-
gen.setField(capture, elemField.asTerm, value)
86+
gen.setField(tree)(capture, elemField.asTerm, value)
8787
case ReflectEvalStrategy.MethodCapture(variable, method, isByName) =>
8888
val rawCapture = gen
8989
.getMethodCapture(method, variable.name)
@@ -104,24 +104,25 @@ private class ResolveReflectEval(config: ExpressionCompilerConfig, expressionSto
104104
val value = gen.unboxIfValueClass(variable, args.head)
105105
val typeSymbol = variable.info.typeSymbol
106106
val elemField = typeSymbol.info.decl(termName("elem")).symbol
107-
gen.setField(capture, elemField.asTerm, value)
107+
gen.setField(tree)(capture, elemField.asTerm, value)
108108
case ReflectEvalStrategy.StaticObject(obj) => gen.getStaticObject(obj)
109109
case ReflectEvalStrategy.Field(field, isByName) =>
110110
// if the field is lazy, if it is private in a value class or a trait
111111
// then we must call the getter method
112112
val fieldValue =
113113
if field.is(Lazy) || field.owner.isValueClass || field.owner.is(Trait)
114-
then gen.callMethod(qualifier, field.getter.asTerm, Nil)
114+
then gen.callMethod(tree)(qualifier, field.getter.asTerm, Nil)
115115
else
116-
val rawValue = gen.getField(qualifier, field)
116+
val rawValue = gen.getField(tree)(qualifier, field)
117117
if isByName then gen.evaluateByName(rawValue) else rawValue
118118
gen.boxIfValueClass(field, fieldValue)
119119
case ReflectEvalStrategy.FieldAssign(field) =>
120120
val arg = gen.unboxIfValueClass(field, args.head)
121-
if field.owner.is(Trait) then gen.callMethod(qualifier, field.setter.asTerm, List(arg))
122-
else gen.setField(qualifier, field, arg)
123-
case ReflectEvalStrategy.MethodCall(method) => gen.callMethod(qualifier, method, args)
124-
case ReflectEvalStrategy.ConstructorCall(ctr, cls) => gen.callConstructor(qualifier, ctr, args)
121+
if field.owner.is(Trait) then
122+
gen.callMethod(tree)(qualifier, field.setter.asTerm, List(arg))
123+
else gen.setField(tree)(qualifier, field, arg)
124+
case ReflectEvalStrategy.MethodCall(method) => gen.callMethod(tree)(qualifier, method, args)
125+
case ReflectEvalStrategy.ConstructorCall(ctr, cls) => gen.callConstructor(tree)(qualifier, ctr, args)
125126
case _ => super.transform(tree)
126127

127128
private def isReflectEval(symbol: Symbol)(using Context): Boolean =
@@ -135,7 +136,7 @@ private class ResolveReflectEval(config: ExpressionCompilerConfig, expressionSto
135136
JavaEncoding.encode(typeSymbol) match
136137
case s"scala.runtime.${_}Ref" =>
137138
val elemField = typeSymbol.info.decl(termName("elem")).symbol
138-
getField(tree, elemField.asTerm)
139+
getField(tree)(tree, elemField.asTerm)
139140
case _ => tree
140141

141142
def boxIfValueClass(term: TermSymbol, tree: Tree): Tree =
@@ -147,7 +148,7 @@ private class ResolveReflectEval(config: ExpressionCompilerConfig, expressionSto
147148
def boxValueClass(valueClass: ClassSymbol, tree: Tree): Tree =
148149
// qualifier is null: a value class cannot be nested into a class
149150
val ctor = valueClass.primaryConstructor.asTerm
150-
callConstructor(nullLiteral, ctor, List(tree))
151+
callConstructor(tree)(nullLiteral, ctor, List(tree))
151152

152153
def unboxIfValueClass(term: TermSymbol, tree: Tree): Tree =
153154
getErasedValueType(atPhase(Phases.elimErasedValueTypePhase)(term.info)) match
@@ -162,7 +163,7 @@ private class ResolveReflectEval(config: ExpressionCompilerConfig, expressionSto
162163
private def unboxValueClass(tree: Tree, tpe: ErasedValueType): Tree =
163164
val cls = tpe.tycon.typeSymbol.asClass
164165
val unboxMethod = ValueClasses.valueClassUnbox(cls).asTerm
165-
callMethod(tree, unboxMethod, Nil)
166+
callMethod(tree)(tree, unboxMethod, Nil)
166167

167168
def getThisObject: Tree =
168169
Apply(Select(expressionThis, termName("getThisObject")), List.empty)
@@ -185,7 +186,7 @@ private class ResolveReflectEval(config: ExpressionCompilerConfig, expressionSto
185186
List(qualifier, Literal(Constant(JavaEncoding.encode(outerCls))))
186187
)
187188

188-
def getClassCapture(qualifier: Tree, originalName: Name, cls: ClassSymbol): Option[Tree] =
189+
def getClassCapture(tree: Tree)(qualifier: Tree, originalName: Name, cls: ClassSymbol): Option[Tree] =
189190
cls.info.decls.iterator
190191
.filter(term => term.isField)
191192
.find { field =>
@@ -196,7 +197,7 @@ private class ResolveReflectEval(config: ExpressionCompilerConfig, expressionSto
196197
info.name == originalName
197198
case _ => false
198199
}
199-
.map(field => getField(qualifier, field.asTerm))
200+
.map(field => getField(tree: Tree)(qualifier, field.asTerm))
200201

201202
def getMethodCapture(method: TermSymbol, originalName: TermName): Option[Tree] =
202203
val methodType = method.info.asInstanceOf[MethodType]
@@ -210,32 +211,40 @@ private class ResolveReflectEval(config: ExpressionCompilerConfig, expressionSto
210211
List(Literal(Constant(JavaEncoding.encode(obj))))
211212
)
212213

213-
def getField(qualifier: Tree, field: TermSymbol): Tree =
214-
Apply(
215-
Select(expressionThis, termName("getField")),
216-
List(
217-
qualifier,
218-
Literal(Constant(JavaEncoding.encode(field.owner.asType))),
219-
Literal(Constant(JavaEncoding.encode(field.name)))
214+
def getField(tree: Tree)(qualifier: Tree, field: TermSymbol): Tree =
215+
if field.owner.isTerm then
216+
report.error(s"Cannot access local val ${field.name} in ${field.owner} as field", tree.srcPos)
217+
ref(defn.Predef_undefined)
218+
else
219+
Apply(
220+
Select(expressionThis, termName("getField")),
221+
List(
222+
qualifier,
223+
Literal(Constant(JavaEncoding.encode(field.owner.asType))),
224+
Literal(Constant(JavaEncoding.encode(field.name)))
225+
)
220226
)
221-
)
222227

223-
def setField(qualifier: Tree, field: TermSymbol, value: Tree): Tree =
224-
Apply(
225-
Select(expressionThis, termName("setField")),
226-
List(
227-
qualifier,
228-
Literal(Constant(JavaEncoding.encode(field.owner.asType))),
229-
Literal(Constant(JavaEncoding.encode(field.name))),
230-
value
228+
def setField(tree: Tree)(qualifier: Tree, field: TermSymbol, value: Tree): Tree =
229+
if field.owner.isTerm then
230+
report.error(s"Cannot access local var ${field.name} in ${field.owner} as field", tree.srcPos)
231+
ref(defn.Predef_undefined)
232+
else
233+
Apply(
234+
Select(expressionThis, termName("setField")),
235+
List(
236+
qualifier,
237+
Literal(Constant(JavaEncoding.encode(field.owner.asType))),
238+
Literal(Constant(JavaEncoding.encode(field.name))),
239+
value
240+
)
231241
)
232-
)
233242

234243
def evaluateByName(function: Tree): Tree =
235244
val castFunction = function.cast(defn.Function0.typeRef.appliedTo(defn.AnyType))
236245
Apply(Select(castFunction, termName("apply")), List())
237246

238-
def callMethod(qualifier: Tree, method: TermSymbol, args: List[Tree]): Tree =
247+
def callMethod(tree: Tree)(qualifier: Tree, method: TermSymbol, args: List[Tree]): Tree =
239248
val methodType = method.info.asInstanceOf[MethodType]
240249
val paramTypesNames = methodType.paramInfos.map(JavaEncoding.encode)
241250
val paramTypesArray = JavaSeqLiteral(
@@ -247,7 +256,7 @@ private class ResolveReflectEval(config: ExpressionCompilerConfig, expressionSto
247256
report.error(s"Unknown captured variable $name in $method", reflectEval.srcPos)
248257
ref(defn.Predef_undefined)
249258
val capturedArgs = methodType.paramNames.dropRight(args.size).map {
250-
case name @ DerivedName(underlying, _) => capturedValue(method, underlying).getOrElse(unknownCapture(name))
259+
case name @ DerivedName(underlying, _) => capturedValue(tree)(method, underlying).getOrElse(unknownCapture(name))
251260
case name => unknownCapture(name)
252261
}
253262

@@ -275,7 +284,7 @@ private class ResolveReflectEval(config: ExpressionCompilerConfig, expressionSto
275284
case _ => result
276285
end callMethod
277286

278-
def callConstructor(qualifier: Tree, ctr: TermSymbol, args: List[Tree]): Tree =
287+
def callConstructor(tree: Tree)(qualifier: Tree, ctr: TermSymbol, args: List[Tree]): Tree =
279288
val methodType = ctr.info.asInstanceOf[MethodType]
280289
val paramTypesNames = methodType.paramInfos.map(JavaEncoding.encode)
281290
val clsName = JavaEncoding.encode(methodType.resType)
@@ -285,7 +294,7 @@ private class ResolveReflectEval(config: ExpressionCompilerConfig, expressionSto
285294
case outer if outer.toString == "$outer" => qualifier
286295
case name @ DerivedName(underlying, _) =>
287296
// if derived then probably a capture
288-
capturedValue(ctr.owner, underlying)
297+
capturedValue(tree: Tree)(ctr.owner, underlying)
289298
.getOrElse {
290299
report.error(s"Unknown captured variable $name in $ctr of ${ctr.owner}", reflectEval.srcPos)
291300
ref(defn.Predef_undefined)
@@ -316,22 +325,22 @@ private class ResolveReflectEval(config: ExpressionCompilerConfig, expressionSto
316325
)
317326
end callConstructor
318327

319-
private def capturedValue(sym: Symbol, originalName: TermName): Option[Tree] =
328+
private def capturedValue(tree: Tree)(sym: Symbol, originalName: TermName): Option[Tree] =
320329
val encodedName = JavaEncoding.encode(originalName)
321-
if expressionStore.classOwners.contains(sym) then capturedByClass(sym.asClass, originalName)
330+
if expressionStore.classOwners.contains(sym) then capturedByClass(tree: Tree)(sym.asClass, originalName)
322331
else if config.localVariables.contains(encodedName) then Some(getLocalValue(encodedName))
323332
else
324333
// if the captured value is not a local variables
325334
// then it must have been captured by the outer method
326335
expressionStore.capturingMethod.flatMap(getMethodCapture(_, originalName))
327336

328-
private def capturedByClass(cls: ClassSymbol, originalName: TermName): Option[Tree] =
337+
private def capturedByClass(tree: Tree)(cls: ClassSymbol, originalName: TermName): Option[Tree] =
329338
val target = expressionStore.classOwners.indexOf(cls)
330339
val qualifier = expressionStore.classOwners
331340
.drop(1)
332341
.take(target)
333342
.foldLeft(getThisObject)((q, cls) => getOuter(q, cls))
334-
getClassCapture(qualifier, originalName, cls)
343+
getClassCapture(tree: Tree)(qualifier, originalName, cls)
335344

336345
private object ResolveReflectEval:
337346
val name = "resolve-reflect-eval"

compiler/test/dotty/tools/debug/DebugStepAssert.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ private[debug] object DebugStepAssert:
8181
case Left(obtained) =>
8282
val message =
8383
s"""|Evaluation failed:
84-
|$obtained""".stripMargin
84+
|${obtained.replace("\n", "\n|")}""".stripMargin
8585
throw new AssertionError(message)
8686
case Right(obtained) =>
8787
val message =
@@ -96,7 +96,7 @@ private[debug] object DebugStepAssert:
9696
s"""|Expected:
9797
|${expected.mkString("\n")}
9898
|Obtained:
99-
|$obtained""".stripMargin
99+
|${obtained.replace("\n", "\n|")}""".stripMargin
100100
assert(expected.forall(e => e.r.findFirstMatchIn(obtained).isDefined), message)
101101
case Right(obtained) =>
102102
val message =

compiler/test/dotty/tools/debug/DebugTests.scala

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,16 +8,19 @@ import dotty.tools.vulpix.*
88
import org.junit.Test
99

1010
import scala.concurrent.duration.*
11+
import scala.util.control.NonFatal
1112

1213
class DebugTests:
1314
import DebugTests.*
1415
@Test def debug: Unit =
1516
implicit val testGroup: TestGroup = TestGroup("debug")
16-
// compileFile("tests/debug/for.scala", TestConfiguration.defaultOptions).checkDebug()
17+
// compileFile("tests/debug/eval-static-fields.scala", TestConfiguration.defaultOptions).checkDebug()
1718
compileFilesInDir("tests/debug", TestConfiguration.defaultOptions).checkDebug()
1819

1920
object DebugTests extends ParallelTesting:
20-
def maxDuration = 45.seconds
21+
def maxDuration =
22+
// Increase the timeout when the user is debugging the tests
23+
if isUserDebugging then 3.hours else 45.seconds
2124
def numberOfSlaves = Runtime.getRuntime().availableProcessors()
2225
def safeMode = Properties.testsSafeMode
2326
def isInteractive = SummaryReport.isInteractive
@@ -99,7 +102,10 @@ object DebugTests extends ParallelTesting:
99102
if verbose then println(s"step ${location.lineNumber}")
100103
assert(location)
101104
case DebugStepAssert(Eval(expr), assert) =>
102-
val result = debugger.evaluate(expr, thread)
105+
val result =
106+
try debugger.evaluate(expr, thread)
107+
catch case NonFatal(cause) =>
108+
throw new Exception(s"Evaluation of $expr failed", cause)
103109
if verbose then println(s"eval $expr $result")
104110
assert(result)
105111
end playDebugSteps

tests/debug/eval-static-fields.check

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
break example.A$ 8
2+
eval a1
3+
result a1
4+
eval this.a2
5+
result a2
6+
eval A.a3
7+
result a3
8+
eval B
9+
result example.A.B
10+
eval this.B.b1
11+
result b1
12+
eval A.B.b2
13+
error value b2 cannot be accessed
14+
eval B.b3
15+
result b3
16+
eval C.c1
17+
result c1
18+
eval D.d1
19+
result d1
20+
eval E
21+
result example.E
22+
eval E.e1
23+
result e1
24+
25+
// eval static fields from private object
26+
break example.A$B$ 16
27+
eval b1
28+
result b1
29+
eval b2
30+
error Cannot access local val b2 in method <init> as field
31+
eval a2
32+
result a2
33+
eval C.c1
34+
result c1
35+
eval E.e1
36+
result e1

tests/debug/eval-static-fields.scala

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
object Test:
2+
def main(args: Array[String]): Unit =
3+
example.A.m()
4+
5+
package example:
6+
object A:
7+
def m(): Unit =
8+
println("A.m()" + a2)
9+
B.m()
10+
11+
val a1 = "a1"
12+
private val a2 = "a2"
13+
private[example] val a3 = "a3"
14+
15+
private object B:
16+
def m(): Unit = println("B.m()")
17+
val b1 = "b1"
18+
private val b2 = "b2"
19+
private[A] val b3 = "b3"
20+
override def toString: String = "example.A.B"
21+
22+
private[A] object C:
23+
val c1 = "c1"
24+
25+
object D:
26+
val d1 = "d1"
27+
28+
private object E:
29+
val e1 = "e1"
30+
override def toString: String = "example.E"

0 commit comments

Comments
 (0)