Skip to content

Commit e0ae65e

Browse files
Keep fun and args together when instrumenting TypeApply, fixes #15705
1 parent 1ab427b commit e0ae65e

File tree

5 files changed

+264
-3
lines changed

5 files changed

+264
-3
lines changed

compiler/src/dotty/tools/dotc/transform/InstrumentCoverage.scala

+33-3
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import typer.LiftCoverage
1717
import util.SourcePosition
1818
import util.Spans.Span
1919
import localopt.StringInterpolatorOpt
20+
import dotty.tools.dotc.transform.InstrumentCoverage.InstrumentedBlock
2021

2122
/** Implements code coverage by inserting calls to scala.runtime.coverage.Invoker
2223
* ("instruments" the source code).
@@ -101,7 +102,24 @@ class InstrumentCoverage extends MacroTransform with IdentityDenotTransformer:
101102

102103
// (fun)[args]
103104
case TypeApply(fun, args) =>
104-
cpy.TypeApply(tree)(transform(fun), args)
105+
val tfun = transform(fun)
106+
tfun match
107+
case InstrumentedBlock(invokeCall, expr) =>
108+
// expr[T] shouldn't be transformed to
109+
// {invoked(...), expr}[T]
110+
//
111+
// but to
112+
// {invoked(...), expr[T]}
113+
//
114+
// This is especially important for trees like (expr[T])(args),
115+
// for which the wrong transformation crashes the compiler.
116+
// See tests/coverage/pos/PolymorphicExtensions.scala
117+
Block(
118+
invokeCall :: Nil,
119+
cpy.TypeApply(tree)(expr, args)
120+
)
121+
case _ =>
122+
cpy.TypeApply(tree)(tfun, args)
105123

106124
// a.b
107125
case Select(qual, name) =>
@@ -242,12 +260,12 @@ class InstrumentCoverage extends MacroTransform with IdentityDenotTransformer:
242260
val statementId = recordStatement(parent, pos, false)
243261
insertInvokeCall(body, pos, statementId)
244262

245-
/** Returns the tree, prepended by a call to Invoker.invoker */
263+
/** Returns the tree, prepended by a call to Invoker.invoked */
246264
private def insertInvokeCall(tree: Tree, pos: SourcePosition, statementId: Int)(using Context): Tree =
247265
val callSpan = syntheticSpan(pos)
248266
Block(invokeCall(statementId, callSpan) :: Nil, tree).withSpan(callSpan.union(tree.span))
249267

250-
/** Generates Invoked.invoked(id, DIR) */
268+
/** Generates Invoker.invoked(id, DIR) */
251269
private def invokeCall(id: Int, span: Span)(using Context): Tree =
252270
val outputPath = ctx.settings.coverageOutputDir.value
253271
ref(defn.InvokedMethodRef).withSpan(span)
@@ -353,3 +371,15 @@ class InstrumentCoverage extends MacroTransform with IdentityDenotTransformer:
353371
object InstrumentCoverage:
354372
val name: String = "instrumentCoverage"
355373
val description: String = "instrument code for coverage checking"
374+
375+
/** Extractor object for trees produced by `insertInvokeCall`. */
376+
object InstrumentedBlock:
377+
private def isInvokedCall(app: Apply)(using Context): Boolean =
378+
app.span.isSynthetic && app.symbol == defn.InvokedMethodRef.symbol
379+
380+
def unapply(t: Tree)(using Context): Option[(Apply, Tree)] =
381+
t match
382+
case Block((app: Apply) :: Nil, expr) if isInvokedCall(app) =>
383+
Some((app, expr))
384+
case _ =>
385+
None
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package covtest
2+
3+
object PolyExt:
4+
extension (s: String)
5+
def foo[A](x: A): A = x
6+
7+
extension [A](i: Int)
8+
def get(x: A): A = x
9+
10+
"str".foo(0) // ({foo("str")}[type])(0) i.e. Apply(TypeApply( Apply(foo, "str"), type ), List(0))
11+
123.get(0) // {(get[type])(123)}(0) i.e. Apply(Apply(TypeApply(...), List(123)), List(0))
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
# Coverage data, format version: 3.0
2+
# Statement data:
3+
# - id
4+
# - source path
5+
# - package name
6+
# - class name
7+
# - class type (Class, Object or Trait)
8+
# - full class name
9+
# - method name
10+
# - start offset
11+
# - end offset
12+
# - line number
13+
# - symbol name
14+
# - tree name
15+
# - is branch
16+
# - invocations count
17+
# - is ignored
18+
# - description (can be multi-line)
19+
# ' ' sign
20+
# ------------------------------------------
21+
0
22+
PolymorphicExtensions.scala
23+
covtest
24+
PolyExt$
25+
Object
26+
covtest.PolyExt$
27+
foo
28+
61
29+
68
30+
4
31+
foo
32+
DefDef
33+
false
34+
0
35+
false
36+
def foo
37+
38+
1
39+
PolymorphicExtensions.scala
40+
covtest
41+
PolyExt$
42+
Object
43+
covtest.PolyExt$
44+
get
45+
114
46+
121
47+
7
48+
get
49+
DefDef
50+
false
51+
0
52+
false
53+
def get
54+
55+
2
56+
PolymorphicExtensions.scala
57+
covtest
58+
PolyExt$
59+
Object
60+
covtest.PolyExt$
61+
<init>
62+
138
63+
147
64+
9
65+
foo
66+
Apply
67+
false
68+
0
69+
false
70+
"str".foo
71+
72+
3
73+
PolymorphicExtensions.scala
74+
covtest
75+
PolyExt$
76+
Object
77+
covtest.PolyExt$
78+
<init>
79+
138
80+
150
81+
9
82+
<none>
83+
Apply
84+
false
85+
0
86+
false
87+
"str".foo(0)
88+
89+
4
90+
PolymorphicExtensions.scala
91+
covtest
92+
PolyExt$
93+
Object
94+
covtest.PolyExt$
95+
<init>
96+
238
97+
248
98+
10
99+
get
100+
Apply
101+
false
102+
0
103+
false
104+
123.get(0)
105+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package covtest
2+
3+
object PolyMeth:
4+
def f[A](x: A): A = x
5+
this.f(0) // (this.f[type])(0) i.e. Apply(TypeApply(Select(this,f), type), List(0))
6+
7+
C[String]().f("str", 0)
8+
9+
class C[T1]:
10+
def f[T2](p1: T1, p2: T2): Unit = ()
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
# Coverage data, format version: 3.0
2+
# Statement data:
3+
# - id
4+
# - source path
5+
# - package name
6+
# - class name
7+
# - class type (Class, Object or Trait)
8+
# - full class name
9+
# - method name
10+
# - start offset
11+
# - end offset
12+
# - line number
13+
# - symbol name
14+
# - tree name
15+
# - is branch
16+
# - invocations count
17+
# - is ignored
18+
# - description (can be multi-line)
19+
# ' ' sign
20+
# ------------------------------------------
21+
0
22+
PolymorphicMethods.scala
23+
covtest
24+
PolyMeth$
25+
Object
26+
covtest.PolyMeth$
27+
f
28+
36
29+
41
30+
3
31+
f
32+
DefDef
33+
false
34+
0
35+
false
36+
def f
37+
38+
1
39+
PolymorphicMethods.scala
40+
covtest
41+
PolyMeth$
42+
Object
43+
covtest.PolyMeth$
44+
<init>
45+
60
46+
69
47+
4
48+
f
49+
Apply
50+
false
51+
0
52+
false
53+
this.f(0)
54+
55+
2
56+
PolymorphicMethods.scala
57+
covtest
58+
PolyMeth$
59+
Object
60+
covtest.PolyMeth$
61+
<init>
62+
147
63+
158
64+
6
65+
<init>
66+
Apply
67+
false
68+
0
69+
false
70+
C[String]()
71+
72+
3
73+
PolymorphicMethods.scala
74+
covtest
75+
PolyMeth$
76+
Object
77+
covtest.PolyMeth$
78+
<init>
79+
147
80+
170
81+
6
82+
f
83+
Apply
84+
false
85+
0
86+
false
87+
C[String]().f("str", 0)
88+
89+
4
90+
PolymorphicMethods.scala
91+
covtest
92+
C
93+
Class
94+
covtest.C
95+
f
96+
187
97+
192
98+
9
99+
f
100+
DefDef
101+
false
102+
0
103+
false
104+
def f
105+

0 commit comments

Comments
 (0)