Skip to content

Scoverage: instrument chained calls correctly#26166

Draft
anatoliykmetyuk wants to merge 3 commits into
scala:mainfrom
anatoliykmetyuk:fix/min12-freshcontext-addmode-ycheck
Draft

Scoverage: instrument chained calls correctly#26166
anatoliykmetyuk wants to merge 3 commits into
scala:mainfrom
anatoliykmetyuk:fix/min12-freshcontext-addmode-ycheck

Conversation

@anatoliykmetyuk
Copy link
Copy Markdown
Contributor

Fixes #26088

Root Issue

The issue in question is actually a manifestation of a larger issue:

addMode(foo())

Will correctly get instrumented by coverage to roughly:

val $1$: T = { Invoker.invoked("foo"); foo() }
Invoker.invoked("addMode")
addMode($1$)

But:

addMode(foo()).setA(...)

Will incorrectly get instrumented to roughly:

val $1$ = addMode(foo())
Invoker.invoked("setA")
$1.setA(...)

So, in case of chained calls foo(x).bar(y).baz(z), only the last call baz(z) will get proper instrumentation that respects argument evaluation order.

Reported Issue Impact

Consider code:

class C { def setA(a: Int): this.type = this }
def addMode(c: C): c.type = c
val x = addMode(identity(new C()).setA(???)).setA(???)

Its line:

val x = addMode(identity(new C()).setA(???)).setA(???)

Coverage instrumentation of that line:

val $2$: (?1 : C) =
  addMode(
    {
      val $1$: C = identity[C]({ Invoker.invoked(...); new C() })
      val a$1: Nothing = ???
      Invoker.invoked(...)
      $1$.setA(a$1)
    }
  )

Ycheck then fails because the result type is tied to the method parameter placeholder ?1, while the block result is tied to the lifted local $1$:

assertion failed: Type Mismatch
Found:    ($1$ : C)
Required: (?1 : C)

Solution

Ensure call chains such as foo(x).bar(y).baz(z) have all their calls lifted and instrumented properly to preserve evaluation order.

In the case of the issue in question, this will have the following effect:

val c$1: C =
  {
    val x$1: C = { Invoker.invoked(...); new C() }
    val $1$: C = identity[C](x$1)
    val a$1: Nothing = ???
    Invoker.invoked(...)
    $1$.setA(a$1)
  }
Invoker.invoked(...)
val $2$: (c$1 : C) = addMode(c$1)

c.type is preserved in addMode, and addMode(...) is traced the same way in addMode(...).setA(...) as it is in addMode(...).

How much have you relied on LLM-based tools in this contribution?

Moderately, for minimization, codebase analysis, tracing, test generation.

How was the solution tested?

New automated tests. Added tests/pos-custom-args/captures/coverage-freshcontext-addmode.scala and tests/coverage/run/chained-apply/test.scala. The coverage run test checks direct, single-chain, multi-chain, and receiver-dependent chained applications.

sbt "testCompilation --enable-coverage-phase coverage-freshcontext-addmode"
sbt "testCompilation coverage-freshcontext-addmode"
sbt "testCompilation --coverage"

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Scoverage lifting does not preserve this.type properly

1 participant