Skip to content

Commit c49e427

Browse files
Refresh InstrumentCoverage to address review comments
1 parent be16d27 commit c49e427

File tree

2 files changed

+64
-46
lines changed

2 files changed

+64
-46
lines changed

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

+56-36
Original file line numberDiff line numberDiff line change
@@ -64,11 +64,17 @@ class InstrumentCoverage extends MacroTransform with IdentityDenotTransformer:
6464

6565
override protected def newTransformer(using Context) = CoverageTransormer()
6666

67-
class CoverageTransormer extends Transformer:
68-
var instrumented = false
67+
private class CoverageTransormer extends Transformer:
6968

7069
override def transform(tree: Tree)(using Context): Tree =
70+
println(tree.show + tree.toString)
7171
tree match
72+
// simple cases
73+
case tree: (Literal | Import | Export) => tree
74+
case tree: (New | This | Super) => instrument(tree)
75+
case tree if (tree.isEmpty || tree.isType) => tree // empty Thicket, Ident, TypTree, ...
76+
77+
// branches
7278
case tree: If =>
7379
cpy.If(tree)(
7480
cond = transform(tree.cond),
@@ -78,61 +84,51 @@ class InstrumentCoverage extends MacroTransform with IdentityDenotTransformer:
7884
case tree: Try =>
7985
cpy.Try(tree)(
8086
expr = instrument(transform(tree.expr), branch = true),
81-
cases = instrumentCasees(tree.cases),
87+
cases = instrumentCases(tree.cases),
8288
finalizer = instrument(transform(tree.finalizer), true)
8389
)
84-
case Apply(fun, _)
85-
if (
86-
fun.symbol.exists &&
87-
fun.symbol.isInstanceOf[Symbol] &&
88-
fun.symbol == defn.Boolean_&& || fun.symbol == defn.Boolean_||
89-
) =>
90-
super.transform(tree)
91-
case tree @ Apply(fun, args) if (fun.isInstanceOf[Apply]) =>
92-
// We have nested apply, we have to lift all arguments
93-
// Example: def T(x:Int)(y:Int)
94-
// T(f())(1) // should not be changed to {val $x = f(); T($x)}(1) but to {val $x = f(); val $y = 1; T($x)($y)}
95-
liftApply(tree)
90+
91+
// f(args)
9692
case tree: Apply =>
97-
if (LiftCoverage.needsLift(tree)) {
93+
if needsLift(tree) then
9894
liftApply(tree)
99-
} else {
95+
else
10096
super.transform(tree)
101-
}
97+
98+
// (f(x))[args]
99+
case tree @ TypeApply(fun: Apply, args) =>
100+
cpy.TypeApply(tree)(transform(fun), args)
101+
102+
// a.b
102103
case Select(qual, _) if (qual.symbol.exists && qual.symbol.is(JavaDefined)) =>
103104
//Java class can't be used as a value, we can't instrument the
104105
//qualifier ({<Probe>;System}.xyz() is not possible !) instrument it
105106
//as it is
106107
instrument(tree)
107108
case tree: Select =>
108-
if (tree.qualifier.isInstanceOf[New]) {
109+
if tree.qualifier.isInstanceOf[New] then
109110
instrument(tree)
110-
} else {
111+
else
111112
cpy.Select(tree)(transform(tree.qualifier), tree.name)
112-
}
113+
113114
case tree: CaseDef => instrumentCaseDef(tree)
115+
case tree: ValDef =>
116+
// only transform the rhs
117+
cpy.ValDef(tree)(rhs = transform(tree.rhs))
114118

115-
case tree: Literal => instrument(tree)
116-
case tree: Ident if (isWildcardArg(tree)) =>
117-
// We don't want to instrument wildcard arguments. `var a = _` can't be instrumented
118-
tree
119-
case tree: New => instrument(tree)
120-
case tree: This => instrument(tree)
121-
case tree: Super => instrument(tree)
122119
case tree: PackageDef =>
123-
// We don't instrument the pid of the package, but we do instrument the statements
120+
// only transform the statements of the package
124121
cpy.PackageDef(tree)(tree.pid, transform(tree.stats))
125-
case tree: Assign => cpy.Assign(tree)(tree.lhs, transform(tree.rhs))
122+
case tree: Assign =>
123+
cpy.Assign(tree)(tree.lhs, transform(tree.rhs))
126124
case tree: Template =>
127125
// Don't instrument the parents (extends) of a template since it
128126
// causes problems if the parent constructor takes parameters
129127
cpy.Template(tree)(
130128
constr = super.transformSub(tree.constr),
131129
body = transform(tree.body)
132130
)
133-
case tree: Import => tree
134-
// Catch EmptyTree since we can't match directly on it
135-
case tree: Thicket if tree.isEmpty => tree
131+
136132
// For everything else just recurse and transform
137133
case _ =>
138134
report.warning(
@@ -146,7 +142,7 @@ class InstrumentCoverage extends MacroTransform with IdentityDenotTransformer:
146142
// NOTE: that if only one arg needs to be lifted, we just lift everything
147143
val lifted = LiftCoverage.liftForCoverage(buffer, tree)
148144
val instrumented = buffer.toList.map(transform)
149-
//We can now instrument the apply as it is with a custom position to point to the function
145+
// We can now instrument the apply as it is with a custom position to point to the function
150146
Block(
151147
instrumented,
152148
instrument(
@@ -156,7 +152,7 @@ class InstrumentCoverage extends MacroTransform with IdentityDenotTransformer:
156152
)
157153
)
158154

159-
def instrumentCasees(cases: List[CaseDef])(using Context): List[CaseDef] =
155+
def instrumentCases(cases: List[CaseDef])(using Context): List[CaseDef] =
160156
cases.map(instrumentCaseDef)
161157

162158
def instrumentCaseDef(tree: CaseDef)(using Context): CaseDef =
@@ -166,7 +162,7 @@ class InstrumentCoverage extends MacroTransform with IdentityDenotTransformer:
166162
instrument(tree, tree.sourcePos, branch)
167163

168164
def instrument(tree: Tree, pos: SourcePosition, branch: Boolean)(using ctx: Context): Tree =
169-
if (pos.exists && !pos.span.isZeroExtent && !tree.isType)
165+
if pos.exists && !pos.span.isZeroExtent then
170166
val id = statementId.incrementAndGet()
171167
val statement = new Statement(
172168
source = ctx.source.file.name,
@@ -192,6 +188,30 @@ class InstrumentCoverage extends MacroTransform with IdentityDenotTransformer:
192188
List(Literal(Constant(id)), Literal(Constant(outputPath)))
193189
)
194190

191+
/**
192+
* Checks if the apply needs a lift in the coverage phase.
193+
* In case of a nested application, we have to lift all arguments
194+
* Example:
195+
* ```
196+
* def T(x:Int)(y:Int)
197+
* T(f())(1)
198+
* ```
199+
* should not be changed to {val $x = f(); T($x)}(1) but to {val $x = f(); val $y = 1; T($x)($y)}
200+
*/
201+
def needsLift(tree: Apply)(using Context): Boolean =
202+
// We don't want to lift a || getB(), to avoid calling getB if a is true.
203+
// Same idea with a && getB(): if a is false, getB shouldn't be called.
204+
def isBooleanOperator(fun: Tree) =
205+
fun.symbol.exists &&
206+
fun.symbol.isInstanceOf[Symbol] &&
207+
fun.symbol == defn.Boolean_&& || fun.symbol == defn.Boolean_||
208+
209+
val fun = tree.fun
210+
211+
fun.isInstanceOf[Apply] || // nested apply
212+
!isBooleanOperator(fun) ||
213+
!tree.args.isEmpty && !tree.args.forall(LiftCoverage.noLift)
214+
195215
object InstrumentCoverage:
196216
val name: String = "instrumentCoverage"
197217
val description: String = "instrument code for coverage cheking"

compiler/src/dotty/tools/dotc/typer/EtaExpansion.scala

+8-10
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import Symbols._
1111
import Names._
1212
import NameKinds.UniqueName
1313
import util.Spans._
14+
import util.Property
1415
import collection.mutable
1516
import Trees._
1617

@@ -158,23 +159,20 @@ object LiftComplex extends LiftComplex
158159
/** Lift complex + lift the prefixes */
159160
object LiftCoverage extends LiftComplex {
160161

161-
private var liftEverything = false
162+
private val LiftEverything = new Property.Key[Boolean]
162163

163-
/** Return true if the apply needs a lift in the coverage phase
164-
Return false if the args are empty, if one or more will be lifter by a
165-
complex lifter.
166-
*/
167-
def needsLift(tree: tpd.Apply)(using Context): Boolean =
168-
!tree.args.isEmpty && !tree.args.forall(super.noLift(_))
164+
private def liftEverything(using Context): Boolean =
165+
ctx.property(LiftEverything).contains(true)
166+
167+
private def liftEverythingContext(using Context): Context =
168+
ctx.fresh.setProperty(LiftEverything, true)
169169

170170
override def noLift(expr: tpd.Tree)(using Context) =
171171
!liftEverything && super.noLift(expr)
172172

173173
def liftForCoverage(defs: mutable.ListBuffer[tpd.Tree], tree: tpd.Apply)(using Context) = {
174174
val liftedFun = liftApp(defs, tree.fun)
175-
liftEverything = true
176-
val liftedArgs = liftArgs(defs, tree.fun.tpe, tree.args)
177-
liftEverything = false
175+
val liftedArgs = liftArgs(defs, tree.fun.tpe, tree.args)(using liftEverythingContext)
178176
tpd.cpy.Apply(tree)(liftedFun, liftedArgs)
179177
}
180178
}

0 commit comments

Comments
 (0)