@@ -4,6 +4,7 @@ package transform
4
4
import java .io .File
5
5
import java .util .concurrent .atomic .AtomicInteger
6
6
7
+ import ast .tpd .*
7
8
import collection .mutable
8
9
import core .Flags .*
9
10
import core .Contexts .{Context , ctx , inContext }
@@ -17,13 +18,13 @@ import typer.LiftCoverage
17
18
import util .{SourcePosition , Property }
18
19
import util .Spans .Span
19
20
import coverage .*
21
+ import localopt .StringInterpolatorOpt .isCompilerIntrinsic
20
22
21
23
/** Implements code coverage by inserting calls to scala.runtime.coverage.Invoker
22
24
* ("instruments" the source code).
23
25
* The result can then be consumed by the Scoverage tool.
24
26
*/
25
27
class InstrumentCoverage extends MacroTransform with IdentityDenotTransformer :
26
- import ast .tpd ._
27
28
28
29
override def phaseName = InstrumentCoverage .name
29
30
@@ -60,7 +61,7 @@ class InstrumentCoverage extends MacroTransform with IdentityDenotTransformer:
60
61
61
62
/** Transforms trees to insert calls to Invoker.invoked to compute the coverage when the code is called */
62
63
private class CoverageTransformer extends Transformer :
63
- override def transform (tree : Tree )(using ctx : Context ): Tree =
64
+ override def transform (tree : Tree )(using Context ): Tree =
64
65
inContext(transformCtx(tree)) { // necessary to position inlined code properly
65
66
tree match
66
67
// simple cases
@@ -131,17 +132,22 @@ class InstrumentCoverage extends MacroTransform with IdentityDenotTransformer:
131
132
cpy.ValDef (tree)(rhs = rhs)
132
133
133
134
case tree : DefDef =>
134
- // Only transform the params (for the default values) and the rhs.
135
- val paramss = transformParamss(tree.paramss)
136
- val rhs = transform(tree.rhs)
137
- val finalRhs =
138
- if canInstrumentDefDef(tree) then
139
- // Ensure that the rhs is always instrumented, if possible
140
- instrumentBody(tree, rhs)
141
- else
142
- rhs
143
- cpy.DefDef (tree)(tree.name, paramss, tree.tpt, finalRhs)
144
-
135
+ if tree.symbol.isOneOf(Inline | Erased ) then
136
+ // Inline and erased definitions will not be in the generated code and therefore do not need to be instrumented.
137
+ // Note that a retained inline method will have a `$retained` variant that will be instrumented.
138
+ tree
139
+ else
140
+ // Only transform the params (for the default values) and the rhs.
141
+ val paramss = transformParamss(tree.paramss)
142
+ val rhs = transform(tree.rhs)
143
+ val finalRhs =
144
+ if canInstrumentDefDef(tree) then
145
+ // Ensure that the rhs is always instrumented, if possible
146
+ instrumentBody(tree, rhs)
147
+ else
148
+ rhs
149
+ cpy.DefDef (tree)(tree.name, paramss, tree.tpt, finalRhs)
150
+ end if
145
151
case tree : PackageDef =>
146
152
// only transform the statements of the package
147
153
cpy.PackageDef (tree)(tree.pid, transform(tree.stats))
@@ -273,24 +279,29 @@ class InstrumentCoverage extends MacroTransform with IdentityDenotTransformer:
273
279
* should not be changed to {val $x = f(); T($x)}(1) but to {val $x = f(); val $y = 1; T($x)($y)}
274
280
*/
275
281
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 &&
282
+ def isShortCircuitedOp (sym : Symbol ) =
281
283
sym == defn.Boolean_&& || sym == defn.Boolean_||
282
284
283
- def isContextual (fun : Apply ): Boolean =
284
- val args = fun.args
285
- args.nonEmpty && args.head.symbol.isAllOf(Given | Implicit )
285
+ def isUnliftableFun (fun : Tree ) =
286
+ /*
287
+ * We don't want to lift a || getB(), to avoid calling getB if a is true.
288
+ * Same idea with a && getB(): if a is false, getB shouldn't be called.
289
+ *
290
+ * On top of that, the `s`, `f` and `raw` string interpolators are special-cased
291
+ * by the compiler and will disappear in phase StringInterpolatorOpt, therefore
292
+ * they shouldn't be lifted.
293
+ */
294
+ val sym = fun.symbol
295
+ sym.exists && (isShortCircuitedOp(sym) || isCompilerIntrinsic(sym))
296
+ end
286
297
287
298
val fun = tree.fun
288
299
val nestedApplyNeedsLift = fun match
289
300
case a : Apply => needsLift(a)
290
301
case _ => false
291
302
292
303
nestedApplyNeedsLift ||
293
- ! isBooleanOperator (fun) && ! tree.args.isEmpty && ! tree.args.forall(LiftCoverage .noLift)
304
+ ! isUnliftableFun (fun) && ! tree.args.isEmpty && ! tree.args.forall(LiftCoverage .noLift)
294
305
295
306
/** Check if the body of a DefDef can be instrumented with instrumentBody. */
296
307
private def canInstrumentDefDef (tree : DefDef )(using Context ): Boolean =
@@ -330,4 +341,4 @@ class InstrumentCoverage extends MacroTransform with IdentityDenotTransformer:
330
341
331
342
object InstrumentCoverage :
332
343
val name : String = " instrumentCoverage"
333
- val description : String = " instrument code for coverage cheking "
344
+ val description : String = " instrument code for coverage checking "
0 commit comments