Skip to content

Fix 21013 wildcard match types#25885

Draft
bracevac wants to merge 3 commits intoscala:mainfrom
dotty-staging:ob/fix-21013-wildcard-match-types
Draft

Fix 21013 wildcard match types#25885
bracevac wants to merge 3 commits intoscala:mainfrom
dotty-staging:ob/fix-21013-wildcard-match-types

Conversation

@bracevac
Copy link
Copy Markdown
Contributor

Fixes #21013

type M[X] = S match { ... } applied to a wildcard was accepted even when the substitution was unsound:

type M1[K] = Double match
  case K => Int

val x1: M1[?] = ???   // accepted — "reduces" to Int
val x2: M1[Int] = x1  // error: M1[Int] does not reduce

6871cff88e exempted match aliases from isUnreducibleWild wholesale. That's too permissive: substituting ? for a match-alias type parameter is only sound when the parameter appears exclusively in the scrutinee. In a pattern, case body, or declared upper bound, it reproduces the type U[X] = (X, X); type V = U[?] unsoundness.

Fix

  • Reducer (TypeApplications.scala) — treat MatchCase arguments as nested positions so top-level applyArg no longer substitutes a wildcard into a pattern or body. Under Mode.AllowLambdaWildcardApply the AppliedType is preserved for isUnreducibleWild to flag.
  • isUnreducibleWild (Types.scala) — narrow the exemption: exempt only when every wildcard argument is passed to a parameter that occurs solely in the scrutinee.

Covers both AppliedType(HKTypeLambda, args) from the reducer and AppliedType(TypeRef, args) from e.g. or-type approximation in TypeOps.

Spec

New paragraph in docs/_spec/03-types.md §Match Types stating the restriction.

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

Extensively

How was the solution tested?

New automated tests (including the issue's reproducer, if applicable)

Substituting a wildcard (`?`) for a type parameter of a match type alias
is unsound whenever that parameter appears outside the match scrutinee —
either in a case pattern (where substitution can make the pattern spuriously
match anything), a case body (where the wildcard loses the relationship
between multiple occurrences), or the declared upper bound.

The exemption for match aliases in `isUnreducibleWild` (added in 6871cff
for scala#9999) was too permissive and allowed constructions such as
`type M1[K] = Double match { case K => Int }` applied as `M1[?]` to
reduce to `Int`, even though `M1[Int]` does not reduce. This is the
classic `type U[X] = (X, X); type V = U[?]` unsoundness lifted to
match types.

Refine the exemption so only parameters used exclusively in the
scrutinee are soundly substitutable with a wildcard. The `Reducer`
treats `MatchCase` arguments as nested positions so wildcard
substitutions in pattern/body positions no longer reduce away the
surrounding application, letting `isUnreducibleWild` flag it.

Fixes scala#21013
Document the rule that a match type alias applied to a wildcard is
legal only when each wildcarded parameter occurs solely in the
scrutinee. Mirrors the compiler check added for scala#21013.
@bracevac bracevac changed the title Ob/fix 21013 wildcard match types Fix 21013 wildcard match types Apr 20, 2026
The previous commit's `!isMatchAlias` guard still fired on
`AppliedType(HKTypeLambda, args)` produced by the reducer, rejecting
direct `M[?]` even when the wildcard was sound (param only in the
scrutinee). Look up the underlying `HKTypeLambda` from either a
`TypeRef` to a `MatchAlias` or a bare `HKTypeLambda` tycon, and
reject only when the wildcarded parameter occurs outside the
scrutinee.

Add tests/pos/i21013.scala exercising direct `M[?]`, bounded
wildcards, and wildcards nested in other type constructors.
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.

Regressions in fingo/spata for match types

1 participant