Skip to content

Fix #24824 FunProtoTyped preservation in wildApprox and map to avoid compiler crash#25662

Open
dancewithheart wants to merge 3 commits intoscala:mainfrom
dancewithheart:fix/24824-funproto-typed-preservation
Open

Fix #24824 FunProtoTyped preservation in wildApprox and map to avoid compiler crash#25662
dancewithheart wants to merge 3 commits intoscala:mainfrom
dancewithheart:fix/24824-funproto-typed-preservation

Conversation

@dancewithheart
Copy link
Copy Markdown
Contributor

@dancewithheart dancewithheart commented Mar 31, 2026

Fixes #24824: preserve FunProtoTyped in wildApprox and FunProtoTyped.map to avoid assert from collectParts that leading to a crash

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

moderately: I feed LLM with relevant issues, PRs; reiterated over proposed solutions,
then switched to work done by @Alex1005a as he had more mature solution

How was the solution tested?

Tested locally added reproduction case:

sbt --client "testCompilation 24824"

and nearby cases (that failed during earlier iterations):

sbt --client "testCompilation i12456"
sbt --client "testCompilation i24553"
sbt --client "testCompilation i9328"
sbt --client "testCompilation i16116"
sbt --client "testCompilation i9299"

Not all tests are passing locally:

sbt --error "testOnly dotty.tools.dotc.CompilationTests"

but errors seems to be environment-related (e.g. subprocess interruptions or JDK-related issues).
I hope CI will clarify this.

Additional notes

My first attempt was to restrict when assert in collectParts is triggered

assert(!ctx.typerState.constraint.contains(t), i"`wildApprox` failed to remove uninstantiated $t")
traverse(t.underlying)

by:

case t: TypeParamRef =>
  def isTrivial(bounds: TypeBounds) =
    bounds.lo.isRef(defn.NothingClass) && bounds.hi.isRef(defn.AnyClass)

  ctx.typerState.constraint.entry(t) match
    case bounds: TypeBounds if ctx.typerState.constraint.contains(t) && !isTrivial(bounds) =>
      assert(false, i"`wildApprox` failed to remove uninstantiated $t")
    case _ =>
      traverse(t.underlying)

It worked for repro sample.

Similar approach was proposed @andrzejressel in #17305 (comment).
Following review remark oh his PR: #17471 (comment) I was investigating why we end up in collectparts in assert.

I tried modifying other parts of wildApprox and related pattern matches,
without luck.

From debugging (it can change the behavior of compiler!) it looked like issue is not in a match cases,
but in how FunProtoTyped is handled in error-recovery paths, where wildApprox reconstructs protos.
Preserving FunProtoTyped avoids breaking invariants about already-typed arguments.

Since the same code was changed #23020 I switched to extract changes around FunProtoTyped as Alex1005a proposed in #23020#issuecomment-3993039055 to check if it works.

@dancewithheart dancewithheart force-pushed the fix/24824-funproto-typed-preservation branch from c04821f to 34369d5 Compare April 1, 2026 08:22
@dancewithheart
Copy link
Copy Markdown
Contributor Author

Hi @Alex1005a, I would like to give you credit - your work on FunProtoTyped handling was very insightful. I arrived at a similar direction after many failed experiments, and in the end I used your changes - kudos 🥇 for this 🙂

I cherry-picked these commits:

  • b287832 — add map and fold for FunProtoTyped
  • 48194df — remove funproto optimization from wildApprox

and used them in my PR.

I’m planning to move the PR from draft to ready for review so it can hopefully be merged - if that’s OK with you 🙂

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.

Crash during type inference after failed implicit search: assertion failed: wildApprox failed to remove uninstantiated T

2 participants