Skip to content

Commit

Permalink
Coverage: reposition inlined trees to avoid unreachable sources, fix s…
Browse files Browse the repository at this point in the history
  • Loading branch information
TheElectronWill committed Oct 23, 2022
1 parent ce777c0 commit 7f7904c
Show file tree
Hide file tree
Showing 6 changed files with 327 additions and 13 deletions.
1 change: 0 additions & 1 deletion compiler/src/dotty/tools/dotc/coverage/Coverage.scala
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ class Coverage:

/** A statement that can be invoked, and thus counted as "covered" by code coverage tools. */
case class Statement(
source: String,
location: Location,
id: Int,
start: Int,
Expand Down
17 changes: 10 additions & 7 deletions compiler/src/dotty/tools/dotc/coverage/Location.scala
Original file line number Diff line number Diff line change
Expand Up @@ -5,32 +5,35 @@ import ast.tpd._
import dotty.tools.dotc.core.Contexts.Context
import dotty.tools.dotc.core.Flags.*
import java.nio.file.Path
import dotty.tools.dotc.util.SourceFile

/** Information about the location of a coverable piece of code.
*
* @param packageName name of the enclosing package
* @param className name of the closest enclosing class
* @param fullClassName fully qualified name of the closest enclosing class
* @param classType "type" of the closest enclosing class: Class, Trait or Object
* @param method name of the closest enclosing method
* @param method name of the closest enclosing method
* @param sourcePath absolute path of the source file
*/
final case class Location(
packageName: String,
className: String,
fullClassName: String,
classType: String,
method: String,
methodName: String,
sourcePath: Path
)

object Location:
/** Extracts the location info of a Tree. */
def apply(tree: Tree)(using ctx: Context): Location =
def apply(tree: Tree, source: SourceFile)(using ctx: Context): Location =

val enclosingClass = ctx.owner.denot.enclosingClass
val packageName = ctx.owner.denot.enclosingPackageClass.name.toSimpleName.toString
val ownerDenot = ctx.owner.denot
val enclosingClass = ownerDenot.enclosingClass
val packageName = ownerDenot.enclosingPackageClass.name.toSimpleName.toString
val className = enclosingClass.name.toSimpleName.toString
val methodName = ownerDenot.enclosingMethod.name.toSimpleName.toString

val classType: String =
if enclosingClass.is(Trait) then "Trait"
Expand All @@ -42,6 +45,6 @@ object Location:
className,
s"$packageName.$className",
classType,
ctx.owner.denot.enclosingMethod.name.toSimpleName.toString(),
ctx.source.file.absolute.jpath
methodName,
source.file.absolute.jpath
)
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/coverage/Serializer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ object Serializer:
|${stmt.location.className}
|${stmt.location.classType}
|${stmt.location.fullClassName}
|${stmt.location.method}
|${stmt.location.methodName}
|${stmt.start}
|${stmt.end}
|${stmt.line}
Expand Down
19 changes: 15 additions & 4 deletions compiler/src/dotty/tools/dotc/transform/InstrumentCoverage.scala
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,10 @@ import core.NameOps.isContextFunction
import core.Types.*
import coverage.*
import typer.LiftCoverage
import util.SourcePosition
import util.{SourcePosition, SourceFile}
import util.Spans.Span
import localopt.StringInterpolatorOpt
import inlines.Inlines

/** Implements code coverage by inserting calls to scala.runtime.coverage.Invoker
* ("instruments" the source code).
Expand Down Expand Up @@ -87,14 +88,15 @@ class InstrumentCoverage extends MacroTransform with IdentityDenotTransformer:
private def recordStatement(tree: Tree, pos: SourcePosition, branch: Boolean)(using ctx: Context): Int =
val id = statementId
statementId += 1

val sourceFile = pos.source
val statement = Statement(
source = ctx.source.file.name,
location = Location(tree),
location = Location(tree, sourceFile),
id = id,
start = pos.start,
end = pos.end,
line = pos.line,
desc = tree.source.content.slice(pos.start, pos.end).mkString,
desc = sourceFile.content.slice(pos.start, pos.end).mkString,
symbolName = tree.symbol.name.toSimpleName.toString,
treeName = tree.getClass.getSimpleName.nn,
branch
Expand Down Expand Up @@ -290,6 +292,15 @@ class InstrumentCoverage extends MacroTransform with IdentityDenotTransformer:
transformStats(tree.body, tree.symbol)
)

case tree: Inlined =>
// Ideally, tree.call would provide precise information about the inlined call,
// and we would use this information for the coverage report.
// But PostTyper simplifies tree.call, so we can't report the actual method that was inlined.
// In any case, the subtrees need to be repositioned right now, otherwise the
// coverage statement will point to a potentially unreachable source file.
val dropped = Inlines.dropInlined(tree) // drop and reposition
transform(dropped) // transform the content of the Inlined

// For everything else just recurse and transform
case _ =>
super.transform(tree)
Expand Down
9 changes: 9 additions & 0 deletions tests/coverage/pos/InlinedFromLib.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package covtest

// assert is a `transparent inline` in Predef,
// but its source path should not appear in the coverage report.
def testInlined(): Unit =
val l = 1
assert(l == 1)
assert(l == List(l).length)
assert(List(l).length == 1)
Loading

0 comments on commit 7f7904c

Please sign in to comment.