@@ -64,11 +64,17 @@ class InstrumentCoverage extends MacroTransform with IdentityDenotTransformer:
64
64
65
65
override protected def newTransformer (using Context ) = CoverageTransormer ()
66
66
67
- class CoverageTransormer extends Transformer :
68
- var instrumented = false
67
+ private class CoverageTransormer extends Transformer :
69
68
70
69
override def transform (tree : Tree )(using Context ): Tree =
70
+ println(tree.show + tree.toString)
71
71
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
72
78
case tree : If =>
73
79
cpy.If (tree)(
74
80
cond = transform(tree.cond),
@@ -78,61 +84,51 @@ class InstrumentCoverage extends MacroTransform with IdentityDenotTransformer:
78
84
case tree : Try =>
79
85
cpy.Try (tree)(
80
86
expr = instrument(transform(tree.expr), branch = true ),
81
- cases = instrumentCasees (tree.cases),
87
+ cases = instrumentCases (tree.cases),
82
88
finalizer = instrument(transform(tree.finalizer), true )
83
89
)
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)
96
92
case tree : Apply =>
97
- if ( LiftCoverage . needsLift(tree)) {
93
+ if needsLift(tree) then
98
94
liftApply(tree)
99
- } else {
95
+ else
100
96
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
102
103
case Select (qual, _) if (qual.symbol.exists && qual.symbol.is(JavaDefined )) =>
103
104
// Java class can't be used as a value, we can't instrument the
104
105
// qualifier ({<Probe>;System}.xyz() is not possible !) instrument it
105
106
// as it is
106
107
instrument(tree)
107
108
case tree : Select =>
108
- if ( tree.qualifier.isInstanceOf [New ]) {
109
+ if tree.qualifier.isInstanceOf [New ] then
109
110
instrument(tree)
110
- } else {
111
+ else
111
112
cpy.Select (tree)(transform(tree.qualifier), tree.name)
112
- }
113
+
113
114
case tree : CaseDef => instrumentCaseDef(tree)
115
+ case tree : ValDef =>
116
+ // only transform the rhs
117
+ cpy.ValDef (tree)(rhs = transform(tree.rhs))
114
118
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)
122
119
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
124
121
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))
126
124
case tree : Template =>
127
125
// Don't instrument the parents (extends) of a template since it
128
126
// causes problems if the parent constructor takes parameters
129
127
cpy.Template (tree)(
130
128
constr = super .transformSub(tree.constr),
131
129
body = transform(tree.body)
132
130
)
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
+
136
132
// For everything else just recurse and transform
137
133
case _ =>
138
134
report.warning(
@@ -146,7 +142,7 @@ class InstrumentCoverage extends MacroTransform with IdentityDenotTransformer:
146
142
// NOTE: that if only one arg needs to be lifted, we just lift everything
147
143
val lifted = LiftCoverage .liftForCoverage(buffer, tree)
148
144
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
150
146
Block (
151
147
instrumented,
152
148
instrument(
@@ -156,7 +152,7 @@ class InstrumentCoverage extends MacroTransform with IdentityDenotTransformer:
156
152
)
157
153
)
158
154
159
- def instrumentCasees (cases : List [CaseDef ])(using Context ): List [CaseDef ] =
155
+ def instrumentCases (cases : List [CaseDef ])(using Context ): List [CaseDef ] =
160
156
cases.map(instrumentCaseDef)
161
157
162
158
def instrumentCaseDef (tree : CaseDef )(using Context ): CaseDef =
@@ -166,7 +162,7 @@ class InstrumentCoverage extends MacroTransform with IdentityDenotTransformer:
166
162
instrument(tree, tree.sourcePos, branch)
167
163
168
164
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
170
166
val id = statementId.incrementAndGet()
171
167
val statement = new Statement (
172
168
source = ctx.source.file.name,
@@ -192,6 +188,30 @@ class InstrumentCoverage extends MacroTransform with IdentityDenotTransformer:
192
188
List (Literal (Constant (id)), Literal (Constant (outputPath)))
193
189
)
194
190
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
+
195
215
object InstrumentCoverage :
196
216
val name : String = " instrumentCoverage"
197
217
val description : String = " instrument code for coverage cheking"
0 commit comments