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

Skip to content

[ty] Narrow tuple expression match subjects#25874

Merged
charliermarsh merged 9 commits into
mainfrom
charlie/fix-tuple-match-subject-narrowing
Jun 12, 2026
Merged

[ty] Narrow tuple expression match subjects#25874
charliermarsh merged 9 commits into
mainfrom
charlie/fix-tuple-match-subject-narrowing

Conversation

@charliermarsh

@charliermarsh charliermarsh commented Jun 11, 2026

Copy link
Copy Markdown
Member

Summary

Prior to this change, we narrowed a sequence value used as a match subject, but not the places used to construct an inline tuple or list display:

class A: ...
class A1(A): ...
class B: ...
class B1(B): ...

def f(a: A, b: B) -> None:
    match [a, b]:
        case [A1(), B1()]:
            reveal_type(a)  # revealed: A1
            reveal_type(b)  # revealed: B1

A match subject is evaluated once, so we retain the bindings read by its narrowable elements at subject evaluation. Successful sequence patterns recursively project their fixed prefix and suffix constraints through nested tuple and list displays onto names, attributes, and literal subscripts whose subject-time bindings are still live.

This supports later cases, starred patterns, repeated places, multiple reaching definitions, nested displays, and constrained or patterns without scanning cases for possible reassignments. Failed matches still do not project element constraints because they do not identify which element failed. Dictionary displays and starred subject displays remain conservative and are documented with TODO tests.

When combining or patterns, we distinguish impossible alternatives from possible alternatives that do not constrain the subject. Impossible alternatives are omitted, while unconstrained alternatives correctly prevent the overall pattern from narrowing.

Closes astral-sh/ty#3743.

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

astral-sh-bot Bot commented Jun 11, 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.36%. The percentage of expected errors that received a diagnostic held steady at 88.82%. The number of fully passing files held steady at 93/134.

@astral-sh-bot

astral-sh-bot Bot commented Jun 11, 2026

Copy link
Copy Markdown

Memory usage report

Memory usage unchanged ✅

@astral-sh-bot

astral-sh-bot Bot commented Jun 11, 2026

Copy link
Copy Markdown

ecosystem-analyzer results

Lint rule Added Removed Changed
unresolved-attribute 0 12 0
invalid-argument-type 0 6 0
unsupported-operator 0 2 0
invalid-assignment 0 0 1
no-matching-overload 0 1 0
Total 0 21 1

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

Raw diff (22 changes)
jax (https://github.com/google/jax)
- jax/experimental/mosaic/gpu/constraints.py:544:13 error[invalid-argument-type] Argument to function `splat_is_compatible_with_tiled` is incorrect: Expected `WGSplatFragLayout`, found `WGSplatFragLayout | WGStridedFragLayout | TiledLayout`
- jax/experimental/mosaic/gpu/constraints.py:544:28 error[invalid-argument-type] Argument to function `splat_is_compatible_with_tiled` is incorrect: Expected `TiledLayout`, found `WGSplatFragLayout | WGStridedFragLayout | TiledLayout`
- jax/experimental/mosaic/gpu/constraints.py:548:13 error[invalid-argument-type] Argument to function `_is_supported_tiled_relayout` is incorrect: Expected `TiledLayout`, found `WGSplatFragLayout | WGStridedFragLayout | TiledLayout`
- jax/experimental/mosaic/gpu/constraints.py:548:28 error[invalid-argument-type] Argument to function `_is_supported_tiled_relayout` is incorrect: Expected `TiledLayout`, found `WGSplatFragLayout | WGStridedFragLayout | TiledLayout`

kopf (https://github.com/nolar/kopf)
- kopf/_cogs/structs/references.py:305:45 error[no-matching-overload] No overload of bound method `Pattern.fullmatch` matches arguments
- kopf/_cogs/structs/references.py:306:24 error[unresolved-attribute] Attribute `split` is not defined on `Marker`, `(Resource, /) -> bool`, `None` in union `str | Marker | ((Resource, /) -> bool) | None`
- kopf/_cogs/structs/references.py:307:55 error[unresolved-attribute] Attribute `split` is not defined on `Marker`, `(Resource, /) -> bool`, `None` in union `str | Marker | ((Resource, /) -> bool) | None`
- kopf/_cogs/structs/references.py:308:53 error[unresolved-attribute] Attribute `split` is not defined on `Marker`, `(Resource, /) -> bool`, `None` in union `str | Marker | ((Resource, /) -> bool) | None`
- kopf/_cogs/structs/references.py:309:54 error[unresolved-attribute] Attribute `split` is not defined on `Marker`, `(Resource, /) -> bool`, `None` in union `str | Marker | ((Resource, /) -> bool) | None`
- kopf/_cogs/structs/references.py:310:45 error[unsupported-operator] Operator `in` is not supported between objects of type `Literal["."]` and `str | Marker | ((Resource, /) -> bool) | None`
- kopf/_cogs/structs/references.py:311:51 error[unresolved-attribute] Attribute `split` is not defined on `Marker`, `(Resource, /) -> bool`, `None` in union `str | Marker | ((Resource, /) -> bool) | None`
- kopf/_cogs/structs/references.py:312:54 error[unresolved-attribute] Attribute `split` is not defined on `Marker`, `(Resource, /) -> bool`, `None` in union `str | Marker | ((Resource, /) -> bool) | None`
- kopf/_cogs/structs/references.py:315:42 error[unsupported-operator] Operator `in` is not supported between objects of type `Literal["/"]` and `str | Marker | ((Resource, /) -> bool) | None`
- kopf/_cogs/structs/references.py:316:51 error[unresolved-attribute] Attribute `rsplit` is not defined on `Marker`, `(Resource, /) -> bool`, `None` in union `str | Marker | ((Resource, /) -> bool) | None`
- kopf/_cogs/structs/references.py:317:53 error[unresolved-attribute] Attribute `rsplit` is not defined on `Marker`, `(Resource, /) -> bool`, `None` in union `str | Marker | ((Resource, /) -> bool) | None`

pylint (https://github.com/pycqa/pylint)
- pylint/checkers/utils.py:2123:20 error[unresolved-attribute] Attribute `name` is not defined on `AssignAttr` in union `AssignName | AssignAttr`
- pylint/checkers/utils.py:2123:35 error[unresolved-attribute] Object of type `NodeNG | None` has no attribute `name`
- pylint/checkers/utils.py:2125:42 error[unresolved-attribute] Attribute `as_string` is not defined on `None` in union `NodeNG | None`
- pylint/checkers/utils.py:2127:45 error[invalid-argument-type] Argument to function `subscript_chain_is_equal` is incorrect: Expected `Subscript`, found `AssignName | AssignAttr`
- pylint/checkers/utils.py:2127:53 error[invalid-argument-type] Argument to function `subscript_chain_is_equal` is incorrect: Expected `Subscript`, found `NodeNG | None`

pytest (https://github.com/pytest-dev/pytest)
- src/_pytest/assertion/rewrite.py:1127:21 error[unresolved-attribute] Object of type `expr` has no attribute `target`
- src/_pytest/assertion/rewrite.py:1128:21 error[invalid-assignment] Invalid subscript assignment with key of type `@Todo` and value of type `expr` on object of type `dict[str, str]`
+ src/_pytest/assertion/rewrite.py:1128:21 error[invalid-assignment] Invalid subscript assignment with key of type `@Todo` and value of type `NamedExpr` on object of type `dict[str, str]`

Full report with detailed diff (timing results)

@charliermarsh charliermarsh force-pushed the charlie/fix-tuple-match-subject-narrowing branch from ccd1df9 to 3a6f6dd Compare June 11, 2026 17:20
@charliermarsh charliermarsh changed the title [ty] Conservatively narrow tuple expression match subjects [ty] Narrow tuple expression match subjects Jun 11, 2026
@charliermarsh charliermarsh force-pushed the charlie/fix-tuple-match-subject-narrowing branch 3 times, most recently from cb378a7 to 62e45a8 Compare June 11, 2026 17:44
@charliermarsh charliermarsh marked this pull request as ready for review June 11, 2026 17:46
@astral-sh-bot astral-sh-bot Bot requested a review from dhruvmanila June 11, 2026 17:48
@charliermarsh charliermarsh force-pushed the charlie/fix-tuple-match-subject-narrowing branch from 3ab5b1d to 0629acd Compare June 11, 2026 18:34
@charliermarsh charliermarsh marked this pull request as draft June 11, 2026 18:37
Comment thread crates/ty_python_core/src/builder.rs Outdated
@charliermarsh charliermarsh force-pushed the charlie/fix-tuple-match-subject-narrowing branch from 0629acd to e6f936e Compare June 11, 2026 19:09
@charliermarsh charliermarsh marked this pull request as ready for review June 11, 2026 19:43
@charliermarsh charliermarsh marked this pull request as draft June 11, 2026 20:08
@charliermarsh charliermarsh marked this pull request as ready for review June 11, 2026 23:47
PatternNarrowingResult::Impossible => return PatternNarrowingResult::Impossible,
PatternNarrowingResult::Possible(element_constraints) => {
constraints =
Self::merge_optional_constraints_and(constraints, element_constraints);

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.

Codex highlighted that this can cause exponential expansion but the example that it provided where this would happen doesn't look as convincing:

class Base: ...

class A1(Base): ...
class A2(Base): ...
class B1(Base): ...
class B2(Base): ...
class C1(Base): ...
class C2(Base): ...
class D1(Base): ...
class D2(Base): ...


def f(x: Base) -> None:
    match ([x], [x], [x], [x]):
        case (
            ([A1()] | [A2()]),
            ([B1()] | [B2()]),
            ([C1()] | [C2()]),
            ([D1()] | [D2()]),
        ):
            # Revealed type: `(A1 & B1 & C1 & D1) | (A1 & B1 & C1 & D2) | (A1 & B1 & C2 & D1) | (A1 & B1 & C2 & D2) | (A1 & B2 & C1 & D1) | (A1 & B2 & C1 & D2) | (A1 & B2 & C2 & D1) | (A1 & B2 & C2 & D2) | (A2 & B1 & C1 & D1) | (A2 & B1 & C1 & D2) | (A2 & B1 & C2 & D1) | (A2 & B1 & C2 & D2) | (A2 & B2 & C1 & D1) | (A2 & B2 & C1 & D2) | (A2 & B2 & C2 & D1) | (A2 & B2 & C2 & D2)`
            reveal_type(x)

The above example doesn't cause a performance regression but increasing the number of list in the subject expression and the number of unions in the case expression will do so.

It's not convincing because I don't think a real world code base would have such code which is why it might not be worth spending time here.

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.

I see a few reference to "projected" / "projection" which I'm not exactly sure what the meaning is in this context. Is it meant to be synonymous to narrowing?

@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, thanks!

@charliermarsh charliermarsh force-pushed the charlie/fix-tuple-match-subject-narrowing branch from 9eb3211 to 8beeb84 Compare June 12, 2026 16:01
@charliermarsh charliermarsh merged commit 3b4ed26 into main Jun 12, 2026
58 checks passed
@charliermarsh charliermarsh deleted the charlie/fix-tuple-match-subject-narrowing branch June 12, 2026 16:48
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.

Another edge case for tuple match case not narrowing

3 participants