diff --git a/compiler/src/dotty/tools/dotc/coverage/Coverage.scala b/compiler/src/dotty/tools/dotc/coverage/Coverage.scala index 8ae249c1f5a3..e41bfcd5d09a 100644 --- a/compiler/src/dotty/tools/dotc/coverage/Coverage.scala +++ b/compiler/src/dotty/tools/dotc/coverage/Coverage.scala @@ -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, diff --git a/compiler/src/dotty/tools/dotc/coverage/Location.scala b/compiler/src/dotty/tools/dotc/coverage/Location.scala index faf1e97d0c01..c949010342c8 100644 --- a/compiler/src/dotty/tools/dotc/coverage/Location.scala +++ b/compiler/src/dotty/tools/dotc/coverage/Location.scala @@ -5,6 +5,7 @@ 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. * @@ -12,7 +13,7 @@ import java.nio.file.Path * @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( @@ -20,17 +21,19 @@ final case class Location( 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" @@ -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 ) diff --git a/compiler/src/dotty/tools/dotc/coverage/Serializer.scala b/compiler/src/dotty/tools/dotc/coverage/Serializer.scala index 23ab73f6d42e..4169f8b94130 100644 --- a/compiler/src/dotty/tools/dotc/coverage/Serializer.scala +++ b/compiler/src/dotty/tools/dotc/coverage/Serializer.scala @@ -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} diff --git a/compiler/src/dotty/tools/dotc/transform/InstrumentCoverage.scala b/compiler/src/dotty/tools/dotc/transform/InstrumentCoverage.scala index 6339a46eb681..d421db888d93 100644 --- a/compiler/src/dotty/tools/dotc/transform/InstrumentCoverage.scala +++ b/compiler/src/dotty/tools/dotc/transform/InstrumentCoverage.scala @@ -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). @@ -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 @@ -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) diff --git a/tests/coverage/pos/InlinedFromLib.scala b/tests/coverage/pos/InlinedFromLib.scala new file mode 100644 index 000000000000..1b05e11b7558 --- /dev/null +++ b/tests/coverage/pos/InlinedFromLib.scala @@ -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) diff --git a/tests/coverage/pos/InlinedFromLib.scoverage.check b/tests/coverage/pos/InlinedFromLib.scoverage.check new file mode 100644 index 000000000000..d7b2a42cd3b3 --- /dev/null +++ b/tests/coverage/pos/InlinedFromLib.scoverage.check @@ -0,0 +1,292 @@ +# Coverage data, format version: 3.0 +# Statement data: +# - id +# - source path +# - package name +# - class name +# - class type (Class, Object or Trait) +# - full class name +# - method name +# - start offset +# - end offset +# - line number +# - symbol name +# - tree name +# - is branch +# - invocations count +# - is ignored +# - description (can be multi-line) +# ' ' sign +# ------------------------------------------ +0 +InlinedFromLib.scala +covtest +InlinedFromLib$package$ +Object +covtest.InlinedFromLib$package$ +testInlined +169 +183 +6 +assertFailed +Apply +false +0 +false +assert(l == 1) + +1 +InlinedFromLib.scala +covtest +InlinedFromLib$package$ +Object +covtest.InlinedFromLib$package$ +testInlined +169 +183 +6 +assertFailed +Apply +true +0 +false +assert(l == 1) + +2 +InlinedFromLib.scala +covtest +InlinedFromLib$package$ +Object +covtest.InlinedFromLib$package$ +testInlined +169 +183 +6 + +Literal +true +0 +false +assert(l == 1) + +3 +InlinedFromLib.scala +covtest +InlinedFromLib$package$ +Object +covtest.InlinedFromLib$package$ +testInlined +198 +205 +7 +apply +Apply +false +0 +false +List(l) + +4 +InlinedFromLib.scala +covtest +InlinedFromLib$package$ +Object +covtest.InlinedFromLib$package$ +testInlined +198 +202 +7 +List +Ident +false +0 +false +List + +5 +InlinedFromLib.scala +covtest +InlinedFromLib$package$ +Object +covtest.InlinedFromLib$package$ +testInlined +198 +212 +7 +length +Select +false +0 +false +List(l).length + +6 +InlinedFromLib.scala +covtest +InlinedFromLib$package$ +Object +covtest.InlinedFromLib$package$ +testInlined +186 +213 +7 +assertFailed +Apply +false +0 +false +assert(l == List(l).length) + +7 +InlinedFromLib.scala +covtest +InlinedFromLib$package$ +Object +covtest.InlinedFromLib$package$ +testInlined +186 +213 +7 +assertFailed +Apply +true +0 +false +assert(l == List(l).length) + +8 +InlinedFromLib.scala +covtest +InlinedFromLib$package$ +Object +covtest.InlinedFromLib$package$ +testInlined +186 +213 +7 + +Literal +true +0 +false +assert(l == List(l).length) + +9 +InlinedFromLib.scala +covtest +InlinedFromLib$package$ +Object +covtest.InlinedFromLib$package$ +testInlined +223 +230 +8 +apply +Apply +false +0 +false +List(l) + +10 +InlinedFromLib.scala +covtest +InlinedFromLib$package$ +Object +covtest.InlinedFromLib$package$ +testInlined +223 +227 +8 +List +Ident +false +0 +false +List + +11 +InlinedFromLib.scala +covtest +InlinedFromLib$package$ +Object +covtest.InlinedFromLib$package$ +testInlined +223 +237 +8 +length +Select +false +0 +false +List(l).length + +12 +InlinedFromLib.scala +covtest +InlinedFromLib$package$ +Object +covtest.InlinedFromLib$package$ +testInlined +216 +243 +8 +assertFailed +Apply +false +0 +false +assert(List(l).length == 1) + +13 +InlinedFromLib.scala +covtest +InlinedFromLib$package$ +Object +covtest.InlinedFromLib$package$ +testInlined +216 +243 +8 +assertFailed +Apply +true +0 +false +assert(List(l).length == 1) + +14 +InlinedFromLib.scala +covtest +InlinedFromLib$package$ +Object +covtest.InlinedFromLib$package$ +testInlined +216 +243 +8 + +Literal +true +0 +false +assert(List(l).length == 1) + +15 +InlinedFromLib.scala +covtest +InlinedFromLib$package$ +Object +covtest.InlinedFromLib$package$ +testInlined +129 +144 +4 +testInlined +DefDef +false +0 +false +def testInlined +