Skip to content

Commit 51f4004

Browse files
fix: ignore lazy definition generated from by-name implicits
Preamble: by-name implicit parameters used for shapeless magic and were introduced in SIP-31: 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[Foo]) 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 51f4004

File tree

3 files changed

+46
-4
lines changed

3 files changed

+46
-4
lines changed

Diff for: 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),

Diff for: plugin/src/test/scala/scoverage/PluginCoverageTest.scala

+27
Original file line numberDiff line numberDiff line change
@@ -373,4 +373,31 @@ class PluginCoverageTest extends FunSuite with MacroSupport {
373373
assert(!compiler.reporter.hasWarnings)
374374
compiler.assertNMeasuredStatements(11)
375375
}
376+
377+
test(
378+
"scoverage should ignore synthetic lazy definitions generated by compiler from by-name implicits"
379+
) {
380+
val compiler = ScoverageCompiler.noPositionValidation
381+
compiler.compileCodeSnippet(
382+
"""
383+
|object test {
384+
|
385+
| trait Foo {
386+
| def next: Foo
387+
| }
388+
|
389+
| object Foo {
390+
| implicit def foo(implicit rec: => Foo): Foo =
391+
| new Foo { def next = rec }
392+
| }
393+
|
394+
| val foo = implicitly[Foo]
395+
|
396+
|}
397+
|
398+
""".stripMargin
399+
)
400+
assert(!compiler.reporter.hasErrors)
401+
assert(!compiler.reporter.hasWarnings)
402+
}
376403
}

Diff for: 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)