[ty] Avoid redundant equality intersections#26057
Conversation
Typing conformance resultsNo changes detected ✅Current numbersThe percentage of diagnostics emitted that were expected errors held steady at 94.37%. The percentage of expected errors that received a diagnostic held steady at 89.00%. The number of fully passing files held steady at 94/134. |
Memory usage reportMemory usage unchanged ✅ |
|
Merging this PR will improve performance by 13.78%
|
| Mode | Benchmark | BASE |
HEAD |
Efficiency | |
|---|---|---|---|---|---|
| ⚡ | Simulation | ty_micro[literal_equality_fallthrough_guarded_any] |
161.6 ms | 130.9 ms | +23.48% |
| ⚡ | Memory | ty_micro[literal_equality_fallthrough_guarded_any] |
13 MB | 12.4 MB | +4.84% |
Tip
Curious why this is faster? Use the CodSpeed MCP and ask your agent.
Comparing charlie/avoid-redundant-equality-intersections (fdfd4d8) with main (870fc14)
Footnotes
-
64 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports. ↩
0ac18a4 to
80a0a61
Compare
a7960d2 to
3949d2d
Compare
9305e37 to
65a13dc
Compare
|
Just gonna merge this in as a fast-follow-up on the previous PR; it's a bit silly but seems not-useless. |
65a13dc to
fdfd4d8
Compare
|
It's nice that this is faster, but unfortunately I don't think this is correct. The premise seems to be that an intersection
from enum import Enum
from typing import Literal, TypeVar
from ty_extensions import Not
class Color(Enum):
RED = 1
BLUE = 2
T = TypeVar("T")
def accepts_not_red(value: Not[Literal[Color.RED]]) -> None:
pass
def f(value: T | Literal[Color.RED]) -> None:
if value == Color.RED:
return
accepts_not_red(value)Prior to this PR, we accepted this code; now, on I think we should revert this change and add ^that as a regression test. |
## Summary This reverts the dynamic-only guard added in #26057. Non-dynamic union arms can still overlap alternatives removed by equality narrowing when their individual comparison result is ambiguous. For example, after excluding `Color.RED` from `T | Literal[Color.RED]`, an unconstrained `T` must remain `T & ~Literal[Color.RED]`; dropping that negative constraint incorrectly widens the result to `T`. The regression test covers that fallthrough behavior. This restores the previous narrowing while we develop a narrower optimization that only skips constraints for arms proven to take the selected branch. The focused equality mdtest and file-scoped hooks pass.
Summary
This restores the dynamic-only guard when applying removed union arms as negative constraints during equality narrowing. Static alternatives have already been narrowed individually, so intersecting every surviving arm with the removed set creates redundant intersection types that compound across repeated fallthrough narrowing.
Dynamic alternatives still need those negative constraints to preserve the branch predicate. Keeping that distinction avoids the guarded-
Anyequality benchmark regression introduced by4e7ab3ac6cwithout changing narrowing behavior. The targeted benchmark showed that removing the guard increased runtime by about 33% locally.The full
ty_python_semanticsuite, Clippy with warnings denied, and file-scoped hooks pass.