[ty] Simplify union lower bounds and intersection upper bounds in constraint sets#21871
[ty] Simplify union lower bounds and intersection upper bounds in constraint sets#21871
Conversation
Diagnostic diff on typing conformance testsNo changes detected when running ty on typing conformance tests ✅ |
|
* origin/main: (33 commits) [ty] Simplify union lower bounds and intersection upper bounds in constraint sets (#21871) [ty] Collapse `never` paths in constraint set BDDs (#21880) Fix leading comment formatting for lambdas with multiple parameters (#21879) [ty] Type inference for `@asynccontextmanager` (#21876) Fix comment placement in lambda parameters (#21868) [`pylint`] Detect subclasses of builtin exceptions (`PLW0133`) (#21382) Fix stack overflow with recursive generic protocols (depth limit) (#21858) New diagnostics for unused range suppressions (#21783) [ty] Use default settings in completion tests [ty] Infer type variables within generic unions (#21862) [ty] Fix overload filtering to prefer more "precise" match (#21859) [ty] Stabilize auto-import [ty] Fix reveal-type E2E test (#21865) [ty] Use concise message for LSP clients not supporting related diagnostic information (#21850) Include more details in Tokens 'offset is inside token' panic message (#21860) apply range suppressions to filter diagnostics (#21623) [ty] followup: add-import action for `reveal_type` too (#21668) [ty] Enrich function argument auto-complete suggestions with annotated types [ty] Add autocomplete suggestions for function arguments [`flake8-bugbear`] Accept immutable slice default arguments (`B008`) (#21823) ...
| // T ≤ α & β ⇒ (T ≤ α) ∧ (T ≤ β) | ||
| // T ≤ α & ¬β ⇒ (T ≤ α) ∧ ¬(T ≤ β) | ||
| // α | β ≤ T ⇒ (α ≤ T) ∧ (β ≤ T) |
There was a problem hiding this comment.
You write these as implications, but don't we need those to be equivalences?
The one about negations looks interesting. I guess it could be simplified to T ≤ ¬β ⇒ ¬(T ≤ β), since the intersection part of it is already covered by the first rule. But more importantly, is this really correct? The left hand side seems to always be true for T = Never, whereas the right hand side seems to always be false for T = Never. That would mean it's not even true as an implication?
There was a problem hiding this comment.
The one about negations looks interesting. I guess it could be simplified to
T ≤ ¬β ⇒ ¬(T ≤ β), since the intersection part of it is already covered by the first rule. But more importantly, is this really correct? The left hand side seems to always be true forT = Never, whereas the right hand side seems to always be false forT = Never. That would mean it's not even true as an implication?
Ah good catch! Negation doesn't distribute through the check like I wrote it. It should be something like
T ≤ ¬α & ¬β ⇒ (T ≤ ¬α) ∧ (T ≤ ¬β)
i.e. we can still separate out the negation elements of the intersection, but they should remain negated types and a positive ≤ check.
I'll fix this in a follow-on PR.
You write these as implications, but don't we need those to be equivalences?
They are equivalences, but we're using this as a normalization step, so we only want to apply them in the direction that I've written them. That is, the goal with this change is that the upper bound of a constraint will never be an intersection type anymore. (and ditto for the lower bound never being a union type)
There was a problem hiding this comment.
You write these as implications, but don't we need those to be equivalences?
They are equivalences, but we're using this as a normalization step, so we only want to apply them in the direction that I've written them. That is, the goal with this change is that the upper bound of a constraint will never be an intersection type anymore. (and ditto for the lower bound never being a union type)
👍
I'm being pedantic, but I think what I meant was: we need these to be equivalences, or otherwise, the structural simplification that we apply here (in one direction) might lead to a constraint set that is not equivalent to the original constraint set anymore. But even if my thinking is correct, there's no need to change anything. The arrows can also just represent the direction in which we're performing the simplification. I mainly wanted to understand.
There was a problem hiding this comment.
we need these to be equivalences, or otherwise, the structural simplification that we apply here (in one direction) might lead to a constraint set that is not equivalent to the original constraint set anymore.
Got it! I think I got this meaning correct in the new comment in #21897
This fixes the logic error that @sharkdp [found](#21871 (comment)) in the constraint set upper bound normalization logic I introduced in #21871. I had originally claimed that `(T ≤ α & ~β)` should simplify into `(T ≤ α) ∧ ¬(T ≤ β)`. But that also suggests that `T ≤ ~β` should simplify to `¬(T ≤ β)` on its own, and that's not correct. The correct simplification is that `~α` is an "atomic" type, not an "intersection" for the purposes of our upper bound simplifcation. So `(T ≤ α & ~β)` should simplify to `(T ≤ α) ∧ (T ≤ ~β)`. That is, break apart the elements of a (proper) intersection, regardless of whether each element is negated or not. This PR fixes the logic, adds a test case, and updates the comments to be hopefully more clear and accurate.
In a constraint set, it's not useful for an upper bound to be an intersection type, or for a lower bound to be a union type. Both of those can be rewritten as simpler BDDs:
We were seeing performance issues on #21551 when not performing this simplification. For instance,
pandaswas producing some constraint sets involving intersections of 8-9 different types. Our sequent map calculation was timing out calculating all of the different permutations of those types:(and then imagine what that looks like for 9 types instead of 3...)
With this change, all of those permutations are now encoded in the BDD structure itself, which is very good at simplifying that kind of thing.
Pulling this out of #21551 for separate review.