subtype: Tweak the diagonal rule to make it more clear#61861
Conversation
Pop quiz: Which of these make `T` diagonal:
```
Tuple{S, T} where S<:T where T
Tuple{S, T} where S<:Tuple{T} where T
Tuple{S, T} where S<:Tuple{T,T} where T
```
The answer right now is 2 and 3, but I would argue that 2 is a bit of
an accident of implementation, because `Tuple` turns back on the
variable occurrence counting that the typevar consitency check
tried to turn off (but we depend on `3` and it clearly "looks"
diagonal).
This PR changes the semantics so that 2 is no longer considered
diagonal. Instead, the diagonal rule conceptually applies to
`Tuple`s: The diagonal rule applies whenever any `Tuple` sees
2+ active typevars in covariant position (and the typevar
was not statically found in invariant position by the outer
UnionAll - same as before).
For a more extensive set of examples of cases that change
behaviors, see the test suite, but of note none of our
existing tests depended on this (other than a test_broken
that now passes).
A particular motivation for this change is intersection,
since intersection needs to extensively reason about whether
or not it is possible for a variable to become diagonal (no
intersection changes are made in this PR, because intersection
is not currently correct with respect to various diagonal
cases - however fixing that requires defining what correctness
means).
Idea by me. Tests by GPT-5.5 Pro. Implemented by Claude Opus 4.7.
|
I think I would have expected all three of these to be diagonal, from the perspective that: are all "clearly" diagonal (i.e. the bounds "carry" I'm not totally clear on what the proposed rule for "covariant position" is in bounds. For example: It's a bit strange that bounds would confer invariant status but not covariant status. |
|
my expectation would be that the bounds do indeed make it covariant, since we can observe which would imply |
|
Ah the lower bound is contravariant, so it disables the diagonal rule: julia> Tuple{Float64,Int,String} <: Tuple{S,S,T} where {T,S>:T}
trueThe behavior actually depends on bounds order: julia> Tuple{Float64,Int,String} <: Tuple{S,S,T} where {S,T<:S}
false |
What is |
That is a consistent position, but goes against the original design intent, as well as the explicit use in intersection of the
The diagonal rule has an additional (oft ignored) constraint that it is disabled unless the (declared) lower bound of the typevar is "obviously concrete" (including Union{}). This change was made in response to a suggestion by the Northeastern folks for substantially this case.
type application does not in general preserve the diagonal rule or imply subtyping. In particular fact, type application is allowed with non-concrete elements for diagonal vars:
It's diagonal. The covariance transform does not hold with respect to the diagonal rule. As I said above, I think it would be consistent to impose this (which would require diagonality for case 1), but that's not the design choice we've made. |
|
@nanosoldier |
I'm not yet sure what position to take on case 1, just finding that subtyping appears to become incorrect for case 2 on this branch currently: vs case 1 that is correct (or at least internally consistent) |
Yes, that's a bug. Will fix. |
|
The package evaluation job you requested has completed - possible new issues were detected. Report summary❗ Packages that crashed249 packages crashed on the previous version too. ✖ Packages that failed19 packages failed only on the current version.
1057 packages failed on the previous version too. ✔ Packages that passed tests11 packages passed tests only on the current version.
5905 packages passed tests on the previous version too. ~ Packages that at least loaded1 packages successfully loaded only on the current version.
3689 packages successfully loaded on the previous version too. ➖ Packages that were skipped altogether897 packages were skipped on the previous version too. |
|
Looks like noise, but let's rerun to be sure: @nanosoldier |
|
The package evaluation job you requested has completed - possible new issues were detected. Report summary✖ Packages that failed1 packages failed only on the current version.
6 packages failed on the previous version too. ✔ Packages that passed tests1 packages passed tests only on the current version.
8 packages passed tests on the previous version too. ~ Packages that at least loaded2 packages successfully loaded on the previous version too. |
|
Caught up with @JeffBezanson offline - his opinion was that case 2 being diagonal is a bug and was not intended. |
Pop quiz: Which of these make
Tdiagonal:The answer right now is 2 and 3, but I would argue that 2 is a bit of an accident of implementation, because
Tupleturns back on the variable occurrence counting that the typevar consitency check tried to turn off (but we depend on3and it clearly "looks" diagonal).This PR changes the semantics so that 2 is no longer considered diagonal. Instead, the diagonal rule conceptually applies to
Tuples: The diagonal rule applies whenever anyTuplesees 2+ active typevars in covariant position (and the typevar was not statically found in invariant position by the outer UnionAll - same as before).For a more extensive set of examples of cases that change behaviors, see the test suite, but of note none of our existing tests depended on this (other than a test_broken that now passes).
A particular motivation for this change is intersection, since intersection needs to extensively reason about whether or not it is possible for a variable to become diagonal (no intersection changes are made in this PR, because intersection is not currently correct with respect to various diagonal cases - however fixing that requires defining what correctness means).
Idea by me. Tests by GPT-5.5 Pro. Implemented by Claude Opus 4.7.