[ty] Preserve short-circuit reachability for direct conditions - #28082
Conversation
Evaluate direct conditions separately from saved expression values so mutable truthiness does not make unreachable branches appear reachable. Store comparison-chain truthiness only when it differs from the inferred value type, and read chained-comparison predicates directly from expression inference. Preserve sparse overrides across result merging and cycle recovery. Cover short-circuit consumers, saved values, comparison chains, conditional expression types, quoted annotation metadata, and cyclic inference. Prerequisite for #28045.
Typing conformance resultsNo changes detected ✅Current numbersThe percentage of diagnostics emitted that were expected errors held steady at 97.79%. The percentage of expected errors that received a diagnostic held steady at 94.33%. The number of fully passing files held steady at 112/136. |
Memory usage reportSummary
Significant changesClick to expand detailed breakdownprefect
sphinx
trio
flake8
|
|
| Lint rule | Added | Removed | Changed |
|---|---|---|---|
invalid-assignment |
0 | 2 | 0 |
invalid-argument-type |
0 | 1 | 0 |
unresolved-import |
0 | 1 | 0 |
unsound-assignment |
0 | 1 | 0 |
| Total | 0 | 5 | 0 |
Raw diff:
pwndbg (https://github.com/pwndbg/pwndbg)
- pwndbg/aglib/disasm/assistant.py:390:24 error[invalid-assignment] Object of type `None` is not assignable to `Emulator`
- pwndbg/aglib/disasm/assistant.py:391:19 error[invalid-assignment] Object of type `None` is not assignable to `Emulator`
pytest (https://github.com/pytest-dev/pytest)
- src/_pytest/_io/terminalwriter.py:80:24 error[unresolved-import] Cannot resolve imported module `colorama`
- src/_pytest/_io/terminalwriter.py:84:24 warning[unsound-assignment] Unsound assignment: `Unknown` is not a subtype of `TextIO | None`
spark (https://github.com/apache/spark)
- python/pyspark/streaming/context.py:389:42 error[invalid-argument-type] Argument to bound method `SparkContext.parallelize` is incorrect: Expected `Iterable[Unknown]`, found `RDD[T@queueStream]`
Merging this PR will not alter performance
Comparing Footnotes
|
Codex ecosystem analysisPR #28082 ecosystem summaryThe five removed diagnostics come from branches made unreachable by platform checks or declared types. Each removal is consistent with evaluating a short-circuit expression directly as a condition. Affected projects1. pwndbg's declared return type makes an emulation-failure branch unreachableReport entries: The merge base reports two This follows the annotations, which do not fully describe runtime failure handling: Both assignments reduce to the same behavior: def f(value: int):
if value and False:
# Merge base: error[invalid-assignment] Object of type `None` is not assignable to `int`
# PR: no diagnostic
value = None2. pytest's Windows-only terminal setup is unreachable on LinuxReport entries: pytest guards its optional An operand with unknown truthiness cannot make def write(stdout):
if stdout and False:
# Merge base: error[unresolved-import] Cannot resolve imported module `missing`
# PR: no diagnostic
import missing
# Merge base: warning[unsound-assignment] Unsound assignment: `Unknown` is not a subtype of `int`
# PR: no diagnostic
file: int = missing3. Spark's declared element type makes a conversion branch unreachableReport entries: The merge base reports A final false operand prevents an def takes_str(x: str):
...
def f(x: int):
if x and False:
# Merge base: error[invalid-argument-type] Argument to function `takes_str` is incorrect: Expected `str`, found `int & ~AlwaysFalsy`
# PR: no diagnostic
takes_str(x)Reproduction
|
|
(I speak through Carl today, we reviewed this synchronously together) |
|
|
||
| /// Like [`Truthiness::and`], but evaluates `other` only when `self` may be true. | ||
| #[must_use] | ||
| pub fn and_else(self, other: impl FnOnce() -> Self) -> Self { |
There was a problem hiding this comment.
I think the normal convention would be to call this .and_then(), similar to how Option has both .or_else() which only calls the closure if self is None and .and_then() which only does work when self is Some
ty can incorrectly treat a branch guarded by an impossible compound condition as reachable when an operand has potentially mutable truthiness. For example,
if value and False:always skips its body in CPython; the first falsy operand selects the control-flow path immediately, it doesn't fully evaluatevalue and Falseand then re-evaluate the truthiness of the result. Savingvalue and Falseto a variable and then testing the saved object can instead callvalue.__bool__twice and get different answers.Distinguish expressions consumed directly as conditions from expressions that produce values. Evaluate direct boolean and conditional expressions structurally, and retain the combined truthiness of chained comparisons even when a comparison returns an arbitrary object. Operands with types equivalent to
Nevercannot produce a result; preserve that distinction from ambiguous truthiness while composing conditions. Value contexts continue to allow repeated truthiness checks to disagree.To limit retained memory, comparison truthiness uses sparse overrides in optional inference metadata, stored as boxed slices. Ordinary comparison chains use the truthiness of their inferred value type, and root chained-comparison conditions reuse expression inference without a separate condition-analysis memo. Cycle widening preserves overrides when needed to keep the effective truthiness monotonic. Tracking operands that cannot produce a result adds only transient evaluation state.
Prerequisite for #28045, which addresses astral-sh/ty#4380.
Ecosystem results
Five expected diagnostic removals across pwndbg, pytest, and Spark; no additions. Each comes from a branch that is unreachable under the checked platform or declared types. See the Codex ecosystem analysis for details and minimized reproducers.
Test plan
not, conditional expressions, and chained comparisons across branches, loops, assertions, comprehension filters, and match guards.Never, aliases and unions of aliases ofNever, and type variables bounded byNeverassert branch reachability using unrelated variables and check precise conditional-expression result types.Annotatedmetadata.