Compiler version
3.8.3 (also verified on 3.7.3 and 3.6.4)
Minimized code
import scala.language.experimental.erasedDefinitions
class Proof
erased given Proof = Proof()
def mixed: (erased Proof) ?=> Int ?=> String =
s"value: ${summon[Int]}"
@main def test(): Unit =
given Int = 99
println(mixed)
Output (click arrow to expand)
Details
Exception in thread "main" java.lang.VerifyError: Bad local variable type
Exception Details:
Location:
bug003$package$.mixed(I)Ljava/lang/String; @0: iload_2
Reason:
Type top (current frame, locals[2]) is not assignable to integer
Current Frame:
bci: @0
flags: { }
locals: { 'bug003$package$', integer }
stack: { }
Bytecode:
0000000: 1cba 002e 0000 b0
at test.main(bug003.scala:10)
Expectation
Should print value: 99.
The method signature is mixed(I)String — one int parameter. But the bytecode uses iload_2 (slot 2) when only slots 0 (this) and 1 (Int) exist. The erased Proof parameter was removed from the signature but slot numbering in the body was not adjusted.
Key observation: order matters
// CRASHES: erased first, then non-erased
def mixed: (erased Proof) ?=> Int ?=> String = s"${summon[Int]}"
// WORKS: non-erased first, then erased (erased in last position)
def reversed: Int ?=> (erased Proof) ?=> String = s"${summon[Int]}"
All these also crash:
def varA: (erased P1) ?=> (erased P2) ?=> Int ?=> String = ... // 2 erased before non-erased
def varB: (erased P1) ?=> Int ?=> (erased P2) ?=> String = ... // erased, non-erased, erased
def varC: Int ?=> (erased P1) ?=> String ?=> String = ... // non-erased, erased, non-erased
The bug triggers whenever an erased context function param appears BEFORE any subsequent non-erased param. Only erased in the very last position is safe.
Notes
Compiler version
3.8.3 (also verified on 3.7.3 and 3.6.4)
Minimized code
Output (click arrow to expand)
Details
Expectation
Should print
value: 99.The method signature is
mixed(I)String— one int parameter. But the bytecode usesiload_2(slot 2) when only slots 0 (this) and 1 (Int) exist. The erasedProofparameter was removed from the signature but slot numbering in the body was not adjusted.Key observation: order matters
All these also crash:
The bug triggers whenever an erased context function param appears BEFORE any subsequent non-erased param. Only erased in the very last position is safe.
Notes
-Ycheck:allpasses ALL compiler phases (includinggenBCode) without detecting the issue.inlinemethods avoid the bug (erased removed during inlining, before bytecode gen).java.lang.VerifyErrorwith erased value #16943 (erased VerifyError for regular methods, fixed in Add regression test #17047) — but that fix didn't cover chained context function types.Also found via my tools: Recursive macro splice causes StackOverflowError during inlining phase #25724, Duplicate enum name (simple + parametric) causes assertion failure #25723, Parser crash (
AssertionError) when line comment hides then in for-generator inside new argument #25248, Crash:AssertionErrorinTypeOps.dominatorswith complex context bounds and quoted expression #25246, Crash intpd.singletononPreviousErrorTypeduring error recovery (inline match + quoted pattern with unresolved import) #25244