@@ -4,6 +4,7 @@ package transform
44import java .io .File
55import java .util .concurrent .atomic .AtomicInteger
66
7+ import ast .tpd .*
78import collection .mutable
89import core .Flags .*
910import core .Contexts .{Context , ctx , inContext }
@@ -17,13 +18,13 @@ import typer.LiftCoverage
1718import util .{SourcePosition , Property }
1819import util .Spans .Span
1920import coverage .*
21+ import localopt .StringInterpolatorOpt .isCompilerIntrinsic
2022
2123/** Implements code coverage by inserting calls to scala.runtime.coverage.Invoker
2224 * ("instruments" the source code).
2325 * The result can then be consumed by the Scoverage tool.
2426 */
2527class InstrumentCoverage extends MacroTransform with IdentityDenotTransformer :
26- import ast .tpd ._
2728
2829 override def phaseName = InstrumentCoverage .name
2930
@@ -60,7 +61,7 @@ class InstrumentCoverage extends MacroTransform with IdentityDenotTransformer:
6061
6162 /** Transforms trees to insert calls to Invoker.invoked to compute the coverage when the code is called */
6263 private class CoverageTransformer extends Transformer :
63- override def transform (tree : Tree )(using ctx : Context ): Tree =
64+ override def transform (tree : Tree )(using Context ): Tree =
6465 inContext(transformCtx(tree)) { // necessary to position inlined code properly
6566 tree match
6667 // simple cases
@@ -131,17 +132,22 @@ class InstrumentCoverage extends MacroTransform with IdentityDenotTransformer:
131132 cpy.ValDef (tree)(rhs = rhs)
132133
133134 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
145151 case tree : PackageDef =>
146152 // only transform the statements of the package
147153 cpy.PackageDef (tree)(tree.pid, transform(tree.stats))
@@ -273,24 +279,29 @@ class InstrumentCoverage extends MacroTransform with IdentityDenotTransformer:
273279 * should not be changed to {val $x = f(); T($x)}(1) but to {val $x = f(); val $y = 1; T($x)($y)}
274280 */
275281 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 ) =
281283 sym == defn.Boolean_&& || sym == defn.Boolean_||
282284
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
286297
287298 val fun = tree.fun
288299 val nestedApplyNeedsLift = fun match
289300 case a : Apply => needsLift(a)
290301 case _ => false
291302
292303 nestedApplyNeedsLift ||
293- ! isBooleanOperator (fun) && ! tree.args.isEmpty && ! tree.args.forall(LiftCoverage .noLift)
304+ ! isUnliftableFun (fun) && ! tree.args.isEmpty && ! tree.args.forall(LiftCoverage .noLift)
294305
295306 /** Check if the body of a DefDef can be instrumented with instrumentBody. */
296307 private def canInstrumentDefDef (tree : DefDef )(using Context ): Boolean =
@@ -330,4 +341,4 @@ class InstrumentCoverage extends MacroTransform with IdentityDenotTransformer:
330341
331342object InstrumentCoverage :
332343 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