Skip to content

Commit c0af7e0

Browse files
Move coverage phase to before FirstTransform + some cleanup
1 parent eb2ea06 commit c0af7e0

File tree

6 files changed

+69
-81
lines changed

6 files changed

+69
-81
lines changed

Diff for: compiler/src/dotty/tools/dotc/Compiler.scala

+1-1
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,6 @@ class Compiler {
4646
List(new sjs.PrepJSInterop) :: // Additional checks and transformations for Scala.js (Scala.js only)
4747
List(new sbt.ExtractAPI) :: // Sends a representation of the API of classes to sbt via callbacks
4848
List(new SetRootTree) :: // Set the `rootTreeOrProvider` on class symbols
49-
List(new CoverageTransformMacro) :: // Perform instrumentation for coverage transform (if -coverage is present)
5049
Nil
5150

5251
/** Phases dealing with TASTY tree pickling and unpickling */
@@ -60,6 +59,7 @@ class Compiler {
6059

6160
/** Phases dealing with the transformation from pickled trees to backend trees */
6261
protected def transformPhases: List[List[Phase]] =
62+
List(new InstrumentCoverage) :: // Perform instrumentation for code coverage (if -coverage setting is set)
6363
List(new FirstTransform, // Some transformations to put trees into a canonical form
6464
new CheckReentrant, // Internal use only: Check that compiled program has no data races involving global vars
6565
new ElimPackagePrefixes, // Eliminate references to package prefixes in Select nodes

Diff for: compiler/src/dotty/tools/dotc/coverage/Coverage.scala

+9-8
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,12 @@ package coverage
33

44
import scala.collection.mutable
55

6-
class Coverage {
6+
class Coverage:
77
private val statementsById = mutable.Map[Int, Statement]()
88

9-
def statements = statementsById.values
9+
def statements: Iterable[Statement] = statementsById.values
1010

11-
def addStatement(stmt: Statement): Unit = statementsById.put(stmt.id, stmt)
12-
}
11+
def addStatement(stmt: Statement): Unit = statementsById(stmt.id) = stmt
1312

1413
case class Statement(
1514
source: String,
@@ -24,7 +23,9 @@ case class Statement(
2423
branch: Boolean,
2524
var count: Int = 0,
2625
ignored: Boolean = false
27-
) {
28-
def invoked(): Unit = count = count + 1
29-
def isInvoked = count > 0
30-
}
26+
):
27+
def invoked(): Unit =
28+
count += 1
29+
30+
def isInvoked: Boolean =
31+
count > 0

Diff for: compiler/src/dotty/tools/dotc/coverage/Location.scala

+2-4
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,8 @@ final case class Location(
2020
sourcePath: String
2121
)
2222

23-
object Location {
24-
def apply(tree: Tree)(using ctx: Context): Location = {
23+
object Location:
24+
def apply(tree: Tree)(using ctx: Context): Location =
2525

2626
val packageName = ctx.owner.denot.enclosingPackageClass.name.toSimpleName.toString()
2727
val className = ctx.owner.denot.enclosingClass.name.toSimpleName.toString()
@@ -34,5 +34,3 @@ object Location {
3434
ctx.owner.denot.enclosingMethod.name.toSimpleName.toString(),
3535
ctx.source.file.absolute.toString()
3636
)
37-
}
38-
}

Diff for: compiler/src/dotty/tools/dotc/coverage/Serializer.scala

+13-17
Original file line numberDiff line numberDiff line change
@@ -5,30 +5,29 @@ import java.io._
55

66
import scala.io.Source
77

8-
object Serializer {
8+
object Serializer:
99

10-
val coverageFileName = "scoverage.coverage"
11-
val coverageDataFormatVersion = "3.0"
12-
// Write out coverage data to the given data directory, using the default coverage filename
10+
private val coverageFileName = "scoverage.coverage"
11+
private val coverageDataFormatVersion = "3.0"
12+
13+
/** Write out coverage data to the given data directory, using the default coverage filename */
1314
def serialize(coverage: Coverage, dataDir: String, sourceRoot: String): Unit =
1415
serialize(coverage, coverageFile(dataDir), new File(sourceRoot))
1516

16-
// Write out coverage data to given file.
17-
def serialize(coverage: Coverage, file: File, sourceRoot: File): Unit = {
17+
/** Write out coverage data to given file. */
18+
def serialize(coverage: Coverage, file: File, sourceRoot: File): Unit =
1819
val writer = new BufferedWriter(new FileWriter(file))
1920
serialize(coverage, writer, sourceRoot)
2021
writer.close()
21-
}
2222

23-
def serialize(coverage: Coverage, writer: Writer, sourceRoot: File): Unit = {
23+
def serialize(coverage: Coverage, writer: Writer, sourceRoot: File): Unit =
2424

25-
def getRelativePath(filePath: String): String = {
25+
def getRelativePath(filePath: String): String =
2626
val base = sourceRoot.getCanonicalFile().toPath()
2727
val relPath = base.relativize(new File(filePath).getCanonicalFile().toPath())
2828
relPath.toString
29-
}
3029

31-
def writeHeader(writer: Writer): Unit = {
30+
def writeHeader(writer: Writer): Unit =
3231
writer.write(s"""# Coverage data, format version: $coverageDataFormatVersion
3332
|# Statement data:
3433
|# - id
@@ -50,8 +49,8 @@ object Serializer {
5049
|# '\f' sign
5150
|# ------------------------------------------
5251
|""".stripMargin)
53-
}
54-
def writeStatement(stmt: Statement, writer: Writer): Unit = {
52+
53+
def writeStatement(stmt: Statement, writer: Writer): Unit =
5554
writer.write(s"""${stmt.id}
5655
|${getRelativePath(stmt.location.sourcePath)}
5756
|${stmt.location.packageName}
@@ -70,14 +69,11 @@ object Serializer {
7069
|${stmt.desc}
7170
|\f
7271
|""".stripMargin)
73-
}
7472

7573
writeHeader(writer)
7674
coverage.statements.toVector
7775
.sortBy(_.id)
7876
.foreach(stmt => writeStatement(stmt, writer))
79-
}
8077

8178
def coverageFile(dataDir: File): File = coverageFile(dataDir.getAbsolutePath)
82-
def coverageFile(dataDir: String): File = new File(dataDir, coverageFileName)
83-
}
79+
def coverageFile(dataDir: String): File = File(dataDir, coverageFileName)

Diff for: compiler/src/dotty/tools/dotc/transform/CoverageTransformMacro.scala renamed to compiler/src/dotty/tools/dotc/transform/InstrumentCoverage.scala

+43-50
Original file line numberDiff line numberDiff line change
@@ -21,53 +21,54 @@ import dotty.tools.dotc.typer.LiftCoverage
2121

2222
import scala.quoted
2323

24-
/** Phase that implements code coverage, executed when the "-coverage
25-
* OUTPUT_PATH" is added to the compilation.
26-
*/
27-
class CoverageTransformMacro extends MacroTransform with IdentityDenotTransformer {
24+
/** Implements code coverage by inserting calls to scala.runtime.Invoker
25+
* ("instruments" the source code).
26+
* The result can then be consumed by the Scoverage tool.
27+
*/
28+
class InstrumentCoverage extends MacroTransform with IdentityDenotTransformer:
2829
import ast.tpd._
2930

30-
override def phaseName = "coverage"
31+
override def phaseName = InstrumentCoverage.name
3132

32-
// Atomic counter used for assignation of IDs to difference statements
33-
val statementId = new AtomicInteger(0)
33+
override def description = InstrumentCoverage.description
3434

35-
var outputPath = ""
35+
// Enabled by argument "-coverage OUTPUT_DIR"
36+
override def isEnabled(using ctx: Context) =
37+
ctx.settings.coverageOutputDir.value.nonEmpty
3638

37-
// Main class used to store all instrumented statements
38-
val coverage = new Coverage
39+
// Atomic counter used for assignation of IDs to difference statements
40+
private val statementId = AtomicInteger(0)
3941

40-
override def run(using ctx: Context): Unit = {
42+
private var outputPath = ""
4143

42-
if (ctx.settings.coverageOutputDir.value.nonEmpty) {
43-
outputPath = ctx.settings.coverageOutputDir.value
44+
// Main class used to store all instrumented statements
45+
private val coverage = Coverage()
4446

45-
// Ensure the dir exists
46-
val dataDir = new File(outputPath)
47-
val newlyCreated = dataDir.mkdirs()
47+
override def run(using ctx: Context): Unit =
48+
outputPath = ctx.settings.coverageOutputDir.value
4849

49-
if (!newlyCreated) {
50-
// If the directory existed before, let's clean it up.
51-
dataDir.listFiles
52-
.filter(_.getName.startsWith("scoverage"))
53-
.foreach(_.delete)
54-
}
50+
// Ensure the dir exists
51+
val dataDir = new File(outputPath)
52+
val newlyCreated = dataDir.mkdirs()
5553

56-
super.run
54+
if (!newlyCreated) {
55+
// If the directory existed before, let's clean it up.
56+
dataDir.listFiles
57+
.filter(_.getName.startsWith("scoverage"))
58+
.foreach(_.delete)
59+
}
5760

61+
super.run
5862

59-
Serializer.serialize(coverage, outputPath, ctx.settings.coverageSourceroot.value)
60-
}
61-
}
63+
Serializer.serialize(coverage, outputPath, ctx.settings.coverageSourceroot.value)
6264

63-
protected def newTransformer(using Context): Transformer =
64-
new CoverageTransormer
65+
override protected def newTransformer(using Context) = CoverageTransormer()
6566

66-
class CoverageTransormer extends Transformer {
67+
class CoverageTransormer extends Transformer:
6768
var instrumented = false
6869

69-
override def transform(tree: Tree)(using Context): Tree = {
70-
tree match {
70+
override def transform(tree: Tree)(using Context): Tree =
71+
tree match
7172
case tree: If =>
7273
cpy.If(tree)(
7374
cond = transform(tree.cond),
@@ -139,10 +140,8 @@ class CoverageTransformMacro extends MacroTransform with IdentityDenotTransforme
139140
tree.sourcePos
140141
)
141142
super.transform(tree)
142-
}
143-
}
144143

145-
def liftApply(tree: Apply)(using Context) = {
144+
def liftApply(tree: Apply)(using Context) =
146145
val buffer = mutable.ListBuffer[Tree]()
147146
// NOTE: that if only one arg needs to be lifted, we just lift everything
148147
val lifted = LiftCoverage.liftForCoverage(buffer, tree)
@@ -156,22 +155,18 @@ class CoverageTransformMacro extends MacroTransform with IdentityDenotTransforme
156155
false
157156
)
158157
)
159-
}
160158

161-
def instrumentCasees(cases: List[CaseDef])(using Context): List[CaseDef] = {
159+
def instrumentCasees(cases: List[CaseDef])(using Context): List[CaseDef] =
162160
cases.map(instrumentCaseDef)
163-
}
164161

165-
def instrumentCaseDef(tree: CaseDef)(using Context): CaseDef = {
162+
def instrumentCaseDef(tree: CaseDef)(using Context): CaseDef =
166163
cpy.CaseDef(tree)(tree.pat, transform(tree.guard), transform(tree.body))
167-
}
168164

169-
def instrument(tree: Tree, branch: Boolean = false)(using Context): Tree = {
165+
def instrument(tree: Tree, branch: Boolean = false)(using Context): Tree =
170166
instrument(tree, tree.sourcePos, branch)
171-
}
172167

173-
def instrument(tree: Tree, pos: SourcePosition, branch: Boolean)(using ctx: Context): Tree = {
174-
if (pos.exists && !pos.span.isZeroExtent && !tree.isType) {
168+
def instrument(tree: Tree, pos: SourcePosition, branch: Boolean)(using ctx: Context): Tree =
169+
if (pos.exists && !pos.span.isZeroExtent && !tree.isType)
175170
val id = statementId.incrementAndGet()
176171
val statement = new Statement(
177172
source = ctx.source.file.name,
@@ -187,18 +182,16 @@ class CoverageTransformMacro extends MacroTransform with IdentityDenotTransforme
187182
)
188183
coverage.addStatement(statement)
189184
Block(List(invokeCall(id)), tree)
190-
} else {
185+
else
191186
tree
192-
}
193-
}
194187

195-
def invokeCall(id: Int)(using Context): Tree = {
188+
def invokeCall(id: Int)(using Context): Tree =
196189
ref(defn.InvokerModuleRef)
197190
.select("invoked".toTermName)
198191
.appliedToArgs(
199192
List(Literal(Constant(id)), Literal(Constant(outputPath)))
200193
)
201-
}
202-
}
203194

204-
}
195+
object InstrumentCoverage:
196+
val name: String = "instrumentCoverage"
197+
val description: String = "instrument code for coverage cheking"

Diff for: compiler/src/dotty/tools/dotc/typer/EtaExpansion.scala

+1-1
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,7 @@ object LiftComplex extends LiftComplex
158158
/** Lift complex + lift the prefixes */
159159
object LiftCoverage extends LiftComplex {
160160

161-
var liftEverything = false
161+
private var liftEverything = false
162162

163163
/** Return true if the apply needs a lift in the coverage phase
164164
Return false if the args are empty, if one or more will be lifter by a

0 commit comments

Comments
 (0)