-
Notifications
You must be signed in to change notification settings - Fork 1.6k
[ty] Exhaustiveness checking & reachability for match statements
#19508
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Conversation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
match statementsmatch statements
Contributor
|
Contributor
|
| Lint rule | Added | Removed | Changed |
|---|---|---|---|
possibly-unresolved-reference |
0 | 2 | 0 |
invalid-return-type |
0 | 1 | 0 |
type-assertion-failure |
0 | 1 | 0 |
| Total | 0 | 4 | 0 |
00bd452 to
d2fa253
Compare
db907cf to
7e70581
Compare
sharkdp
commented
Jul 23, 2025
| pub(crate) guard: Option<Expression<'db>>, | ||
|
|
||
| /// A reference to the pattern of the previous match case | ||
| pub(crate) previous_predicate: Option<Box<PatternPredicate<'db>>>, |
Contributor
Author
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So there are probably more efficient ways to implement this, but this linked-list approach was so unintrusive..
carljm
approved these changes
Jul 23, 2025
Contributor
carljm
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is awesome!
Member
|
Pumped for this! |
UnboundVariable
pushed a commit
to UnboundVariable/ruff
that referenced
this pull request
Jul 24, 2025
…hlight * 'main' of https://github.com/astral-sh/ruff: [ty] Minor: fix incomplete docstring (astral-sh#19534) [ty] Move server tests as integration tests (astral-sh#19522) [`ruff`] Offer fixes for `RUF039` in more cases (astral-sh#19065) [ty] Support `dataclasses.InitVar` (astral-sh#19527) [`ruff`] Fix `RUF033` breaking with named default expressions (astral-sh#19115) Update pre-commit hook name (astral-sh#19530) Bump 0.12.5 (astral-sh#19528) [ty] Rename type_api => ty_extensions (astral-sh#19523) [ty] Added support for "go to references" in ty playground. (astral-sh#19516) [ty] Return a tuple spec from the iterator protocol (astral-sh#19496) [ty] Exhaustiveness checking & reachability for `match` statements (astral-sh#19508) [ty] Fix narrowing and reachability of class patterns with arguments (astral-sh#19512)
AlexWaygood
pushed a commit
that referenced
this pull request
Jul 25, 2025
…19508) ## Summary Implements proper reachability analysis and — in effect — exhaustiveness checking for `match` statements. This allows us to check the following code without any errors (leads to *"can implicitly return `None`"* on `main`): ```py from enum import Enum, auto class Color(Enum): RED = auto() GREEN = auto() BLUE = auto() def hex(color: Color) -> str: match color: case Color.RED: return "#ff0000" case Color.GREEN: return "#00ff00" case Color.BLUE: return "#0000ff" ``` Note that code like this already worked fine if there was a `assert_never(color)` statement in a catch-all case, because we would then consider that `assert_never` call terminal. But now this also works without the wildcard case. Adding a member to the enum would still lead to an error here, if that case would not be handled in `hex`. What needed to happen to support this is a new way of evaluating match pattern constraints. Previously, we would simply compare the type of the subject expression against the patterns. For the last case here, the subject type would still be `Color` and the value type would be `Literal[Color.BLUE]`, so we would infer an ambiguous truthiness. Now, before we compare the subject type against the pattern, we first generate a union type that corresponds to the set of all values that would have *definitely been matched* by previous patterns. Then, we build a "narrowed" subject type by computing `subject_type & ~already_matched_type`, and compare *that* against the pattern type. For the example here, `already_matched_type = Literal[Color.RED] | Literal[Color.GREEN]`, and so we have a narrowed subject type of `Color & ~(Literal[Color.RED] | Literal[Color.GREEN]) = Literal[Color.BLUE]`, which allows us to infer a reachability of `AlwaysTrue`. <details> <summary>A note on negated reachability constraints</summary> It might seem that we now perform duplicate work, because we also record *negated* reachability constraints. But that is still important for cases like the following (and possibly also for more realistic scenarios): ```py from typing import Literal def _(x: int | str): match x: case None: pass # never reachable case _: y = 1 y ``` </details> closes astral-sh/ty#99 ## Test Plan * I verified that this solves all examples from the linked ticket (the first example needs a PEP 695 type alias, because we don't support legacy type aliases yet) * Verified that the ecosystem changes are all because of removed false positives * Updated tests
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Summary
Implements proper reachability analysis and — in effect — exhaustiveness checking for
matchstatements. This allows us to check the following code without any errors (leads to "can implicitly returnNone" onmain):Note that code like this already worked fine if there was a
assert_never(color)statement in a catch-all case, because we would then consider thatassert_nevercall terminal. But now this also works without the wildcard case. Adding a member to the enum would still lead to an error here, if that case would not be handled inhex.What needed to happen to support this is a new way of evaluating match pattern constraints. Previously, we would simply compare the type of the subject expression against the patterns. For the last case here, the subject type would still be
Colorand the value type would beLiteral[Color.BLUE], so we would infer an ambiguous truthiness.Now, before we compare the subject type against the pattern, we first generate a union type that corresponds to the set of all values that would have definitely been matched by previous patterns. Then, we build a "narrowed" subject type by computing
subject_type & ~already_matched_type, and compare that against the pattern type. For the example here,already_matched_type = Literal[Color.RED] | Literal[Color.GREEN], and so we have a narrowed subject type ofColor & ~(Literal[Color.RED] | Literal[Color.GREEN]) = Literal[Color.BLUE], which allows us to infer a reachability ofAlwaysTrue.A note on negated reachability constraints
It might seem that we now perform duplicate work, because we also record negated reachability constraints. But that is still important for cases like the following (and possibly also for more realistic scenarios):
closes astral-sh/ty#99
Test Plan