@@ -15,12 +15,10 @@ import dotty.tools.dotc.coverage.Location
15
15
import dotty .tools .dotc .core .Symbols .defn
16
16
import dotty .tools .dotc .core .Symbols .Symbol
17
17
import dotty .tools .dotc .core .Decorators .toTermName
18
- import dotty .tools .dotc .util .SourcePosition
18
+ import dotty .tools .dotc .util .{ SourcePosition , Property }
19
19
import dotty .tools .dotc .core .Constants .Constant
20
20
import dotty .tools .dotc .typer .LiftCoverage
21
21
22
- import scala .quoted
23
-
24
22
/** Implements code coverage by inserting calls to scala.runtime.Invoker
25
23
* ("instruments" the source code).
26
24
* The result can then be consumed by the Scoverage tool.
@@ -51,29 +49,40 @@ class InstrumentCoverage extends MacroTransform with IdentityDenotTransformer:
51
49
val dataDir = new File (outputPath)
52
50
val newlyCreated = dataDir.mkdirs()
53
51
54
- if ( ! newlyCreated) {
52
+ if ! newlyCreated then
55
53
// If the directory existed before, let's clean it up.
56
- dataDir.listFiles
57
- .filter(_.getName.startsWith(" scoverage" ))
58
- .foreach(_.delete)
59
- }
60
-
54
+ dataDir.listFiles.nn
55
+ .filter(_.nn.getName.nn.startsWith(" scoverage" ))
56
+ .foreach(_.nn.delete())
57
+ end if
61
58
super .run
62
59
63
60
Serializer .serialize(coverage, outputPath, ctx.settings.coverageSourceroot.value)
64
61
65
62
override protected def newTransformer (using Context ) = CoverageTransormer ()
66
63
64
+ /** Transforms trees to insert calls to Invoker.invoked to compute the coverage when the code is called */
67
65
private class CoverageTransormer extends Transformer :
66
+ private val IgnoreLiterals = new Property .Key [Boolean ]
67
+
68
+ private def ignoreLiteralsContext (using ctx : Context ): Context =
69
+ ctx.fresh.setProperty(IgnoreLiterals , true )
68
70
69
- override def transform (tree : Tree )(using Context ): Tree =
70
- println(tree.show + tree.toString)
71
+ override def transform (tree : Tree )(using ctx : Context ): Tree =
71
72
tree match
72
73
// simple cases
73
- case tree : (Literal | Import | Export ) => tree
74
- case tree : (New | This | Super ) => instrument(tree)
74
+ case tree : (Import | Export | This | Super | New ) => tree
75
75
case tree if (tree.isEmpty || tree.isType) => tree // empty Thicket, Ident, TypTree, ...
76
76
77
+ // Literals must be instrumented (at least) when returned by a def,
78
+ // otherwise `def d = "literal"` is not covered when called from a test.
79
+ // They can be left untouched when passed in a parameter of an Apply.
80
+ case tree : Literal =>
81
+ if ctx.property(IgnoreLiterals ).contains(true ) then
82
+ tree
83
+ else
84
+ instrument(tree)
85
+
77
86
// branches
78
87
case tree : If =>
79
88
cpy.If (tree)(
@@ -88,28 +97,42 @@ class InstrumentCoverage extends MacroTransform with IdentityDenotTransformer:
88
97
finalizer = instrument(transform(tree.finalizer), true )
89
98
)
90
99
100
+ // a.f(args)
101
+ case tree @ Apply (fun : Select , args) =>
102
+ // don't transform the first Select, but do transform `a.b` in `a.b.f(args)`
103
+ val transformedFun = cpy.Select (fun)(transform(fun.qualifier), fun.name)
104
+ if needsLift(tree) then
105
+ val transformed = cpy.Apply (tree)(transformedFun, args) // args will be transformed in instrumentLifted
106
+ instrumentLifted(transformed)(using ignoreLiteralsContext)
107
+ else
108
+ val transformed = cpy.Apply (tree)(transformedFun, transform(args))
109
+ instrument(transformed)(using ignoreLiteralsContext)
110
+
91
111
// f(args)
92
112
case tree : Apply =>
93
113
if needsLift(tree) then
94
- liftApply (tree)
114
+ instrumentLifted (tree)( using ignoreLiteralsContext) // see comment about Literals
95
115
else
96
- super .transform(tree)
116
+ instrument( super .transform(tree)( using ignoreLiteralsContext) )
97
117
98
118
// (f(x))[args]
99
- case tree @ TypeApply (fun : Apply , args) =>
119
+ case TypeApply (fun : Apply , args) =>
100
120
cpy.TypeApply (tree)(transform(fun), args)
101
121
102
122
// a.b
103
- case Select (qual, _) if (qual.symbol.exists && qual.symbol.is(JavaDefined )) =>
104
- // Java class can't be used as a value, we can't instrument the
105
- // qualifier ({<Probe>;System}.xyz() is not possible !) instrument it
106
- // as it is
107
- instrument(tree)
108
- case tree : Select =>
109
- if tree.qualifier.isInstanceOf [New ] then
123
+ case Select (qual, name) =>
124
+ if qual.symbol.exists && qual.symbol.is(JavaDefined ) then
125
+ // Java class can't be used as a value, we can't instrument the
126
+ // qualifier ({<Probe>;System}.xyz() is not possible !) instrument it
127
+ // as it is
110
128
instrument(tree)
111
129
else
112
- cpy.Select (tree)(transform(tree.qualifier), tree.name)
130
+ val transformed = cpy.Select (tree)(transform(qual), name)
131
+ if transformed.qualifier.isDef then
132
+ // instrument calls to methods without parameter list
133
+ instrument(transformed)
134
+ else
135
+ transformed
113
136
114
137
case tree : CaseDef => instrumentCaseDef(tree)
115
138
case tree : ValDef =>
@@ -120,6 +143,7 @@ class InstrumentCoverage extends MacroTransform with IdentityDenotTransformer:
120
143
// only transform the statements of the package
121
144
cpy.PackageDef (tree)(tree.pid, transform(tree.stats))
122
145
case tree : Assign =>
146
+ // only transform the rhs
123
147
cpy.Assign (tree)(tree.lhs, transform(tree.rhs))
124
148
case tree : Template =>
125
149
// Don't instrument the parents (extends) of a template since it
@@ -131,13 +155,9 @@ class InstrumentCoverage extends MacroTransform with IdentityDenotTransformer:
131
155
132
156
// For everything else just recurse and transform
133
157
case _ =>
134
- report.warning(
135
- " Unmatched: " + tree.getClass + " " + tree.symbol,
136
- tree.sourcePos
137
- )
138
158
super .transform(tree)
139
159
140
- def liftApply (tree : Apply )(using Context ) =
160
+ def instrumentLifted (tree : Apply )(using Context ) =
141
161
val buffer = mutable.ListBuffer [Tree ]()
142
162
// NOTE: that if only one arg needs to be lifted, we just lift everything
143
163
val lifted = LiftCoverage .liftForCoverage(buffer, tree)
@@ -170,10 +190,10 @@ class InstrumentCoverage extends MacroTransform with IdentityDenotTransformer:
170
190
id = id,
171
191
start = pos.start,
172
192
end = pos.end,
173
- line = ctx.source.offsetToLine( pos.point) ,
193
+ line = pos.line ,
174
194
desc = tree.source.content.slice(pos.start, pos.end).mkString,
175
195
symbolName = tree.symbol.name.toSimpleName.toString(),
176
- treeName = tree.getClass.getSimpleName,
196
+ treeName = tree.getClass.getSimpleName.nn ,
177
197
branch
178
198
)
179
199
coverage.addStatement(statement)
@@ -209,8 +229,7 @@ class InstrumentCoverage extends MacroTransform with IdentityDenotTransformer:
209
229
val fun = tree.fun
210
230
211
231
fun.isInstanceOf [Apply ] || // nested apply
212
- ! isBooleanOperator(fun) ||
213
- ! tree.args.isEmpty && ! tree.args.forall(LiftCoverage .noLift)
232
+ ! isBooleanOperator(fun) && ! tree.args.isEmpty && ! tree.args.forall(LiftCoverage .noLift)
214
233
215
234
object InstrumentCoverage :
216
235
val name : String = " instrumentCoverage"
0 commit comments