Skip to content

Commit 8dd2df4

Browse files
fix: ignore lazy definition generated from by-name implicits (#536)
Preamble: by-name implicit parameters used for shapeless magic and were introduced in SIP-31 (available from 2.13+): https://docs.scala-lang.org/sips/byname-implicits.html Using scoverage with such code may result in the following warning from the plugin: [warn] Could not instrument [Select/value rec$1]. Originally discovered by using scoverage with the code that derives typeclass instances using kittens library: case class Bar() case class Foo(bars: List[Bar]) object Foo { implicit eq: cats.Eq[Foo] = cats.derived.semiauto.eq } Solution: Ignore LazyDefns$1.rec$1 from instrumentation like other synthetic code Other changes: It was necessary to disable position validation phase for the test case that reproduces the issue because the code generated by scala compiler fails such validation. It could be also checked outside testing environment by manually setting -Yvalidate-pos:typer option to the compiler. Not sure whether it's expected compiler behavior or not
1 parent 6c3cd69 commit 8dd2df4

File tree

4 files changed

+60
-5
lines changed

4 files changed

+60
-5
lines changed

build.sbt

+8-1
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,14 @@ lazy val plugin =
151151
sharedSettings
152152
)
153153
.settings(
154-
Test / unmanagedSourceDirectories += (Test / sourceDirectory).value / "scala-2.12+"
154+
Test / unmanagedSourceDirectories += (Test / sourceDirectory).value / "scala-2.12+",
155+
Test / unmanagedSourceDirectories ++= {
156+
val sourceDir = (Test / sourceDirectory).value
157+
CrossVersion.partialVersion(scalaVersion.value) match {
158+
case Some((2, n)) if n >= 13 => Seq(sourceDir / "scala-2.13+")
159+
case _ => Seq.empty
160+
}
161+
}
155162
)
156163
.dependsOn(domain, reporter % "test->compile", serializer, buildInfo % Test)
157164

plugin/src/main/scala/scoverage/ScoveragePlugin.scala

+8
Original file line numberDiff line numberDiff line change
@@ -790,6 +790,14 @@ class ScoverageInstrumentationComponent(
790790
*/
791791
case s: Select if s.symbol.isLazy => tree
792792

793+
// Generated by compiler for lazy definitions involving
794+
// by-name implicit parameters. More on that here:
795+
// https://docs.scala-lang.org/sips/byname-implicits.html
796+
//
797+
// final <synthetic> val lazyDefns$1: LazyDefns$1 = new LazyDefns$1();
798+
// lazyDefns$1.rec$1()
799+
case s: Select if s.symbol.isSynthetic => tree
800+
793801
case s: Select =>
794802
instrument(
795803
treeCopy.Select(s, traverseApplication(s.qualifier), s.name),
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package scoverage
2+
3+
import munit.FunSuite
4+
5+
class Scala213PluginCoverageTest extends FunSuite with MacroSupport {
6+
7+
test(
8+
"scoverage should ignore synthetic lazy definitions generated by compiler from by-name implicits"
9+
) {
10+
val compiler = ScoverageCompiler.noPositionValidation
11+
compiler.compileCodeSnippet(
12+
"""
13+
|object test {
14+
|
15+
| trait Foo {
16+
| def next: Foo
17+
| }
18+
|
19+
| object Foo {
20+
| implicit def foo(implicit rec: => Foo): Foo =
21+
| new Foo { def next = rec }
22+
| }
23+
|
24+
| val foo = implicitly[Foo]
25+
|
26+
|}
27+
|
28+
""".stripMargin
29+
)
30+
assert(!compiler.reporter.hasErrors)
31+
assert(!compiler.reporter.hasWarnings)
32+
}
33+
}

plugin/src/test/scala/scoverage/ScoverageCompiler.scala

+11-4
Original file line numberDiff line numberDiff line change
@@ -57,12 +57,17 @@ private[scoverage] object ScoverageCompiler {
5757

5858
def default: ScoverageCompiler = {
5959
val reporter = new scala.tools.nsc.reporters.ConsoleReporter(settings)
60-
new ScoverageCompiler(settings, reporter)
60+
new ScoverageCompiler(settings, reporter, validatePositions = true)
61+
}
62+
63+
def noPositionValidation: ScoverageCompiler = {
64+
val reporter = new scala.tools.nsc.reporters.ConsoleReporter(settings)
65+
new ScoverageCompiler(settings, reporter, validatePositions = false)
6166
}
6267

6368
def defaultJS: ScoverageCompiler = {
6469
val reporter = new scala.tools.nsc.reporters.ConsoleReporter(jsSettings)
65-
new ScoverageCompiler(jsSettings, reporter)
70+
new ScoverageCompiler(jsSettings, reporter, validatePositions = true)
6671
}
6772

6873
def locationCompiler: LocationCompiler = {
@@ -152,7 +157,8 @@ private[scoverage] object ScoverageCompiler {
152157

153158
class ScoverageCompiler(
154159
settings: scala.tools.nsc.Settings,
155-
rep: scala.tools.nsc.reporters.Reporter
160+
rep: scala.tools.nsc.reporters.Reporter,
161+
validatePositions: Boolean
156162
) extends scala.tools.nsc.Global(settings, rep) {
157163

158164
def addToClassPath(file: File): Unit = {
@@ -268,7 +274,8 @@ class ScoverageCompiler(
268274

269275
override def computeInternalPhases(): Unit = {
270276
super.computeInternalPhases()
271-
addToPhasesSet(validator, "scoverage validator")
277+
if (validatePositions)
278+
addToPhasesSet(validator, "scoverage validator")
272279
addToPhasesSet(
273280
instrumentationComponent,
274281
"scoverage instrumentationComponent"

0 commit comments

Comments
 (0)