Skip to content

Commit d07040d

Browse files
committed
Fix coverage for macro suspension after InstrumentCoverage
1 parent 66ad0cd commit d07040d

File tree

9 files changed

+146
-21
lines changed

9 files changed

+146
-21
lines changed

compiler/src/dotty/tools/dotc/core/Contexts.scala

+6-4
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ import util.Store
4141
import plugins.*
4242
import java.util.concurrent.atomic.AtomicInteger
4343
import java.nio.file.InvalidPathException
44+
import dotty.tools.dotc.coverage.Coverage
4445

4546
object Contexts {
4647

@@ -982,10 +983,11 @@ object Contexts {
982983
/** Was best effort file used during compilation? */
983984
private[core] var usedBestEffortTasty = false
984985

985-
/** Counter to assign a unique id to each statement (for coverage) */
986-
private[dotc] var coverageStatementId = 0
987-
/** A variable denoting if the coverage file already written to (for coverage) */
988-
private[dotc] var coverageStartedWriting = false
986+
/** Stores all instrumented statements (for InstrumentCoverage).
987+
* We need this information to be persisted across different runs,
988+
* so it's stored here.
989+
*/
990+
private[dotc] var coverage: Coverage = Coverage()
989991

990992
// Types state
991993
/** A table for hash consing unique types */

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

+12
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,27 @@ package dotty.tools.dotc
22
package coverage
33

44
import scala.collection.mutable
5+
import java.nio.file.Path
56

67
/** Holds a list of statements to include in the coverage reports. */
78
class Coverage:
89
private val statementsById = new mutable.LongMap[Statement](256)
910

11+
private var statementId: Int = 0
12+
13+
def nextStatementId(): Int =
14+
statementId += 1
15+
statementId - 1
16+
17+
1018
def statements: Iterable[Statement] = statementsById.values
1119

1220
def addStatement(stmt: Statement): Unit = statementsById(stmt.id) = stmt
1321

22+
def removeStatementsFromFile(sourcePath: Path) =
23+
val removedIds = statements.filter(_.location.sourcePath == sourcePath).map(_.id.toLong)
24+
removedIds.foreach(statementsById.remove)
25+
1426

1527
/**
1628
* A statement that can be invoked, and thus counted as "covered" by code coverage tools.

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

+6-8
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
11
package dotty.tools.dotc
22
package coverage
33

4-
import java.nio.file.{Path, Paths, Files, StandardOpenOption}
4+
import java.nio.file.{Path, Paths, Files}
55
import java.io.Writer
66
import scala.language.unsafeNulls
77
import scala.collection.mutable.StringBuilder
8-
import dotty.tools.dotc.core.Contexts.Context
98

109
/**
1110
* Serializes scoverage data.
@@ -17,20 +16,20 @@ object Serializer:
1716
private val CoverageDataFormatVersion = "3.0"
1817

1918
/** Write out coverage data to the given data directory, using the default coverage filename */
20-
def serialize(coverage: Coverage, dataDir: String, sourceRoot: String)(using Context): Unit =
19+
def serialize(coverage: Coverage, dataDir: String, sourceRoot: String): Unit =
2120
serialize(coverage, Paths.get(dataDir, CoverageFileName).toAbsolutePath, Paths.get(sourceRoot).toAbsolutePath)
2221

2322
/** Write out coverage data to a file. */
24-
def serialize(coverage: Coverage, file: Path, sourceRoot: Path)(using Context): Unit =
25-
val writer = Files.newBufferedWriter(file, StandardOpenOption.CREATE, StandardOpenOption.APPEND)
23+
def serialize(coverage: Coverage, file: Path, sourceRoot: Path): Unit =
24+
val writer = Files.newBufferedWriter(file)
2625
try
2726
serialize(coverage, writer, sourceRoot)
2827
finally
2928
writer.close()
3029

3130
/** Write out coverage data (info about each statement that can be covered) to a writer.
3231
*/
33-
def serialize(coverage: Coverage, writer: Writer, sourceRoot: Path)(using ctx: Context): Unit =
32+
def serialize(coverage: Coverage, writer: Writer, sourceRoot: Path): Unit =
3433

3534
def getRelativePath(filePath: Path): String =
3635
// We need to normalize the path here because the relativizing paths containing '.' or '..' differs between Java versions
@@ -82,8 +81,7 @@ object Serializer:
8281
|\f
8382
|""".stripMargin)
8483

85-
if (!ctx.base.coverageStartedWriting) writeHeader(writer)
86-
ctx.base.coverageStartedWriting = true
84+
writeHeader(writer)
8785
coverage.statements.toSeq
8886
.sortBy(_.id)
8987
.foreach(stmt => writeStatement(stmt, writer))

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

+5-8
Original file line numberDiff line numberDiff line change
@@ -38,9 +38,6 @@ class InstrumentCoverage extends MacroTransform with IdentityDenotTransformer:
3838
override def isEnabled(using ctx: Context) =
3939
ctx.settings.coverageOutputDir.value.nonEmpty
4040

41-
// stores all instrumented statements
42-
private val coverage = Coverage()
43-
4441
private var coverageExcludeClasslikePatterns: List[Pattern] = Nil
4542
private var coverageExcludeFilePatterns: List[Pattern] = Nil
4643

@@ -51,7 +48,7 @@ class InstrumentCoverage extends MacroTransform with IdentityDenotTransformer:
5148
val dataDir = File(outputPath)
5249
val newlyCreated = dataDir.mkdirs()
5350

54-
if !newlyCreated && !ctx.base.coverageStartedWriting then
51+
if !newlyCreated then
5552
// If the directory existed before, let's clean it up.
5653
dataDir.listFiles.nn
5754
.filter(_.nn.getName.nn.startsWith("scoverage"))
@@ -61,9 +58,10 @@ class InstrumentCoverage extends MacroTransform with IdentityDenotTransformer:
6158
coverageExcludeClasslikePatterns = ctx.settings.coverageExcludeClasslikes.value.map(_.r.pattern)
6259
coverageExcludeFilePatterns = ctx.settings.coverageExcludeFiles.value.map(_.r.pattern)
6360

61+
ctx.base.coverage.removeStatementsFromFile(ctx.compilationUnit.source.file.absolute.jpath)
6462
super.run
6563

66-
Serializer.serialize(coverage, outputPath, ctx.settings.sourceroot.value)
64+
Serializer.serialize(ctx.base.coverage, outputPath, ctx.settings.sourceroot.value)
6765

6866
private def isClassIncluded(sym: Symbol)(using Context): Boolean =
6967
val fqn = sym.fullName.toText(ctx.printerFn(ctx)).show
@@ -107,8 +105,7 @@ class InstrumentCoverage extends MacroTransform with IdentityDenotTransformer:
107105
* @return the statement's id
108106
*/
109107
private def recordStatement(tree: Tree, pos: SourcePosition, branch: Boolean)(using ctx: Context): Int =
110-
val id = ctx.base.coverageStatementId
111-
ctx.base.coverageStatementId += 1
108+
val id = ctx.base.coverage.nextStatementId()
112109

113110
val sourceFile = pos.source
114111
val statement = Statement(
@@ -124,7 +121,7 @@ class InstrumentCoverage extends MacroTransform with IdentityDenotTransformer:
124121
treeName = tree.getClass.getSimpleName.nn,
125122
branch
126123
)
127-
coverage.addStatement(statement)
124+
ctx.base.coverage.addStatement(statement)
128125
id
129126

130127
/**

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ class CoverageTests:
6565
val (targetDir, expectFile, expectMeasurementFile) =
6666
if Files.isDirectory(path) then
6767
val dirName = path.getFileName().toString
68-
assert(!Files.walk(path).filter(scalaFile.matches(_)).toList.isEmpty, s"No scala files found in test directory: ${path}")
68+
assert(!Files.walk(path).filter(scalaFile.matches(_)).toArray.isEmpty, s"No scala files found in test directory: ${path}")
6969
val targetDir = computeCoverageInTmp(path, isDirectory = true, dir, run)
7070
(targetDir, path.resolve(s"test.scoverage.check"), path.resolve(s"test.measurement.check"))
7171
else
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package example
2+
3+
sealed trait Test
4+
5+
object Test {
6+
case object Foo extends Test
7+
8+
val visitorType = mkVisitorType[Test]
9+
10+
trait Visitor[A] {
11+
type V[a] = visitorType.Out[a]
12+
}
13+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
package example
2+
3+
val _ = Test.Foo
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package example
2+
3+
import scala.quoted.*
4+
5+
private def mkVisitorTypeImpl[T: Type](using q: Quotes): Expr[VisitorType[T]] =
6+
'{new VisitorType[T]{}}
7+
8+
transparent inline def mkVisitorType[T]: VisitorType[T] = ${ mkVisitorTypeImpl[T] }
9+
10+
trait VisitorType[T] {
11+
type Out[A]
12+
}
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+
macro-late-suspend/VisitorMacros.scala
23+
example
24+
VisitorMacros$package
25+
Object
26+
example.VisitorMacros$package
27+
mkVisitorTypeImpl
28+
124
29+
144
30+
6
31+
unpickleExprV2
32+
Apply
33+
false
34+
0
35+
false
36+
new VisitorType[T]{}
37+
38+
2
39+
macro-late-suspend/VisitorMacros.scala
40+
example
41+
VisitorMacros$package
42+
Object
43+
example.VisitorMacros$package
44+
mkVisitorTypeImpl
45+
40
46+
69
47+
5
48+
mkVisitorTypeImpl
49+
DefDef
50+
false
51+
0
52+
false
53+
private def mkVisitorTypeImpl
54+
55+
3
56+
macro-late-suspend/Test.scala
57+
example
58+
Test
59+
Object
60+
example.Test
61+
<init>
62+
102
63+
121
64+
8
65+
<init>
66+
Apply
67+
false
68+
0
69+
false
70+
mkVisitorType[Test]
71+
72+
4
73+
macro-late-suspend/UsesTest.scala
74+
example
75+
UsesTest$package
76+
Object
77+
example.UsesTest$package
78+
<init>
79+
22
80+
22
81+
3
82+
<none>
83+
Literal
84+
true
85+
0
86+
false
87+
88+

0 commit comments

Comments
 (0)