Thanks to visit codestin.com
Credit goes to github.com

Skip to content

[ty] Make class-pattern fallthrough member-aware#26283

Merged
charliermarsh merged 4 commits into
mainfrom
charlie/class-pattern-member-exhaustiveness
Jun 25, 2026
Merged

[ty] Make class-pattern fallthrough member-aware#26283
charliermarsh merged 4 commits into
mainfrom
charlie/class-pattern-member-exhaustiveness

Conversation

@charliermarsh

@charliermarsh charliermarsh commented Jun 23, 2026

Copy link
Copy Markdown
Member

Summary

This is the first of three stacked PRs split from #26010.

On main, we consider a class pattern exhaustive whenever its subpatterns are irrefutable. We do not check whether the attributes requested by the pattern are actually present:

class Base: ...

class Child(Base):
    x: int = 0

def missing_attribute(value: Base) -> int:
    match value:
        case Base(x=_):
            return 1

def subject_attribute(value: Child) -> int:
    match value:
        case Base(x=_):
            return 1

main accepts both functions. This is wrong for missing_attribute: a Base instance may not have x, so the pattern can fail and the function can return None. However, subject_attribute is exhaustive because its static subject type is Child, which guarantees that x is present.

(This isn't exactly right -- Pyright requires that Child is @final, but I think this is more consistent with our semantics elsewhere.)

This PR retains the attributes requested by a class pattern and checks them against the static subject type. With this change, missing_attribute reports invalid-return-type, while subject_attribute remains accepted. Nested attribute patterns use the same check recursively, including inside sequence, or, and as patterns.

@astral-sh-bot astral-sh-bot Bot added the ty Multi-file analysis & type inference label Jun 23, 2026
@astral-sh-bot

astral-sh-bot Bot commented Jun 23, 2026

Copy link
Copy Markdown

Typing conformance results

No changes detected ✅

Current numbers
The percentage of diagnostics emitted that were expected errors held steady at 94.47%. The percentage of expected errors that received a diagnostic held steady at 89.19%. The number of fully passing files held steady at 95/134.

@astral-sh-bot

astral-sh-bot Bot commented Jun 23, 2026

Copy link
Copy Markdown

Memory usage report

Memory usage unchanged ✅

@astral-sh-bot

astral-sh-bot Bot commented Jun 23, 2026

Copy link
Copy Markdown

ecosystem-analyzer results

Lint rule Added Removed Changed
type-assertion-failure 0 0 2
invalid-return-type 0 1 0
Total 0 1 2

Large timing changes:

Project Old Time New Time Change
egglog-python 0.31s 0.50s +61%

Raw diff:

Expression (https://github.com/cognitedata/Expression)
- expression/extra/parser.py:128:34 error[invalid-return-type] Function can implicitly return `None`, which is not assignable to return type `Result[tuple[tuple[_A@and_then, _B@and_then], tuple[str, int]], str]`

egglog-python (https://github.com/egraphs-good/egglog-python)
- python/egglog/egraph_state.py:623:25 error[type-assertion-failure] Type `Unknown & ~None & ~int & ~float & ~str` is not equivalent to `Never`
+ python/egglog/egraph_state.py:623:25 error[type-assertion-failure] Type `Unknown & ~None` is not equivalent to `Never`
- python/egglog/pretty.py:309:17 error[type-assertion-failure] Type `Unknown & ~None & ~int & ~float & ~str` is not equivalent to `Never`
+ python/egglog/pretty.py:309:17 error[type-assertion-failure] Type `Unknown & ~None` is not equivalent to `Never`

Full report with detailed diff (timing results)

@charliermarsh charliermarsh marked this pull request as ready for review June 23, 2026 23:55
@charliermarsh charliermarsh requested review from a team as code owners June 23, 2026 23:55
@charliermarsh charliermarsh force-pushed the charlie/class-pattern-member-exhaustiveness branch 3 times, most recently from bf68588 to 6cf2b7c Compare June 24, 2026 11:30
@dhruvmanila

Copy link
Copy Markdown
Member

Should this raise an error? At runtime, this prints second (going through the second branch) given that Point has no attribute even though __match_args__ says it does accept 1 positional argument.

This also means that the first pattern is refutable which is why it falls through to the second case.

from dataclasses import dataclass

class Point:
    __match_args__ = ("x",)

def f(value: Point) -> None:
    match value:
        case Point(_):
            print("first")
        case x:
            reveal_type(x)
            print("second")

f(Point())

Both pyrefly and mypy raises a diagnostic stating that Point has no attribute x but Pyright doesn't.

I think not raising a diagnostic is fine but it might still be useful to make the pattern refutable in this case.

Comment thread crates/ty_python_core/src/predicate.rs Outdated
}

impl ClassPatternPredicateKind<'_> {
pub fn is_argumentless(&self) -> bool {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is_empty please :)

@dhruvmanila

Copy link
Copy Markdown
Member

I think not raising a diagnostic is fine but it might still be useful to make the pattern refutable in this case.

Maybe this is something that needs to be done in #26284 ?

@charliermarsh charliermarsh force-pushed the charlie/class-pattern-member-exhaustiveness branch from 6cf2b7c to afd19f8 Compare June 25, 2026 11:13
@charliermarsh charliermarsh enabled auto-merge (squash) June 25, 2026 11:14
@charliermarsh charliermarsh merged commit 7e921ca into main Jun 25, 2026
62 checks passed
@charliermarsh charliermarsh deleted the charlie/class-pattern-member-exhaustiveness branch June 25, 2026 11:18
@charliermarsh

Copy link
Copy Markdown
Member Author

Yeah, this is handled by #26284! The missing x makes Point(_) refutable, and the fallback branch reveals Point.

charliermarsh added a commit that referenced this pull request Jun 25, 2026
## Summary

This is the second of three stacked PRs split from #26010. It is based
on #26283.

The first PR makes keyword class-pattern exhaustiveness subject-aware,
but positional patterns still use a syntax-only approximation: any
positional capture is treated as irrefutable without determining which
value Python passes to it. That can classify a pattern as exhaustive
when `__match_args__` is missing, uncertain, widened, or names an absent
attribute.

This resolves each positional source from the class named in the
pattern. For ordinary classes, we require a definitely bound fixed tuple
of literal names from `__match_args__` and check each selected member
recursively. For Python's match-self builtins, including their
subclasses, the first positional pattern receives the complete subject.
Generated `NamedTuple.__match_args__` values follow the same path.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ty Multi-file analysis & type inference

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants