Skip to content

Commit

Permalink
Keep fun and args together when instrumenting TypeApply, fixes #15705
Browse files Browse the repository at this point in the history
  • Loading branch information
TheElectronWill committed Jul 25, 2022
1 parent 0b16533 commit 9406a1b
Show file tree
Hide file tree
Showing 5 changed files with 263 additions and 3 deletions.
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

0 comments on commit 9406a1b

Please sign in to comment.