@@ -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 =
0 commit comments