Skip to content

[ty] Preserve constrained TypeVar identity in narrowing intersections#23850

Merged
charliermarsh merged 1 commit intomainfrom
charlie/inter
Mar 10, 2026
Merged

[ty] Preserve constrained TypeVar identity in narrowing intersections#23850
charliermarsh merged 1 commit intomainfrom
charlie/inter

Conversation

@charliermarsh
Copy link
Member

Summary

Closes astral-sh/ty#2782.

@astral-sh-bot
Copy link

astral-sh-bot bot commented Mar 10, 2026

Typing conformance results

No changes detected ✅

Current numbers
The percentage of diagnostics emitted that were expected errors held steady at 85.05%. The percentage of expected errors that received a diagnostic held steady at 78.05%. The number of fully passing files held steady at 63/132.

@astral-sh-bot
Copy link

astral-sh-bot bot commented Mar 10, 2026

mypy_primer results

Changes were detected when running on open source projects
vision (https://github.com/pytorch/vision)
- torchvision/datasets/utils.py:416:16: error[invalid-return-type] Return type does not match returned value: expected `T@verify_str_arg`, found `str`
- torchvision/datasets/utils.py:426:12: error[invalid-return-type] Return type does not match returned value: expected `T@verify_str_arg`, found `str`
- Found 1419 diagnostics
+ Found 1417 diagnostics

mongo-python-driver (https://github.com/mongodb/mongo-python-driver)
- bson/regex.py:97:28: error[invalid-assignment] Object of type `str | bytes` is not assignable to `_T@Regex`: Incompatible value of type `str | bytes`
- Found 436 diagnostics
+ Found 435 diagnostics

Tanjun (https://github.com/FasterSpeeding/Tanjun)
+ tanjun/annotations.py:1949:74: error[invalid-argument-type] Argument to bound method `__init__` is incorrect: Expected `int | float`, found `_NumberT@__getitem__ & ~int`
+ tanjun/annotations.py:1996:74: error[invalid-argument-type] Argument to bound method `__init__` is incorrect: Expected `int | float`, found `_NumberT@__getitem__ & ~int`
- Found 129 diagnostics
+ Found 131 diagnostics

xarray (https://github.com/pydata/xarray)
- xarray/coding/cftime_offsets.py:1517:16: error[invalid-return-type] Return type does not match returned value: expected `T_FreqStr@_legacy_to_new_freq`, found `str & ~AlwaysFalsy`
- xarray/coding/cftime_offsets.py:1520:16: error[invalid-assignment] Object of type `str` is not assignable to `T_FreqStr@_legacy_to_new_freq`
- xarray/coding/cftime_offsets.py:1522:16: error[invalid-assignment] Object of type `str` is not assignable to `T_FreqStr@_legacy_to_new_freq`
- xarray/coding/cftime_offsets.py:1524:16: error[invalid-assignment] Object of type `str` is not assignable to `T_FreqStr@_legacy_to_new_freq`
- xarray/coding/cftime_offsets.py:1530:20: error[invalid-assignment] Object of type `str` is not assignable to `T_FreqStr@_legacy_to_new_freq`
- xarray/coding/cftime_offsets.py:1532:20: error[invalid-assignment] Object of type `str` is not assignable to `T_FreqStr@_legacy_to_new_freq`
- xarray/coding/cftime_offsets.py:1535:20: error[invalid-assignment] Object of type `str` is not assignable to `T_FreqStr@_legacy_to_new_freq`
- xarray/coding/cftime_offsets.py:1538:20: error[invalid-assignment] Object of type `str` is not assignable to `T_FreqStr@_legacy_to_new_freq`
- xarray/coding/cftime_offsets.py:1540:16: error[invalid-assignment] Object of type `str` is not assignable to `T_FreqStr@_legacy_to_new_freq`
- xarray/coding/cftime_offsets.py:1542:16: error[invalid-assignment] Object of type `str` is not assignable to `T_FreqStr@_legacy_to_new_freq`
- xarray/coding/cftime_offsets.py:1544:16: error[invalid-assignment] Object of type `str` is not assignable to `T_FreqStr@_legacy_to_new_freq`
- xarray/coding/cftime_offsets.py:1546:16: error[invalid-assignment] Object of type `str` is not assignable to `T_FreqStr@_legacy_to_new_freq`
- xarray/coding/cftime_offsets.py:1548:16: error[invalid-assignment] Object of type `str` is not assignable to `T_FreqStr@_legacy_to_new_freq`
- xarray/coding/cftime_offsets.py:1550:12: error[invalid-return-type] Return type does not match returned value: expected `T_FreqStr@_legacy_to_new_freq`, found `(str & ~AlwaysFalsy) | T_FreqStr@_legacy_to_new_freq`
- xarray/computation/rolling.py:1216:20: error[invalid-return-type] Return type does not match returned value: expected `T_Xarray@Coarsen`, found `DataArray`
- xarray/core/parallel.py:139:12: error[invalid-return-type] Return type does not match returned value: expected `T_Xarray@infer_template`, found `Dataset | DataArray`
- Found 1713 diagnostics
+ Found 1697 diagnostics

prefect (https://github.com/PrefectHQ/prefect)
- src/prefect/utilities/templating.py:92:47: error[invalid-argument-type] Argument to function `find_placeholders` is incorrect: Argument type `object` does not satisfy constraints (`str`, `int`, `int | float`, `bool`, `dict[Any, Any]`, `list[Any]`, `None`) of type variable `T`
- src/prefect/utilities/templating.py:172:20: error[invalid-return-type] Return type does not match returned value: expected `T@apply_values | type[NotSet]`, found `str`
- src/prefect/utilities/templating.py:182:24: error[invalid-return-type] Return type does not match returned value: expected `T@apply_values | type[NotSet]`, found `str`
- src/prefect/utilities/templating.py:216:36: error[invalid-assignment] Object of type `str | Unknown` is not assignable to `T@apply_values`
- src/prefect/utilities/templating.py:216:36: error[no-matching-overload] No overload of bound method `replace` matches arguments
- src/prefect/utilities/templating.py:216:36: warning[possibly-missing-attribute] Attribute `replace` may be missing on object of type `str | T@apply_values`
- src/prefect/utilities/templating.py:218:32: error[invalid-assignment] Object of type `str | Unknown` is not assignable to `T@apply_values`
- src/prefect/utilities/templating.py:218:32: error[no-matching-overload] No overload of bound method `replace` matches arguments
- src/prefect/utilities/templating.py:218:32: warning[possibly-missing-attribute] Attribute `replace` may be missing on object of type `str | T@apply_values`
- src/prefect/utilities/templating.py:220:20: error[invalid-return-type] Return type does not match returned value: expected `T@apply_values | type[NotSet]`, found `str | T@apply_values`
- src/prefect/utilities/templating.py:224:29: error[no-matching-overload] No overload of function `apply_values` matches arguments
- src/prefect/utilities/templating.py:232:17: error[invalid-assignment] Invalid subscript assignment with key of type `object` and value of type `Unknown & ~<class 'NotSet'>` on object of type `dict[str, Any]`
- src/prefect/utilities/templating.py:234:17: error[invalid-assignment] Invalid subscript assignment with key of type `object` and value of type `object` on object of type `dict[str, Any]`
- src/prefect/utilities/templating.py:330:29: error[no-matching-overload] No overload of bound method `get` matches arguments
- src/prefect/utilities/templating.py:339:13: error[invalid-assignment] Invalid subscript assignment with key of type `object` and value of type `Unknown` on object of type `dict[str, Any]`
- src/prefect/utilities/templating.py:355:20: error[invalid-return-type] Return type does not match returned value: expected `T@resolve_block_document_references | dict[str, Any]`, found `str`
- src/prefect/utilities/templating.py:431:20: error[invalid-return-type] Return type does not match returned value: expected `T@resolve_variables`, found `str`
- src/prefect/utilities/templating.py:451:36: error[invalid-assignment] Object of type `str | Unknown` is not assignable to `T@resolve_variables`
- src/prefect/utilities/templating.py:451:36: error[no-matching-overload] No overload of bound method `replace` matches arguments
- src/prefect/utilities/templating.py:451:36: warning[possibly-missing-attribute] Attribute `replace` may be missing on object of type `str | T@resolve_variables`
- src/prefect/utilities/templating.py:453:36: error[invalid-assignment] Object of type `str | Unknown` is not assignable to `T@resolve_variables`
- src/prefect/utilities/templating.py:453:36: error[no-matching-overload] No overload of bound method `replace` matches arguments
- src/prefect/utilities/templating.py:453:36: warning[possibly-missing-attribute] Attribute `replace` may be missing on object of type `str | T@resolve_variables`
- src/prefect/utilities/templating.py:454:20: error[invalid-return-type] Return type does not match returned value: expected `T@resolve_variables`, found `str | T@resolve_variables`
- src/prefect/utilities/templating.py:456:16: error[invalid-return-type] Return type does not match returned value: expected `T@resolve_variables`, found `dict[object, Unknown]`
+ src/prefect/utilities/templating.py:456:16: error[invalid-return-type] Return type does not match returned value: expected `T@resolve_variables`, found `dict[Unknown, Unknown]`
- Found 5884 diagnostics
+ Found 5860 diagnostics

scikit-build-core (https://github.com/scikit-build/scikit-build-core)
+ src/scikit_build_core/build/wheel.py:99:20: error[no-matching-overload] No overload of bound method `__init__` matches arguments
- Found 59 diagnostics
+ Found 60 diagnostics

cryptography (https://github.com/pyca/cryptography)
- src/cryptography/x509/name.py:179:47: error[invalid-assignment] Object of type `bytes | str` is not assignable to `NameAttributeValueType@NameAttribute`: Incompatible value of type `bytes | str`
- Found 38 diagnostics
+ Found 37 diagnostics

hydpy (https://github.com/hydpy-dev/hydpy)
- hydpy/core/devicetools.py:2565:20: error[invalid-return-type] Return type does not match returned value: expected `tuple[t@_make_tuple | None, t@_make_tuple | None]`, found `tuple[None | str | int, None | str | int]`
- hydpy/core/devicetools.py:2748:20: error[invalid-return-type] Return type does not match returned value: expected `tuple[t@_make_tuple | None, t@_make_tuple | None]`, found `tuple[None | str | int, None | str | int]`
- hydpy/exe/xmltools.py:1866:34: error[invalid-argument-type] Argument to bound method `append` is incorrect: Expected `_TypeGetOrChangeItem@_get_items_of_certain_item_types`, found `GetItem | (_TypeGetOrChangeItem@_get_items_of_certain_item_types & ChangeItem)`
- hydpy/exe/xmltools.py:1871:38: error[invalid-argument-type] Argument to bound method `append` is incorrect: Expected `_TypeGetOrChangeItem@_get_items_of_certain_item_types`, found `GetItem | (_TypeGetOrChangeItem@_get_items_of_certain_item_types & ChangeItem)`
- hydpy/exe/xmltools.py:2370:20: error[invalid-assignment] Object of type `SetItem` is not assignable to `_TypeSetOrAddOrMultiplyItem@_get_changeitem`
- Found 1067 diagnostics
+ Found 1062 diagnostics

static-frame (https://github.com/static-frame/static-frame)
- static_frame/core/node_values.py:176:20: error[invalid-return-type] Return type does not match returned value: expected `TVContainer_co@InterfaceValues`, found `Top[Series[Any, Any]]`
- Found 1869 diagnostics
+ Found 1868 diagnostics

pandas (https://github.com/pandas-dev/pandas)
- pandas/core/algorithms.py:208:16: error[invalid-return-type] Return type does not match returned value: expected `ArrayLikeT@_reconstruct_data`, found `ExtensionArray`
- pandas/core/arrays/datetimes.py:2979:21: error[invalid-assignment] Object of type `Timestamp` is not assignable to `_TimestampNoneT1@_maybe_normalize_endpoints`
- pandas/core/arrays/datetimes.py:2982:19: error[invalid-assignment] Object of type `Timestamp` is not assignable to `_TimestampNoneT2@_maybe_normalize_endpoints`
- pandas/core/resample.py:3191:12: error[invalid-return-type] Return type does not match returned value: expected `FreqIndexT@_asfreq_compat`, found `PeriodIndex | DatetimeIndex | TimedeltaIndex`
+ pandas/core/resample.py:3191:12: error[invalid-return-type] Return type does not match returned value: expected `FreqIndexT@_asfreq_compat`, found `DatetimeIndex | TimedeltaIndex`
- Found 4585 diagnostics
+ Found 4582 diagnostics

core (https://github.com/home-assistant/core)
- homeassistant/components/asuswrt/helpers.py:44:16: error[invalid-return-type] Return type does not match returned value: expected `T@translate_to_legacy`, found `dict[Unknown, object]`
+ homeassistant/components/asuswrt/helpers.py:44:16: error[invalid-return-type] Return type does not match returned value: expected `T@translate_to_legacy`, found `dict[Unknown, Unknown]`
- homeassistant/components/asuswrt/helpers.py:44:17: error[no-matching-overload] No overload of bound method `get` matches arguments
- Found 12093 diagnostics
+ Found 12092 diagnostics

@astral-sh-bot
Copy link

astral-sh-bot bot commented Mar 10, 2026

Memory usage report

Summary

Project Old New Diff Outcome
prefect 701.47MB 701.56MB +0.01% (84.12kB)
flake8 47.89MB 47.89MB -
sphinx 265.19MB 265.19MB -
trio 117.81MB 117.81MB -

Significant changes

Click to expand detailed breakdown

prefect

Name Old New Diff Outcome
Type<'db>::class_member_with_policy_ 17.21MB 17.24MB +0.19% (32.73kB)
check_file_impl 17.83MB 17.81MB -0.16% (28.58kB)
infer_definition_types 88.41MB 88.42MB +0.01% (12.44kB)
infer_expression_types_impl 60.35MB 60.36MB +0.02% (11.32kB)
Type<'db>::member_lookup_with_policy_ 15.34MB 15.35MB +0.06% (9.16kB)
Type<'db>::apply_specialization_ 3.58MB 3.58MB +0.23% (8.31kB)
Type<'db>::try_call_dunder_get_ 10.43MB 10.44MB +0.08% (8.23kB)
StaticClassLiteral<'db>::implicit_attribute_inner_ 9.70MB 9.71MB +0.06% (6.45kB)
StaticClassLiteral<'db>::implicit_attribute_inner_::interned_arguments 5.12MB 5.12MB +0.11% (5.53kB)
is_redundant_with_impl::interned_arguments 5.34MB 5.34MB +0.06% (3.35kB)
is_redundant_with_impl 5.55MB 5.55MB +0.06% (3.34kB)
IntersectionType 2.30MB 2.30MB +0.14% (3.33kB)
IntersectionType<'db>::from_two_elements_ 342.09kB 345.32kB +0.94% (3.23kB)
Type<'db>::apply_specialization_::interned_arguments 2.86MB 2.87MB +0.07% (2.03kB)
infer_scope_types_impl 52.63MB 52.63MB -0.00% (1.84kB)
... 17 more

@charliermarsh charliermarsh added ty Multi-file analysis & type inference ecosystem-analyzer labels Mar 10, 2026
def f[T: (P, Q)](t: T) -> None:
if isinstance(t, P):
reveal_type(t) # revealed: P
reveal_type(t) # revealed: T@f & P
Copy link
Member Author

Choose a reason for hiding this comment

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

These look like regressions but I think they might be correct?

Given:

class P:
    def p(self) -> None: ...

class Q:
    def q(self) -> None: ...

class Both(P, Q):
    pass

def f[T: (P, Q)](t: T) -> T:
    if isinstance(t, P):
        reveal_type(t)  # revealed: T@f & P
        t.p()
        return t
    else:
        reveal_type(t)  # revealed: Q & ~P
        t.q()
        return t

def takes_q(x: Q) -> Q:
    return f(x)

both = Both()
q: Q = takes_q(both)  # valid; runtime goes into the `if` branch

Copy link
Member

Choose a reason for hiding this comment

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

I agree!

@astral-sh-bot
Copy link

astral-sh-bot bot commented Mar 10, 2026

ecosystem-analyzer results

Lint rule Added Removed Changed
invalid-await 0 40 0
invalid-assignment 0 24 0
invalid-return-type 0 17 3
no-matching-overload 0 7 0
invalid-argument-type 2 3 0
possibly-missing-attribute 0 4 0
Total 2 95 3

Full report with detailed diff (timing results)

@charliermarsh
Copy link
Member Author

I understand the spirit of this change but I'm not familiar with the implementation here. The ecosystem diagnostics do look like false positives being removed, though.

@charliermarsh charliermarsh marked this pull request as ready for review March 10, 2026 01:50
@charliermarsh charliermarsh assigned dcreager and unassigned oconnor663 Mar 10, 2026
@astral-sh-bot astral-sh-bot bot requested a review from oconnor663 March 10, 2026 01:51
Copy link
Member

@dcreager dcreager left a comment

Choose a reason for hiding this comment

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

Looks good!

def f[T: (P, Q)](t: T) -> None:
if isinstance(t, P):
reveal_type(t) # revealed: P
reveal_type(t) # revealed: T@f & P
Copy link
Member

Choose a reason for hiding this comment

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

I agree!

@charliermarsh charliermarsh merged commit be723fb into main Mar 10, 2026
51 checks passed
@charliermarsh charliermarsh deleted the charlie/inter branch March 10, 2026 20:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ecosystem-analyzer ty Multi-file analysis & type inference

Projects

None yet

Development

Successfully merging this pull request may close these issues.

narrow from union to constrained typevar type

3 participants