Skip to content

Keep 'fun' and 'args' together when instrumenting TypeApply for coverage #15739

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jul 27, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 32 additions & 3 deletions compiler/src/dotty/tools/dotc/transform/InstrumentCoverage.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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) =>
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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
11 changes: 11 additions & 0 deletions tests/coverage/pos/PolymorphicExtensions.scala
Original file line number Diff line number Diff line change
@@ -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))
105 changes: 105 additions & 0 deletions tests/coverage/pos/PolymorphicExtensions.scoverage.check
Original file line number Diff line number Diff line change
@@ -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$
<init>
138
147
9
foo
Apply
false
0
false
"str".foo

3
PolymorphicExtensions.scala
covtest
PolyExt$
Object
covtest.PolyExt$
<init>
138
150
9
<none>
Apply
false
0
false
"str".foo(0)

4
PolymorphicExtensions.scala
covtest
PolyExt$
Object
covtest.PolyExt$
<init>
238
248
10
get
Apply
false
0
false
123.get(0)

10 changes: 10 additions & 0 deletions tests/coverage/pos/PolymorphicMethods.scala
Original file line number Diff line number Diff line change
@@ -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 = ()
105 changes: 105 additions & 0 deletions tests/coverage/pos/PolymorphicMethods.scoverage.check
Original file line number Diff line number Diff line change
@@ -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$
<init>
60
69
4
f
Apply
false
0
false
this.f(0)

2
PolymorphicMethods.scala
covtest
PolyMeth$
Object
covtest.PolyMeth$
<init>
147
158
6
<init>
Apply
false
0
false
C[String]()

3
PolymorphicMethods.scala
covtest
PolyMeth$
Object
covtest.PolyMeth$
<init>
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