Skip to content

Commit e4b63e3

Browse files
Don't lift standard string interpolators for coverage + add tests
Fixes #15487
1 parent 93211c3 commit e4b63e3

File tree

7 files changed

+670
-26
lines changed

7 files changed

+670
-26
lines changed

compiler/src/dotty/tools/dotc/transform/InstrumentCoverage.scala

+20-6
Original file line numberDiff line numberDiff line change
@@ -273,20 +273,34 @@ class InstrumentCoverage extends MacroTransform with IdentityDenotTransformer:
273273
* should not be changed to {val $x = f(); T($x)}(1) but to {val $x = f(); val $y = 1; T($x)($y)}
274274
*/
275275
private def needsLift(tree: Apply)(using Context): Boolean =
276-
def isBooleanOperator(fun: Tree) =
277-
// We don't want to lift a || getB(), to avoid calling getB if a is true.
278-
// Same idea with a && getB(): if a is false, getB shouldn't be called.
279-
val sym = fun.symbol
280-
sym.exists &&
276+
inline def isShortCircuitedOp(sym: Symbol) =
281277
sym == defn.Boolean_&& || sym == defn.Boolean_||
282278

279+
inline def isCompilerIntrinsic(sym: Symbol) =
280+
sym == defn.StringContext_s ||
281+
sym == defn.StringContext_f ||
282+
sym == defn.StringContext_raw
283+
284+
def isUnliftableFun(fun: Tree) =
285+
/*
286+
* We don't want to lift a || getB(), to avoid calling getB if a is true.
287+
* Same idea with a && getB(): if a is false, getB shouldn't be called.
288+
*
289+
* On top of that, the `s`, `f` and `raw` string interpolators are special-cased
290+
* by the compiler and will disappear in phase StringInterpolatorOpt, therefore
291+
* they shouldn't be lifted.
292+
*/
293+
val sym = fun.symbol
294+
sym.exists && (isShortCircuitedOp(sym) || isCompilerIntrinsic(sym))
295+
end
296+
283297
val fun = tree.fun
284298
val nestedApplyNeedsLift = fun match
285299
case a: Apply => needsLift(a)
286300
case _ => false
287301

288302
nestedApplyNeedsLift ||
289-
!isBooleanOperator(fun) && !tree.args.isEmpty && !tree.args.forall(LiftCoverage.noLift)
303+
!isUnliftableFun(fun) && !tree.args.isEmpty && !tree.args.forall(LiftCoverage.noLift)
290304

291305
/** Check if the body of a DefDef can be instrumented with instrumentBody. */
292306
private def canInstrumentDefDef(tree: DefDef)(using Context): Boolean =

tests/coverage/run/interpolation/test.check

+3
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,6 @@
33
2: t
44
3: t
55
4: y
6+
1, 3
7+
0x007f
8+
a\nb

tests/coverage/run/interpolation/test.scala

+7-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,13 @@ object Test:
33
def simple(a: Int, b: String): String =
44
s"$a, ${b.length}"
55

6+
def hexa(i: Int): String =
7+
f"0x${i}%04x"
8+
69
def main(args: Array[String]): Unit =
710
val xs: List[String] = List("d", "o", "t", "t", "y")
8-
911
xs.zipWithIndex.map((s, i) => println(s"$i: $s"))
12+
13+
println(simple(1, "abc"))
14+
println(hexa(127))
15+
println(raw"a\nb")

tests/coverage/run/interpolation/test.scoverage.check

+155-19
Original file line numberDiff line numberDiff line change
@@ -75,78 +75,214 @@ interpolation/test.scala
7575
Test$
7676
Object
7777
<empty>.Test$
78-
main
79-
147
80-
176
78+
hexa
79+
113
80+
126
8181
6
82+
f
83+
Apply
84+
false
85+
0
86+
false
87+
f"0x${i}%04x"
88+
89+
4
90+
interpolation/test.scala
91+
<empty>
92+
Test$
93+
Object
94+
<empty>.Test$
95+
hexa
96+
82
97+
90
98+
5
99+
hexa
100+
DefDef
101+
false
102+
0
103+
false
104+
def hexa
105+
106+
5
107+
interpolation/test.scala
108+
<empty>
109+
Test$
110+
Object
111+
<empty>.Test$
112+
main
113+
195
114+
224
115+
9
82116
apply
83117
Apply
84118
false
85119
0
86120
false
87121
List("d", "o", "t", "t", "y")
88122

89-
4
123+
6
90124
interpolation/test.scala
91125
<empty>
92126
Test$
93127
Object
94128
<empty>.Test$
95129
$anonfun
96-
220
97-
229
98-
8
130+
267
131+
276
132+
10
99133
s
100134
Apply
101135
false
102136
0
103137
false
104138
s"$i: $s"
105139

106-
5
140+
7
107141
interpolation/test.scala
108142
<empty>
109143
Test$
110144
Object
111145
<empty>.Test$
112146
$anonfun
113-
212
114-
230
115-
8
147+
259
148+
277
149+
10
116150
println
117151
Apply
118152
false
119153
0
120154
false
121155
println(s"$i: $s")
122156

123-
6
157+
8
124158
interpolation/test.scala
125159
<empty>
126160
Test$
127161
Object
128162
<empty>.Test$
129163
main
130-
182
131-
231
132-
8
164+
229
165+
278
166+
10
133167
map
134168
Apply
135169
false
136170
0
137171
false
138172
xs.zipWithIndex.map((s, i) => println(s"$i: $s"))
139173

140-
7
174+
9
141175
interpolation/test.scala
142176
<empty>
143177
Test$
144178
Object
145179
<empty>.Test$
146180
main
147-
82
148-
90
149-
5
181+
292
182+
308
183+
12
184+
simple
185+
Apply
186+
false
187+
0
188+
false
189+
simple(1, "abc")
190+
191+
10
192+
interpolation/test.scala
193+
<empty>
194+
Test$
195+
Object
196+
<empty>.Test$
197+
main
198+
284
199+
309
200+
12
201+
println
202+
Apply
203+
false
204+
0
205+
false
206+
println(simple(1, "abc"))
207+
208+
11
209+
interpolation/test.scala
210+
<empty>
211+
Test$
212+
Object
213+
<empty>.Test$
214+
main
215+
322
216+
331
217+
13
218+
hexa
219+
Apply
220+
false
221+
0
222+
false
223+
hexa(127)
224+
225+
12
226+
interpolation/test.scala
227+
<empty>
228+
Test$
229+
Object
230+
<empty>.Test$
231+
main
232+
314
233+
332
234+
13
235+
println
236+
Apply
237+
false
238+
0
239+
false
240+
println(hexa(127))
241+
242+
13
243+
interpolation/test.scala
244+
<empty>
245+
Test$
246+
Object
247+
<empty>.Test$
248+
main
249+
345
250+
354
251+
14
252+
raw
253+
Apply
254+
false
255+
0
256+
false
257+
raw"a\nb"
258+
259+
14
260+
interpolation/test.scala
261+
<empty>
262+
Test$
263+
Object
264+
<empty>.Test$
265+
main
266+
337
267+
355
268+
14
269+
println
270+
Apply
271+
false
272+
0
273+
false
274+
println(raw"a\nb")
275+
276+
15
277+
interpolation/test.scala
278+
<empty>
279+
Test$
280+
Object
281+
<empty>.Test$
282+
main
283+
130
284+
138
285+
8
150286
main
151287
DefDef
152288
false
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
true false true false false
2+
true
3+
true
+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
2+
def notCalled() = ???
3+
4+
def f(x: Boolean, y: Boolean): Boolean = x
5+
6+
@main
7+
def Test: Unit =
8+
val a = true || notCalled() // true
9+
val b = false && notCalled() // false
10+
val c = (true || false) || notCalled() // true
11+
val d = true && (false && notCalled()) // false
12+
val e = (true && false) && notCalled() // false
13+
println(s"$a $b $c $d $e")
14+
15+
var x = f(true, false)
16+
println(x) // true
17+
18+
x = f(true || notCalled(), false && notCalled())
19+
println(x) // true

0 commit comments

Comments
 (0)