Skip to content

Commit bad8b86

Browse files
TheElectronWillsmarter
authored andcommitted
Fix contexts in instrumentCoverage
Also add more tests and fix the remaining todo
1 parent cb66c7a commit bad8b86

24 files changed

+1328
-230
lines changed

compiler/src/dotty/tools/dotc/coverage/Location.scala

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package coverage
33

44
import ast.tpd._
55
import dotty.tools.dotc.core.Contexts.Context
6+
import dotty.tools.dotc.core.Flags.*
67

78
/** @param packageName
89
* the name of the encosing package
@@ -23,14 +24,20 @@ final case class Location(
2324
object Location:
2425
def apply(tree: Tree)(using ctx: Context): Location =
2526

26-
val packageName = ctx.owner.denot.enclosingPackageClass.name.toSimpleName.toString()
27-
val className = ctx.owner.denot.enclosingClass.name.toSimpleName.toString()
27+
val enclosingClass = ctx.owner.denot.enclosingClass
28+
val packageName = ctx.owner.denot.enclosingPackageClass.name.toSimpleName.toString
29+
val className = enclosingClass.name.toSimpleName.toString
30+
31+
val classType: String =
32+
if enclosingClass.is(Trait) then "Trait"
33+
else if enclosingClass.is(ModuleClass) then "Object"
34+
else "Class"
2835

2936
Location(
3037
packageName,
3138
className,
3239
s"$packageName.$className",
33-
"Class" /* TODO refine this further */,
40+
classType,
3441
ctx.owner.denot.enclosingMethod.name.toSimpleName.toString(),
3542
ctx.source.file.absolute.toString()
3643
)

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

Lines changed: 48 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -6,18 +6,16 @@ import java.util.concurrent.atomic.AtomicInteger
66

77
import collection.mutable
88
import core.Flags.JavaDefined
9-
import dotty.tools.dotc.core.Contexts.Context
10-
import dotty.tools.dotc.core.DenotTransformers.IdentityDenotTransformer
11-
import dotty.tools.dotc.coverage.Coverage
12-
import dotty.tools.dotc.coverage.Statement
13-
import dotty.tools.dotc.coverage.Serializer
14-
import dotty.tools.dotc.coverage.Location
15-
import dotty.tools.dotc.core.Symbols.defn
16-
import dotty.tools.dotc.core.Symbols.Symbol
17-
import dotty.tools.dotc.core.Decorators.toTermName
18-
import dotty.tools.dotc.util.{SourcePosition, Property}
19-
import dotty.tools.dotc.core.Constants.Constant
20-
import dotty.tools.dotc.typer.LiftCoverage
9+
import core.Contexts.{Context, ctx, inContext}
10+
import core.DenotTransformers.IdentityDenotTransformer
11+
import core.Symbols.{defn, Symbol}
12+
import core.Decorators.{toTermName, i}
13+
import core.Constants.Constant
14+
import typer.LiftCoverage
15+
import util.{SourcePosition, Property}
16+
import util.Spans.Span
17+
import coverage.*
18+
import dotty.tools.dotc.util.SourceFile
2119

2220
/** Implements code coverage by inserting calls to scala.runtime.Invoker
2321
* ("instruments" the source code).
@@ -105,7 +103,7 @@ class InstrumentCoverage extends MacroTransform with IdentityDenotTransformer:
105103
val transformed = cpy.Apply(tree)(transformedFun, args) // args will be transformed in instrumentLifted
106104
instrumentLifted(transformed)(using ignoreLiteralsContext)
107105
else
108-
val transformed = cpy.Apply(tree)(transformedFun, transform(args))
106+
val transformed = cpy.Apply(tree)(transformedFun, transform(args)(using ignoreLiteralsContext))
109107
instrument(transformed)(using ignoreLiteralsContext)
110108

111109
// f(args)
@@ -136,41 +134,47 @@ class InstrumentCoverage extends MacroTransform with IdentityDenotTransformer:
136134

137135
case tree: CaseDef => instrumentCaseDef(tree)
138136
case tree: ValDef =>
139-
// only transform the rhs
140-
cpy.ValDef(tree)(rhs = transform(tree.rhs))
137+
// only transform the rhs, in the local context
138+
val rhs = transform(tree.rhs)(using localCtx(tree))
139+
cpy.ValDef(tree)(rhs=rhs)
140+
141+
case tree: DefDef =>
142+
// only transform the params (for the default values) and the rhs
143+
val defCtx = localCtx(tree)
144+
val paramss = transformParamss(tree.paramss)(using defCtx)
145+
val rhs = transform(tree.rhs)(using defCtx)
146+
cpy.DefDef(tree)(tree.name, paramss, tree.tpt, rhs)
141147

142148
case tree: PackageDef =>
143149
// only transform the statements of the package
144-
cpy.PackageDef(tree)(tree.pid, transform(tree.stats))
150+
cpy.PackageDef(tree)(tree.pid, transform(tree.stats)(using localCtx(tree)))
145151
case tree: Assign =>
146152
// only transform the rhs
147153
cpy.Assign(tree)(tree.lhs, transform(tree.rhs))
148-
case tree: Template =>
149-
// Don't instrument the parents (extends) of a template since it
150-
// causes problems if the parent constructor takes parameters
151-
cpy.Template(tree)(
152-
constr = super.transformSub(tree.constr),
153-
body = transform(tree.body)
154-
)
155154

156155
// For everything else just recurse and transform
156+
// Special care for Templates: it's important to set the owner of the `stats`, like super.transform
157157
case _ =>
158158
super.transform(tree)
159159

160+
/** Lifts and instruments an application.
161+
* Note that if only one arg needs to be lifted, we just lift everything.
162+
*/
160163
def instrumentLifted(tree: Apply)(using Context) =
161-
val buffer = mutable.ListBuffer[Tree]()
162-
// NOTE: that if only one arg needs to be lifted, we just lift everything
163-
val lifted = LiftCoverage.liftForCoverage(buffer, tree)
164-
val instrumented = buffer.toList.map(transform)
165-
// We can now instrument the apply as it is with a custom position to point to the function
166-
Block(
167-
instrumented,
168-
instrument(
169-
lifted,
170-
tree.sourcePos,
171-
false
172-
)
173-
)
164+
// withSource is necessary to position inlined code properly
165+
inContext(ctx.withSource(tree.source)) {
166+
// lifting
167+
val buffer = mutable.ListBuffer[Tree]()
168+
val liftedApply = LiftCoverage.liftForCoverage(buffer, tree)
169+
170+
// instrumentation
171+
val instrumentedArgs = buffer.toList.map(transform)
172+
val instrumentedApply = instrument(liftedApply)
173+
Block(
174+
instrumentedArgs,
175+
instrumentedApply
176+
).withSpan(instrumentedApply.span)
177+
}
174178

175179
def instrumentCases(cases: List[CaseDef])(using Context): List[CaseDef] =
176180
cases.map(instrumentCaseDef)
@@ -197,16 +201,19 @@ class InstrumentCoverage extends MacroTransform with IdentityDenotTransformer:
197201
branch
198202
)
199203
coverage.addStatement(statement)
200-
Block(List(invokeCall(id)), tree)
204+
inContext(ctx.withSource(tree.source)) {
205+
val span = Span(pos.start, pos.end) // synthetic span
206+
Block(List(invokeCall(id, span)), tree).withSpan(span)
207+
}
201208
else
202209
tree
203210

204-
def invokeCall(id: Int)(using Context): Tree =
205-
ref(defn.InvokerModuleRef)
206-
.select("invoked".toTermName)
211+
def invokeCall(id: Int, span: Span)(using Context): Tree =
212+
ref(defn.InvokerModuleRef).withSpan(span)
213+
.select("invoked".toTermName).withSpan(span)
207214
.appliedToArgs(
208215
List(Literal(Constant(id)), Literal(Constant(outputPath)))
209-
)
216+
).withSpan(span)
210217

211218
/**
212219
* Checks if the apply needs a lift in the coverage phase.

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,7 @@ class Mixin extends MiniPhase with SymTransformer { thisPhase =>
214214
initFlags = stat.symbol.flags | PrivateLocal
215215
).installAfter(thisPhase)
216216
stat.symbol.enteredAfter(thisPhase)
217+
case _ =>
217218
}
218219
(scall, stats ::: inits, args)
219220
case _ =>

compiler/test/dotty/tools/dotc/coverage/CoverageTests.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ class CoverageTests:
5252
def computeCoverageInTmp(inputFiles: Seq[Path], sourceRoot: String): Path =
5353
val target = Files.createTempDirectory("coverage")
5454
val args = Array(
55+
"-Ycheck:instrumentCoverage",
5556
"-coverage-out",
5657
target.toString,
5758
"-coverage-sourceroot",
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package covtest
2+
3+
class C:
4+
def f(x: Int) = x
5+
def x = 1
6+
f(x)
7+
8+
object O:
9+
def g(y: Int) = y
10+
def y = 1
11+
g(y)
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
# Coverage data, format version: 3.0
2+
# Statement data:
3+
# - id
4+
# - source path
5+
# - package name
6+
# - class name
7+
# - class type (Class, Object or Trait)
8+
# - full class name
9+
# - method name
10+
# - start offset
11+
# - end offset
12+
# - line number
13+
# - symbol name
14+
# - tree name
15+
# - is branch
16+
# - invocations count
17+
# - is ignored
18+
# - description (can be multi-line)
19+
# ' ' sign
20+
# ------------------------------------------
21+
1
22+
tests/coverage/expect/Constructor.scala
23+
covtest
24+
C
25+
Class
26+
covtest.C
27+
x
28+
56
29+
57
30+
4
31+
<none>
32+
Literal
33+
false
34+
0
35+
false
36+
1
37+
38+
2
39+
tests/coverage/expect/Constructor.scala
40+
covtest
41+
C
42+
Class
43+
covtest.C
44+
<init>
45+
60
46+
64
47+
5
48+
f
49+
Apply
50+
false
51+
0
52+
false
53+
f(x)
54+
55+
3
56+
tests/coverage/expect/Constructor.scala
57+
covtest
58+
O$
59+
Object
60+
covtest.O$
61+
y
62+
106
63+
107
64+
9
65+
<none>
66+
Literal
67+
false
68+
0
69+
false
70+
1
71+
72+
4
73+
tests/coverage/expect/Constructor.scala
74+
covtest
75+
O$
76+
Object
77+
covtest.O$
78+
<init>
79+
110
80+
114
81+
10
82+
g
83+
Apply
84+
false
85+
0
86+
false
87+
g(y)
88+

0 commit comments

Comments
 (0)