diff --git a/compiler/src/dotty/tools/dotc/transform/InstrumentCoverage.scala b/compiler/src/dotty/tools/dotc/transform/InstrumentCoverage.scala index 28ac9b890fd3..1cc1340ad7c7 100644 --- a/compiler/src/dotty/tools/dotc/transform/InstrumentCoverage.scala +++ b/compiler/src/dotty/tools/dotc/transform/InstrumentCoverage.scala @@ -101,7 +101,24 @@ class InstrumentCoverage extends MacroTransform with IdentityDenotTransformer: // (fun)[args] case TypeApply(fun, args) => - cpy.TypeApply(tree)(transform(fun), args) + val tfun = transform(fun) + tfun match + case InstrumentCoverage.InstrumentedBlock(invokeCall, expr) => + // expr[T] shouldn't be transformed to + // {invoked(...), expr}[T] + // + // but to + // {invoked(...), expr[T]} + // + // This is especially important for trees like (expr[T])(args), + // for which the wrong transformation crashes the compiler. + // See tests/coverage/pos/PolymorphicExtensions.scala + Block( + invokeCall :: Nil, + cpy.TypeApply(tree)(expr, args) + ) + case _ => + cpy.TypeApply(tree)(tfun, args) // a.b case Select(qual, name) => @@ -242,12 +259,12 @@ class InstrumentCoverage extends MacroTransform with IdentityDenotTransformer: val statementId = recordStatement(parent, pos, false) insertInvokeCall(body, pos, statementId) - /** Returns the tree, prepended by a call to Invoker.invoker */ + /** Returns the tree, prepended by a call to Invoker.invoked */ private def insertInvokeCall(tree: Tree, pos: SourcePosition, statementId: Int)(using Context): Tree = val callSpan = syntheticSpan(pos) Block(invokeCall(statementId, callSpan) :: Nil, tree).withSpan(callSpan.union(tree.span)) - /** Generates Invoked.invoked(id, DIR) */ + /** Generates Invoker.invoked(id, DIR) */ private def invokeCall(id: Int, span: Span)(using Context): Tree = val outputPath = ctx.settings.coverageOutputDir.value ref(defn.InvokedMethodRef).withSpan(span) @@ -353,3 +370,15 @@ class InstrumentCoverage extends MacroTransform with IdentityDenotTransformer: object InstrumentCoverage: val name: String = "instrumentCoverage" val description: String = "instrument code for coverage checking" + + /** Extractor object for trees produced by `insertInvokeCall`. */ + object InstrumentedBlock: + private def isInvokedCall(app: Apply)(using Context): Boolean = + app.span.isSynthetic && app.symbol == defn.InvokedMethodRef.symbol + + def unapply(t: Tree)(using Context): Option[(Apply, Tree)] = + t match + case Block((app: Apply) :: Nil, expr) if isInvokedCall(app) => + Some((app, expr)) + case _ => + None diff --git a/tests/coverage/pos/PolymorphicExtensions.scala b/tests/coverage/pos/PolymorphicExtensions.scala new file mode 100644 index 000000000000..a2bafc451606 --- /dev/null +++ b/tests/coverage/pos/PolymorphicExtensions.scala @@ -0,0 +1,11 @@ +package covtest + +object PolyExt: + extension (s: String) + def foo[A](x: A): A = x + + extension [A](i: Int) + def get(x: A): A = x + + "str".foo(0) // ({foo("str")}[type])(0) i.e. Apply(TypeApply( Apply(foo, "str"), type ), List(0)) + 123.get(0) // {(get[type])(123)}(0) i.e. Apply(Apply(TypeApply(...), List(123)), List(0)) diff --git a/tests/coverage/pos/PolymorphicExtensions.scoverage.check b/tests/coverage/pos/PolymorphicExtensions.scoverage.check new file mode 100644 index 000000000000..e244b529ce0a --- /dev/null +++ b/tests/coverage/pos/PolymorphicExtensions.scoverage.check @@ -0,0 +1,105 @@ +# 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 +PolymorphicExtensions.scala +covtest +PolyExt$ +Object +covtest.PolyExt$ +foo +61 +68 +4 +foo +DefDef +false +0 +false +def foo + +1 +PolymorphicExtensions.scala +covtest +PolyExt$ +Object +covtest.PolyExt$ +get +114 +121 +7 +get +DefDef +false +0 +false +def get + +2 +PolymorphicExtensions.scala +covtest +PolyExt$ +Object +covtest.PolyExt$ + +138 +147 +9 +foo +Apply +false +0 +false +"str".foo + +3 +PolymorphicExtensions.scala +covtest +PolyExt$ +Object +covtest.PolyExt$ + +138 +150 +9 + +Apply +false +0 +false +"str".foo(0) + +4 +PolymorphicExtensions.scala +covtest +PolyExt$ +Object +covtest.PolyExt$ + +238 +248 +10 +get +Apply +false +0 +false +123.get(0) + diff --git a/tests/coverage/pos/PolymorphicMethods.scala b/tests/coverage/pos/PolymorphicMethods.scala new file mode 100644 index 000000000000..a0cbf102bd5c --- /dev/null +++ b/tests/coverage/pos/PolymorphicMethods.scala @@ -0,0 +1,10 @@ +package covtest + +object PolyMeth: + def f[A](x: A): A = x + this.f(0) // (this.f[type])(0) i.e. Apply(TypeApply(Select(this,f), type), List(0)) + + C[String]().f("str", 0) + +class C[T1]: + def f[T2](p1: T1, p2: T2): Unit = () diff --git a/tests/coverage/pos/PolymorphicMethods.scoverage.check b/tests/coverage/pos/PolymorphicMethods.scoverage.check new file mode 100644 index 000000000000..77615cc6f0bc --- /dev/null +++ b/tests/coverage/pos/PolymorphicMethods.scoverage.check @@ -0,0 +1,105 @@ +# 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 +PolymorphicMethods.scala +covtest +PolyMeth$ +Object +covtest.PolyMeth$ +f +36 +41 +3 +f +DefDef +false +0 +false +def f + +1 +PolymorphicMethods.scala +covtest +PolyMeth$ +Object +covtest.PolyMeth$ + +60 +69 +4 +f +Apply +false +0 +false +this.f(0) + +2 +PolymorphicMethods.scala +covtest +PolyMeth$ +Object +covtest.PolyMeth$ + +147 +158 +6 + +Apply +false +0 +false +C[String]() + +3 +PolymorphicMethods.scala +covtest +PolyMeth$ +Object +covtest.PolyMeth$ + +147 +170 +6 +f +Apply +false +0 +false +C[String]().f("str", 0) + +4 +PolymorphicMethods.scala +covtest +C +Class +covtest.C +f +187 +192 +9 +f +DefDef +false +0 +false +def f +