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

Skip to content

[ty] Infer class and mapping pattern bindings#25941

Merged
charliermarsh merged 3 commits into
mainfrom
charlie/class-mapping-pattern-bindings
Jun 26, 2026
Merged

[ty] Infer class and mapping pattern bindings#25941
charliermarsh merged 3 commits into
mainfrom
charlie/class-mapping-pattern-bindings

Conversation

@charliermarsh

@charliermarsh charliermarsh commented Jun 13, 2026

Copy link
Copy Markdown
Member

Summary

We already infer the types of captures and aliases in sequence patterns by following the values passed from each pattern node to its children. This PR extends the same recursive analysis to class and mapping patterns.

from dataclasses import dataclass
from typing import Literal, TypedDict

class IntPayload(TypedDict):
    tag: Literal["int"]
    value: int

class StrPayload(TypedDict):
    tag: Literal["str"]
    value: str

@dataclass
class Box:
    payload: IntPayload | StrPayload

def handle(box: Box) -> None:
    match box:
        case Box({"tag": "int", "value": value}) as whole:
            reveal_type(value)  # int
            reveal_type(whole)  # Box

A class pattern passes the selected instance member to each child pattern. Positional patterns resolve that member through __match_args__; keyword patterns use the named attribute. A mapping pattern passes the value associated with each matched key, while **rest receives the new dictionary built from the remaining entries.

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

astral-sh-bot Bot commented Jun 13, 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 13, 2026

Copy link
Copy Markdown

Memory usage report

Memory usage unchanged ✅

@astral-sh-bot

astral-sh-bot Bot commented Jun 13, 2026

Copy link
Copy Markdown

ecosystem-analyzer results

Lint rule Added Removed Changed
unresolved-attribute 8 0 0
invalid-argument-type 6 0 0
invalid-assignment 0 0 3
type-assertion-failure 0 1 2
invalid-return-type 1 1 0
no-matching-overload 1 0 0
Total 16 2 5

Flaky changes detected. This PR summary excludes flaky changes; see the HTML report for details.

Raw diff (23 changes)
cwltool (https://github.com/common-workflow-language/cwltool)
+ cwltool/main.py:244:57 error[no-matching-overload] No overload of bound method `str.join` matches arguments
+ cwltool/validate_js.py:101:29 error[invalid-argument-type] Method `__getitem__` of type `bound method Top[MutableMapping[Unknown, Unknown]].__getitem__(key: Never, /) -> object` cannot be called with key of type `str` on object of type `Top[MutableMapping[Unknown, Unknown]]`
+ cwltool/validate_js.py:101:29 error[invalid-argument-type] Method `__getitem__` of type `bound method str.__getitem__(key: SupportsIndex | slice[SupportsIndex | None, SupportsIndex | None, SupportsIndex | None], /) -> str` cannot be called with key of type `str` on object of type `str`

egglog-python (https://github.com/egraphs-good/egglog-python)
- python/egglog/egraph_state.py:378:25 error[type-assertion-failure] Type `Unknown & ~Literal["delete"] & ~Literal["subsume"]` 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/egraph_state.py:623:25 error[type-assertion-failure] Type `Value` is not equivalent to `Never`
- python/egglog/pretty.py:309:17 error[type-assertion-failure] Type `Unknown & ~None` is not equivalent to `Never`
+ python/egglog/pretty.py:309:17 error[type-assertion-failure] Type `Value` is not equivalent to `Never`
+ python/egglog/thunk.py:69:28 error[invalid-return-type] Return type does not match returned value: expected `T@__call__`, found `TypeVar`

jax (https://github.com/google/jax)
- jax/experimental/mosaic/gpu/constraints.py:211:6 error[invalid-return-type] Function can implicitly return `None`, which is not assignable to return type `Variable | RegisterLayout | TMEMLayout | ... omitted 6 union elements`

pylint (https://github.com/pycqa/pylint)
+ pylint/checkers/match_statements_checker.py:164:25 error[unresolved-attribute] Attribute `value` is not defined on `NodeNG` in union `NodeNG | Proxy`
+ pylint/checkers/classes/class_checker.py:946:54 error[unresolved-attribute] Attribute `name` is not defined on `NodeNG` in union `NodeNG | UninferableBase | Proxy`
+ pylint/checkers/refactoring/recommendation_checker.py:261:29 error[unresolved-attribute] Attribute `name` is not defined on `Attribute` in union `Attribute | Name`
+ pylint/checkers/refactoring/recommendation_checker.py:265:29 error[unresolved-attribute] Attribute `attrname` is not defined on `Name` in union `Attribute | Name`
+ pylint/checkers/refactoring/refactoring_checker.py:652:16 error[unresolved-attribute] Object of type `NodeNG | None` has no attribute `value`
+ pylint/checkers/refactoring/refactoring_checker.py:1178:12 error[unresolved-attribute] Object of type `NodeNG` has no attribute `name`
+ pylint/checkers/refactoring/refactoring_checker.py:1830:32 error[unresolved-attribute] Attribute `name` is not defined on `NodeNG` in union `NodeNG | Proxy`
+ pylint/checkers/refactoring/refactoring_checker.py:1841:38 error[unresolved-attribute] Attribute `name` is not defined on `NodeNG` in union `NodeNG | Proxy`
+ pylint/checkers/utils.py:1303:83 error[invalid-argument-type] Argument to function `has_known_bases` is incorrect: Expected `ClassDef`, found `(ClassDef & BaseInstance) | (FunctionDef & BaseInstance) | (Lambda & BaseInstance) | (UnboundMethod & BaseInstance)`
+ pylint/checkers/utils.py:1304:38 error[invalid-argument-type] Argument is incorrect: Expected `NodeNG`, found `(ClassDef & BaseInstance) | (FunctionDef & BaseInstance) | (Lambda & BaseInstance) | (UnboundMethod & BaseInstance)`

pytest (https://github.com/pytest-dev/pytest)
- src/_pytest/assertion/rewrite.py:1012:25 error[invalid-assignment] Invalid subscript assignment with key of type `Unknown` and value of type `expr` on object of type `dict[str, str]`
+ src/_pytest/assertion/rewrite.py:1012:25 error[invalid-assignment] Invalid subscript assignment with key of type `str` and value of type `expr` on object of type `dict[str, str]`
- src/_pytest/assertion/rewrite.py:1110:17 error[invalid-assignment] Invalid subscript assignment with key of type `Unknown` and value of type `NamedExpr` on object of type `dict[str, str]`
+ src/_pytest/assertion/rewrite.py:1110:17 error[invalid-assignment] Invalid subscript assignment with key of type `str` and value of type `NamedExpr` on object of type `dict[str, str]`
- src/_pytest/assertion/rewrite.py:1128:21 error[invalid-assignment] Invalid subscript assignment with key of type `Unknown` and value of type `NamedExpr` on object of type `dict[str, str]`
+ src/_pytest/assertion/rewrite.py:1128:21 error[invalid-assignment] Invalid subscript assignment with key of type `Any & str` and value of type `NamedExpr` on object of type `dict[str, str]`

schema_salad (https://github.com/common-workflow-language/schema_salad)
+ src/schema_salad/typescript_codegen.py:408:25 error[invalid-argument-type] Argument is incorrect: Expected `bool`, found `Any | str | Literal[False]`

typeshed-stats (https://github.com/AlexWaygood/typeshed-stats)
+ src/typeshed_stats/gather.py:520:27 error[invalid-argument-type] Argument to function `sorted` is incorrect: Expected `Iterable[str]`, found `Top[list[Unknown]]`

Full report with detailed diff (timing results)

@charliermarsh charliermarsh force-pushed the charlie/class-mapping-pattern-bindings branch from b453dd7 to 54e1f3b Compare June 13, 2026 12:52
@charliermarsh charliermarsh force-pushed the charlie/class-mapping-pattern-bindings branch from 54e1f3b to 2c2d601 Compare June 13, 2026 14:04
@charliermarsh charliermarsh force-pushed the charlie/class-mapping-pattern-bindings branch 2 times, most recently from 60429ac to 846bfac Compare June 13, 2026 15:40
@charliermarsh charliermarsh force-pushed the charlie/class-mapping-pattern-bindings branch from 846bfac to 6b9cf4e Compare June 13, 2026 18:11
@charliermarsh charliermarsh force-pushed the charlie/class-mapping-pattern-bindings branch from 6b9cf4e to e42c4f5 Compare June 13, 2026 18:36
@charliermarsh charliermarsh force-pushed the charlie/class-mapping-pattern-bindings branch 2 times, most recently from f1558fa to 9ca3ba0 Compare June 13, 2026 20:11
@charliermarsh charliermarsh force-pushed the charlie/pattern-as branch 2 times, most recently from 4011765 to 2a91303 Compare June 13, 2026 21:31
@charliermarsh charliermarsh force-pushed the charlie/class-mapping-pattern-bindings branch from 9ca3ba0 to 7f028a9 Compare June 13, 2026 21:31
@charliermarsh charliermarsh force-pushed the charlie/class-mapping-pattern-bindings branch 3 times, most recently from 25345ad to faa393d Compare June 14, 2026 01:14
@charliermarsh charliermarsh force-pushed the charlie/class-mapping-pattern-bindings branch from faa393d to 0cd4aba Compare June 14, 2026 02:17
@charliermarsh charliermarsh force-pushed the charlie/class-mapping-pattern-bindings branch 3 times, most recently from 83f5d2d to 90ec895 Compare June 15, 2026 01:21
@codspeed-hq

codspeed-hq Bot commented Jun 15, 2026

Copy link
Copy Markdown

Merging this PR will not alter performance

✅ 85 untouched benchmarks
⏩ 64 skipped benchmarks1


Comparing charlie/class-mapping-pattern-bindings (17abf80) with main (71541d4)

Open in CodSpeed

Footnotes

  1. 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.

@charliermarsh charliermarsh force-pushed the charlie/class-mapping-pattern-bindings branch from 9fa61a9 to 304fe53 Compare June 15, 2026 13:19
@charliermarsh charliermarsh force-pushed the charlie/class-mapping-pattern-bindings branch from 304fe53 to 8d89b66 Compare June 15, 2026 14:45
@charliermarsh charliermarsh changed the base branch from charlie/pattern-as to charlie/class-pattern-exhaustiveness June 15, 2026 14:48
@charliermarsh charliermarsh force-pushed the charlie/class-pattern-exhaustiveness branch from 3c2f4af to 66be638 Compare June 15, 2026 16:19
@charliermarsh charliermarsh force-pushed the charlie/class-mapping-pattern-bindings branch from 8d89b66 to ee71ae3 Compare June 15, 2026 16:19
@charliermarsh charliermarsh force-pushed the charlie/class-pattern-exhaustiveness branch from 66be638 to 9837919 Compare June 15, 2026 17:59
@charliermarsh charliermarsh force-pushed the charlie/class-mapping-pattern-bindings branch from ee71ae3 to 966ed8b Compare June 15, 2026 17:59
@charliermarsh charliermarsh force-pushed the charlie/class-pattern-exhaustiveness branch from 9837919 to d32c160 Compare June 15, 2026 20:05
@charliermarsh charliermarsh force-pushed the charlie/class-mapping-pattern-bindings branch from 966ed8b to 2ac5a92 Compare June 15, 2026 20:05
@charliermarsh charliermarsh force-pushed the charlie/class-pattern-exhaustiveness branch from d32c160 to 05c004c Compare June 15, 2026 20:31
@charliermarsh charliermarsh force-pushed the charlie/class-mapping-pattern-bindings branch from 2ac5a92 to bce8e8c Compare June 15, 2026 20:31
@charliermarsh charliermarsh force-pushed the charlie/class-pattern-exhaustiveness branch from 05c004c to 6a9e995 Compare June 15, 2026 20:45
@charliermarsh charliermarsh force-pushed the charlie/class-mapping-pattern-bindings branch from bce8e8c to 05d8601 Compare June 15, 2026 20:45
@charliermarsh charliermarsh force-pushed the charlie/class-pattern-exhaustiveness branch from 6a9e995 to 7a9bec2 Compare June 15, 2026 22:28
@charliermarsh charliermarsh force-pushed the charlie/class-mapping-pattern-bindings branch from 05d8601 to 220f8b4 Compare June 15, 2026 22:28
@charliermarsh charliermarsh force-pushed the charlie/class-pattern-exhaustiveness branch from 7a9bec2 to 5592892 Compare June 16, 2026 01:24
def test_widened_match_args_does_not_select_an_attribute(value: WidenedMatchArgs) -> None:
match value:
case WidenedMatchArgs(item):
reveal_type(item) # revealed: Unknown

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

A bit questionable, but consistent, I think.

@dhruvmanila dhruvmanila Jun 26, 2026

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.

Consistent with other type checkers?

I agree here though.

@dhruvmanila dhruvmanila left a comment

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.

Looks good!

def test_match_nested_generic_subclass_capture(value: GenericPatternBase[int]) -> list[int]:
match value:
case GenericPatternChild(items=items):
reveal_type(items) # revealed: Unknown

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.

Should there be a TODO here given that this should be list[int] instead of Unknown?

def test_widened_match_args_does_not_select_an_attribute(value: WidenedMatchArgs) -> None:
match value:
case WidenedMatchArgs(item):
reveal_type(item) # revealed: Unknown

@dhruvmanila dhruvmanila Jun 26, 2026

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.

Consistent with other type checkers?

I agree here though.

Comment on lines +955 to +956
case MissingClassPatternAttribute(missing=item) | (int() as item):
reveal_type(item) # revealed: int

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.

This is cool given that other type checkers reveal Unknown | int (or Any | int) which is incorrect (because of @final) given that the first pattern can never be matched here.

Comment on lines +1005 to +1008
def test_definite_class_alternative_removes_later_bindings(value: DefiniteFirst) -> None:
match value:
case (DefiniteFirst() as item) | UnreachableLater(payload=item):
reveal_type(item) # revealed: DefiniteFirst

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.

When I switch the order here as follows it reveals str | DefiniteFirst which I think is correct given that DefiniteFirst is not a final class in this example. Can we add both of these test cases here? One where the order is switched and the other which additionally also sets the DefiniteFirst as a final class?

def test_definite_class_alternative_removes_later_bindings(value: DefiniteFirst) -> None:
    match value:
        case UnreachableLater(payload=item) | (DefiniteFirst() as item):
            reveal_type(item)  # revealed: str | DefiniteFirst

class OverlapCaptureB:
member: int

class OverlapCaptureC(OverlapCaptureA, OverlapCaptureB): ...

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.

The OverlapCaptureC seems unused here?

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