Commit 5ccc481
committed
Fix #16018: Existential widening for wildcard arguments
The Scala 3 typer was rejecting subtypings of the form
F[? >: lo <: hi] <: F[X]
for covariant or contravariant `F`, even when `X = hi` (covariant) or
`X = lo` (contravariant). This is a regression against Scala 2 and surfaced
as the akka-derived report in #16018:
def f[M](xs: java.lang.Iterable[? <: Container[Any, ? <: M]])
: Seq[Container[Any, M]] =
seqOf(xs).collect {
case g: SubContainer[Any, M] @unchecked => g
case other => other
}
Once `Inferencing.captureWildcards` lifted the Java wildcard to a
`TypeBox.CAP` skolem and pattern matching narrowed the first case body
to `pat ∩ ?N.CAP`, the inferred result reduced to a chain of subtype
checks that ultimately required `F[? <: M] <:< F[M]` for covariant `F`.
That check went through `TypeComparer.compareCaptured` and was answered
in the negative.
Type-theoretic justification
----------------------------
For a covariant type constructor `F` and an existential `∃X. X <: hi`,
⨆{F[X] | X <: hi} = F[hi] (covariant supremum)
— the supremum is attained at `X = hi`, by covariance. The compiler must
admit that supremum as a subtype of `F[hi]`. Dually for contravariance with
the lower bound:
⨅{G[X] | X >: lo} = G[lo] (contravariant infimum)
This is the standard existential-elimination rule for variant occurrences.
Bug
---
`compareCaptured` was checking
v > 0: isSubType(paramBounds(tparam).hi, arg2)
v < 0: isSubType(arg2, paramBounds(tparam).lo)
i.e. it asked whether the *declared parameter bound* (which collapses to
`Any` / `Nothing` for unconstrained type parameters) conforms to `arg2`,
instead of asking whether the *wildcard's own bound* (`arg1.hi` / `arg1.lo`)
does. That rejected every interesting case.
Fix
---
In `TypeComparer.compareCaptured` use the wildcard's own hi/lo, intersected
(resp. unioned) with the declared parameter bounds to preserve soundness in
the corner case where a wildcard is wider than its parameter permits:
v > 0: isSubType(arg1.hi & paramBounds(tparam).hi, arg2)
v < 0: isSubType(arg2, arg1.lo | paramBounds(tparam).lo)
Tests (directional matrix)
--------------------------
- `tests/pos/i16018.scala` — mbovel's minimization plus a pure-Scala matrix
that exercises subtype patterns + fallback `case other` over wildcards.
- `tests/pos/i16018b.scala` — the akka-style shapes that motivated the
ticket: `java.lang.Iterable[? <: G[? <: M]]` / `java.util.List[…]`
routed through `seqOf`, then `collect` / `map` with subtype + fallback
patterns.
- `tests/pos/i16018c.scala` — positive half of the variance × bound matrix
for the fix itself: covariant + upper, contravariant + lower, nested,
via method type params, with parameter-bound tightening.
- `tests/neg/i16018c.scala` — negative half of the matrix: invariant
containers, covariant + lower, contravariant + upper, and the soundness
guard against wildcards wider than the required type. These must remain
rejected after the fix.
Verified locally
----------------
- All four `i16018*` tests: 16/16 pass via
`sbt 'scala3-compiler-bootstrapped/testOnly … CompilationTests'`
with `-Ddotty.tests.filter=i16018`.
- `wildcard` / `match` / `variance` filter subsets: 16/16 each.
- Full `CompilationTests` run: the remaining 4 failures
(`tests/run/i13358.scala`, `tests/run/lazy-*.scala`,
`tests/run/t5552.scala`, `tests/run/t7406.scala`,
`tests/run/isInstanceOf-eval.scala`,
`tests/run/i24553.scala`,
`tests/pos-custom-args/captures/fill-cbn.scala`, and the
capture-checking neg suite) reproduce on `main` without this change and
are caused by JDK 25 environment drift (`sun.misc.Unsafe` deprecation
messages on stdout, the introduction of `java.lang.IO`, and removal of
`native` from `Object.wait`).1 parent b6e5747 commit 5ccc481
5 files changed
Lines changed: 220 additions & 2 deletions
File tree
- compiler/src/dotty/tools/dotc/core
- tests
- neg
- pos
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1890 | 1890 | | |
1891 | 1891 | | |
1892 | 1892 | | |
| 1893 | + | |
| 1894 | + | |
| 1895 | + | |
| 1896 | + | |
| 1897 | + | |
| 1898 | + | |
| 1899 | + | |
| 1900 | + | |
| 1901 | + | |
| 1902 | + | |
| 1903 | + | |
| 1904 | + | |
| 1905 | + | |
1893 | 1906 | | |
1894 | | - | |
| 1907 | + | |
| 1908 | + | |
1895 | 1909 | | |
1896 | | - | |
| 1910 | + | |
| 1911 | + | |
1897 | 1912 | | |
1898 | 1913 | | |
1899 | 1914 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
0 commit comments