Skip to content

Fix "Missing argument results in java.lang.VerifyError"#24938

Closed
lihaoyi wants to merge 2 commits intoscala:mainfrom
lihaoyi:24201
Closed

Fix "Missing argument results in java.lang.VerifyError"#24938
lihaoyi wants to merge 2 commits intoscala:mainfrom
lihaoyi:24201

Conversation

@lihaoyi
Copy link
Contributor

@lihaoyi lihaoyi commented Jan 10, 2026

Attempts to fix #24201, vibe coded

● Root Cause

When an enum companion object extends a class with by-name parameters and uses named arguments out of order:

abstract class Foo[T](defaultValue: => T, arg1: Int = 1, arg2: Int = 2)

enum Baz:
case E1, E2

object Baz extends Foo[Baz](Baz.E1, arg2 = 2) // arg1 uses default, arg2 is named

The compiler desugars the named arguments into a Block with local defs:

object Baz extends {
def defaultValue$1 = Baz.E1 // local method for first arg
def arg1$1 = 1 // local method for default value
new Foo(defaultValue$1, arg1$1, 2)
}

The problem occurs in two stages:

  1. Baz.E1 wasn't being hoisted: The original refNeedsHoist didn't detect that Baz.E1 references the companion module being constructed. So Baz.E1 stayed as-is in defaultValue$1's body.
  2. Closure captures uninitialized this: When ElimByName wraps the by-name argument in a closure () => defaultValue$1, this closure needs to call defaultValue$1() which is an instance method on this. But this isn't initialized until after the super constructor runs → VerifyError.

Why The Fix Works

Fix Part 1: Detect self-references

tp.symbol == cls || tp.symbol == cls.companionModule || refNeedsHoist(tp.prefix)
Now Baz.E1 is correctly detected as needing hoisting because Baz (the prefix) refers to cls (the module being constructed). This creates a static method Baz$$superArg$1() that returns Baz.E1.

Fix Part 2: Inline hoisted calls

When processing the Block, if a local def's RHS was hoisted (e.g., defaultValue$1 = Baz$$superArg$1()), we track this in inlinedDefs. Then when creating the body of any subsequently hoisted method, we replace references to defaultValue$1 with Baz$$superArg$1() directly.

Result: The closure body becomes () => Baz$$superArg$1() instead of () => defaultValue$1. Since Baz$$superArg$1() is a static method, no this capture is needed → no VerifyError.

The static method hoisting technique already existed in HoistSuperArgs - that's the entire purpose of the phase.

@lihaoyi lihaoyi marked this pull request as draft January 10, 2026 13:16
@tanishiking
Copy link
Member

Oops, we were working on the same issue #24983
But it seems like we're trying to do the same thing: inline the method's body (which has a reference to this) into the hoisted static method 🎉

object Baz extends {
    def defaultValue$1: Baz = Baz.E1   // lifted by-name method (ExprType)
    val arg1$1: Int = Foo.<init>$default$2[Baz]
    new Foo[Baz](defaultValue$1, arg1$1, arg2 = 2)
} { ... }

object Baz extends {
    // defaultValue$1 removed
    ...
    new Foo[Baz](Baz$$superArg$1()(arg1$1), ...)
} { ...
    private <static> def Baz$$superArg$1()(arg1$1: Int): () ?=> Baz =
        () => Baz.E1 
}

instead of

object Baz extends {
    def defaultValue$1: Baz = Baz.E1
    ...
    new Foo[Baz](Baz$$superArg$1()(defaultValue$1, arg1$1), ...)
} { ...
    private <static> def Baz$$superArg$1()(defaultValue$1: => Baz, ...): () ?=> Baz =
        () => defaultValue$1
}

A difference is, while my change inlines synthetic methods with ExprType using

sym.is(Synthetic) && sym.is(Method) && sym.info.isInstanceOf[ExprType]

you're attempting to inline when the body has a reference to this (and handling ValDef as well). Your approach might be clearer and less hacky 🤔
Are you planning to finish this up? Or would you like me to borrow some ideas from it and complete it myself?

@lihaoyi
Copy link
Contributor Author

lihaoyi commented Jan 20, 2026

@tanishiking please go ahead and take ideas from this PR! This is largely Claude's work haha, I don't actually know what's happening (which is why I haven't manage to fix the CI failures...)

@SolalPirelli
Copy link
Contributor

given that CI fails + the new LLM policy, @tanishiking do you want to keep working on this or your PR to fix the issue?

@tanishiking
Copy link
Member

@SolalPirelli Sure, I was struggling to fix the same issue at #25157, gonna get back to this when I have time 👍

@tanishiking
Copy link
Member

closing for #25502 :)

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.

Missing argument results in java.lang.VerifyError

3 participants