Skip to content

Conversation

dcreager
Copy link
Member

@dcreager dcreager commented Oct 2, 2025

We have to track whether a typevar appears in a position where it's inferable or not. In a non-inferable position (in the body of the generic class or function that binds it), assignability must hold for every possible specialization of the typevar. In an inferable position, it only needs to hold for some specialization. #20093 is working on using constraint sets to model assignability of typevars, and the constraint sets that we produce will be the same for inferable vs non-inferable typevars; what changes is what we compare that constraint set to. (For a non-inferable typevar, the constraint set must equal the set of valid specializations; for an inferable typevar, it must not be never.)

When I first added support for tracking inferable vs non-inferable typevars, it seemed like it would be easiest to have separate Type variants for each. The alternative (which lines up with the Δ set in POPL15) would be to explicitly plumb through a list of inferable typevars through our type property methods. That seemed cumbersome.

In retrospect, that was the wrong decision. We've had to jump through hoops to translate types between the inferable and non-inferable variants, which has been quite brittle. Combined with the original point above, that much of the assignability logic will become more identical between inferable and non-inferable, there is less justification for the two Type variants. And plumbing an extra inferable parameter through all of these methods turns out to not be as bad as I anticipated.

@dcreager dcreager added internal An internal refactor or improvement ty Multi-file analysis & type inference labels Oct 2, 2025
Copy link
Contributor

github-actions bot commented Oct 2, 2025

Diagnostic diff on typing conformance tests

No changes detected when running ty on typing conformance tests ✅

Copy link
Contributor

github-actions bot commented Oct 2, 2025

mypy_primer results

Changes were detected when running on open source projects
werkzeug (https://github.com/pallets/werkzeug)
- src/werkzeug/local.py:511:24: error[invalid-return-type] Return type does not match returned value: expected `T@LocalProxy`, found `T@LocalProxy | ~None | Unknown`
- Found 384 diagnostics
+ Found 383 diagnostics

boostedblob (https://github.com/hauntsaninja/boostedblob)
- boostedblob/request.py:25:33: error[no-matching-overload] No overload of function `field` matches arguments
- boostedblob/request.py:29:34: error[no-matching-overload] No overload of function `field` matches arguments
- boostedblob/request.py:76:33: error[no-matching-overload] No overload of function `field` matches arguments
- boostedblob/request.py:80:34: error[no-matching-overload] No overload of function `field` matches arguments
- boostedblob/request.py:85:51: error[no-matching-overload] No overload of function `field` matches arguments
- Found 76 diagnostics
+ Found 71 diagnostics

pytest (https://github.com/pytest-dev/pytest)
- src/_pytest/python.py:1064:39: error[no-matching-overload] No overload of function `field` matches arguments
- Found 489 diagnostics
+ Found 488 diagnostics

typeshed-stats (https://github.com/AlexWaygood/typeshed-stats)
+ src/typeshed_stats/gather.py:1000:13: error[invalid-argument-type] Argument to bound method `from_lines` is incorrect: Expected `str | ((str, /) -> Pattern)`, found `<class 'GitWildMatchPattern'>`
- Found 24 diagnostics
+ Found 25 diagnostics

schemathesis (https://github.com/schemathesis/schemathesis)
- src/schemathesis/config/_projects.py:534:9: error[invalid-assignment] Object of type `Unknown` is not assignable to attribute `_parent` on type `T@from_hierarchy | Unknown`
- Found 322 diagnostics
+ Found 321 diagnostics

mitmproxy (https://github.com/mitmproxy/mitmproxy)
- test/mitmproxy/contentviews/test__utils.py:23:38: error[invalid-argument-type] Argument to function `make_metadata` is incorrect: Expected `Message | TCPMessage | UDPMessage | WebSocketMessage | DNSMessage`, found `Response | None`
+ test/mitmproxy/contentviews/test__utils.py:23:38: error[invalid-argument-type] Argument to function `make_metadata` is incorrect: Expected `ContentviewMessage`, found `Response | None`

schema_salad (https://github.com/common-workflow-language/schema_salad)
- schema_salad/sourceline.py:17:33: error[invalid-argument-type] Argument to function `_add_lc_filename` is incorrect: Argument type `AnyStr@_add_lc_filename` does not satisfy constraints (`str`, `bytes`) of type variable `AnyStr`
- schema_salad/sourceline.py:20:33: error[invalid-argument-type] Argument to function `_add_lc_filename` is incorrect: Argument type `AnyStr@_add_lc_filename` does not satisfy constraints (`str`, `bytes`) of type variable `AnyStr`
- Found 166 diagnostics
+ Found 164 diagnostics

xarray (https://github.com/pydata/xarray)
- xarray/backends/api.py:1352:42: error[invalid-argument-type] Argument to function `_remove_path` is incorrect: Expected `NestedSequence[_FLike@_remove_path]`, found `(_FLike@_remove_path & Top[list[Unknown]]) | (NestedSequence[_FLike@_remove_path] & Top[list[Unknown]])`
+ xarray/tests/test_namedarray.py:446:19: error[no-matching-overload] No overload of bound method `_new` matches arguments

vision (https://github.com/pytorch/vision)
- references/detection/coco_utils.py:165:60: warning[possibly-unresolved-reference] Name `keypoints` used when possibly not defined
- Found 1481 diagnostics
+ Found 1480 diagnostics

strawberry (https://github.com/strawberry-graphql/strawberry)
- strawberry/federation/object_type.py:90:9: error[invalid-argument-type] Argument to function `type` is incorrect: Expected `T@_impl_type`, found `T@_impl_type | None`
- strawberry/types/base.py:272:63: error[no-matching-overload] No overload of function `field` matches arguments
- Found 382 diagnostics
+ Found 380 diagnostics

meson (https://github.com/mesonbuild/meson)
- mesonbuild/modules/hotdoc.py:52:12: error[invalid-return-type] Return type does not match returned value: expected `list[_T@ensure_list]`, found `(_T@ensure_list & Top[list[Unknown]]) | list[_T@ensure_list]`
- Found 885 diagnostics
+ Found 884 diagnostics

openlibrary (https://github.com/internetarchive/openlibrary)
+ openlibrary/book_providers.py:747:9: error[invalid-argument-type] Argument to function `multisort_best` is incorrect: Expected `list[tuple[Literal["min", "max"], (@Todo, /) -> int | float]]`, found `list[Unknown | tuple[str, (rec) -> Unknown]]`
- Found 848 diagnostics
+ Found 849 diagnostics

mkdocs (https://github.com/mkdocs/mkdocs)
- mkdocs/config/config_options.py:219:20: error[invalid-return-type] Return type does not match returned value: expected `list[T@ListOfItems]`, found `Top[list[Unknown]] & ~AlwaysTruthy`
- Found 206 diagnostics
+ Found 205 diagnostics

prefect (https://github.com/PrefectHQ/prefect)
+ src/prefect/_internal/concurrency/api.py:39:16: warning[redundant-cast] Value is already of type `Call[T@cast_to_call]`
- src/prefect/states.py:365:16: error[invalid-return-type] Return type does not match returned value: expected `State[R@return_value_to_state]`, found `R@return_value_to_state & Top[State[Any]]`

scikit-build-core (https://github.com/scikit-build/scikit-build-core)
- src/scikit_build_core/build/_wheelfile.py:51:22: error[no-matching-overload] No overload of function `field` matches arguments
- src/scikit_build_core/settings/skbuild_docs_sphinx.py:27:36: error[no-matching-overload] No overload of function `field` matches arguments
- src/scikit_build_core/settings/skbuild_docs_sphinx.py:43:30: error[no-matching-overload] No overload of function `field` matches arguments
- Found 52 diagnostics
+ Found 49 diagnostics

ibis (https://github.com/ibis-project/ibis)
- ibis/util.py:107:16: error[invalid-return-type] Return type does not match returned value: expected `list[V@promote_list]`, found `(V@promote_list & Top[list[Unknown]]) | (Iterable[V@promote_list] & Top[list[Unknown]])`
- Found 3293 diagnostics
+ Found 3292 diagnostics

dd-trace-py (https://github.com/DataDog/dd-trace-py)
- ddtrace/contrib/internal/pymongo/client.py:340:68: warning[possibly-unresolved-reference] Name `cursor` used when possibly not defined
- Found 7577 diagnostics
+ Found 7576 diagnostics

bokeh (https://github.com/bokeh/bokeh)
- src/bokeh/core/property/container.py:136:24: error[invalid-return-type] Return type does not match returned value: expected `PropertyValueList[T@List]`, found `list[T@List] & Top[PropertyValueList[Unknown]]`
- src/bokeh/core/property/container.py:161:24: error[invalid-return-type] Return type does not match returned value: expected `PropertyValueSet[T@Set]`, found `set[T@Set] & Top[PropertyValueSet[Unknown]]`
- src/bokeh/layouts.py:668:24: error[invalid-return-type] Return type does not match returned value: expected `list[L@_parse_children_arg]`, found `(L@_parse_children_arg & Top[list[Unknown]]) | list[L@_parse_children_arg]`
- Found 465 diagnostics
+ Found 462 diagnostics

jax (https://github.com/google/jax)
- jax/_src/pallas/fuser/block_spec.py:1517:39: error[non-subscriptable] Cannot subscript object of type `None` with no `__getitem__` method
+ jax/_src/tree_util.py:440:29: error[invalid-argument-type] Argument to function `reduce` is incorrect: Expected `(T@tree_reduce & ~Unspecified, Unknown, /) -> T@tree_reduce & ~Unspecified`, found `(T@tree_reduce, Any, /) -> T@tree_reduce`

pandas (https://github.com/pandas-dev/pandas)
- pandas/core/frame.py:2170:16: error[invalid-return-type] Return type does not match returned value: expected `MutableMappingT@to_dict | list[MutableMappingT@to_dict]`, found `@Todo | MutableMappingT@to_dict | <class 'dict'> | list[@Todo | MutableMappingT@to_dict | <class 'dict'>]`
- Found 3386 diagnostics
+ Found 3385 diagnostics

sympy (https://github.com/sympy/sympy)
- sympy/functions/combinatorial/numbers.py:3193:61: warning[possibly-unresolved-reference] Name `s` used when possibly not defined
- sympy/ntheory/factor_.py:148:43: warning[possibly-unresolved-reference] Name `facs` used when possibly not defined
- sympy/ntheory/factor_.py:148:43: warning[possibly-unresolved-reference] Name `facs` used when possibly not defined
- sympy/polys/densetools.py:292:36: error[invalid-argument-type] Argument to function `dup_TC` is incorrect: Expected `Domain[Es@convert | Expr | int | ... omitted 3 union elements]`, found `Domain[Er@dup_eval]`
- sympy/polys/rings.py:871:22: error[unresolved-attribute] Type `object` has no attribute `domain`
+ sympy/polys/rings.py:872:55: error[unresolved-attribute] Type `object` has no attribute `is_element`
- sympy/polys/rings.py:880:20: error[invalid-return-type] Return type does not match returned value: expected `PolyElement[Er@PolyElement] | PolyElement[PolyElement[Er@PolyElement]]`, found `Top[PolyElement[Unknown]]`
- sympy/polys/rings.py:931:22: error[unresolved-attribute] Type `object` has no attribute `domain`
+ sympy/polys/rings.py:932:55: error[unresolved-attribute] Type `object` has no attribute `is_element`
- sympy/polys/rings.py:940:20: error[invalid-return-type] Return type does not match returned value: expected `PolyElement[Er@PolyElement] | PolyElement[PolyElement[Er@PolyElement]]`, found `Top[PolyElement[Unknown]]`
- sympy/polys/rings.py:1008:22: error[unresolved-attribute] Type `object` has no attribute `domain`
+ sympy/polys/rings.py:1009:55: error[unresolved-attribute] Type `object` has no attribute `is_element`
+ sympy/polys/rings.py:1098:49: warning[unused-ignore-comment] Unused blanket `type: ignore` directive
+ sympy/polys/rings.py:1133:46: warning[unused-ignore-comment] Unused blanket `type: ignore` directive
+ sympy/polys/rings.py:1169:50: warning[unused-ignore-comment] Unused blanket `type: ignore` directive
+ sympy/polys/rings.py:1206:50: warning[unused-ignore-comment] Unused blanket `type: ignore` directive
- sympy/polys/rings.py:1017:20: error[invalid-return-type] Return type does not match returned value: expected `PolyElement[Er@PolyElement] | PolyElement[PolyElement[Er@PolyElement]]`, found `Top[PolyElement[Unknown]]`
- sympy/polys/rings.py:1095:28: error[unresolved-attribute] Type `object` has no attribute `domain`
- sympy/polys/rings.py:1096:21: error[unresolved-attribute] Type `object` has no attribute `domain`
- sympy/polys/rings.py:1130:28: error[unresolved-attribute] Type `object` has no attribute `domain`
- sympy/polys/rings.py:1131:21: error[unresolved-attribute] Type `object` has no attribute `domain`
- sympy/polys/rings.py:1166:28: error[unresolved-attribute] Type `object` has no attribute `domain`
- sympy/polys/rings.py:1167:21: error[unresolved-attribute] Type `object` has no attribute `domain`
- sympy/polys/rings.py:1203:28: error[unresolved-attribute] Type `object` has no attribute `domain`
- sympy/polys/rings.py:1204:21: error[unresolved-attribute] Type `object` has no attribute `domain`
- sympy/polys/rings.py:3456:17: error[unsupported-operator] Operator `*` is unsupported between objects of type `PolyElement[Er@PolyElement] | Unknown` and `Unknown | PolyElement[Er@PolyElement] | int | float`
- Found 11029 diagnostics
+ Found 11017 diagnostics

core (https://github.com/home-assistant/core)
- homeassistant/auth/permissions/merge.py:63:13: error[invalid-assignment] Method `__setitem__` of type `bound method Top[dict[Unknown, Unknown]].__setitem__(key: Never, value: Never, /) -> None` cannot be called with a key of type `Never` and a value of type `Mapping[str, Mapping[str, Mapping[str, bool] | bool | None] | bool | None] | bool | None` on object of type `Mapping[str, SubCategoryType] & Top[dict[Unknown, Unknown]]`
+ homeassistant/auth/permissions/merge.py:63:13: error[invalid-assignment] Method `__setitem__` of type `bound method Top[dict[Unknown, Unknown]].__setitem__(key: Never, value: Never, /) -> None` cannot be called with a key of type `Never` and a value of type `CategoryType` on object of type `Mapping[str, SubCategoryType] & Top[dict[Unknown, Unknown]]`
- homeassistant/components/google_assistant/report_state.py:164:54: error[invalid-argument-type] Argument to function `create_checker` is incorrect: Expected `((HomeAssistant, str, dict[Unknown, Unknown] | MappingProxyType[Unknown, Unknown], Any, str, dict[Unknown, Unknown] | MappingProxyType[Unknown, Unknown], Any, /) -> bool | None) | None`, found `def extra_significant_check(hass: HomeAssistant, old_state: str, old_attrs: dict[Unknown, Unknown], old_extra_arg: dict[Unknown, Unknown], new_state: str, new_attrs: dict[Unknown, Unknown], new_extra_arg: dict[Unknown, Unknown]) -> Unknown`
+ homeassistant/components/google_assistant/report_state.py:164:54: error[invalid-argument-type] Argument to function `create_checker` is incorrect: Expected `ExtraCheckTypeFunc | None`, found `def extra_significant_check(hass: HomeAssistant, old_state: str, old_attrs: dict[Unknown, Unknown], old_extra_arg: dict[Unknown, Unknown], new_state: str, new_attrs: dict[Unknown, Unknown], new_extra_arg: dict[Unknown, Unknown]) -> Unknown`
- homeassistant/components/otbr/__init__.py:40:57: error[invalid-argument-type] Argument to function `async_register_firmware_info_provider` is incorrect: Expected `SyncHardwareFirmwareInfoModule | AsyncHardwareFirmwareInfoModule`, found `<module 'homeassistant.components.otbr.homeassistant_hardware'>`
+ homeassistant/components/otbr/__init__.py:40:57: error[invalid-argument-type] Argument to function `async_register_firmware_info_provider` is incorrect: Expected `HardwareFirmwareInfoModule`, found `<module 'homeassistant.components.otbr.homeassistant_hardware'>`
- homeassistant/components/owntracks/__init__.py:148:9: error[invalid-assignment] Method `__setitem__` of type `(bound method dict[str, Any].__setitem__(key: str, value: Any, /) -> None) | (Overload[(key: SupportsIndex, value: Any, /) -> None, (key: slice[Any, Any, Any], value: Iterable[Any], /) -> None])` cannot be called with a key of type `Literal["topic"]` and a value of type `Unknown` on object of type `dict[str, Any] | list[Any] | str | ... omitted 3 union elements`
+ homeassistant/components/owntracks/__init__.py:148:9: error[invalid-assignment] Method `__setitem__` of type `(bound method JsonValueType.__setitem__(key: str, value: dict[str, Any] | list[Any] | str | ... omitted 3 union elements, /) -> None) | (Overload[(key: SupportsIndex, value: dict[str, Any] | list[Any] | str | ... omitted 3 union elements, /) -> None, (key: slice[Any, Any, Any], value: Iterable[dict[str, Any] | list[Any] | str | ... omitted 3 union elements], /) -> None])` cannot be called with a key of type `Literal["topic"]` and a value of type `Unknown` on object of type `JsonValueType`
- homeassistant/components/random/config_flow.py:113:82: warning[possibly-unresolved-reference] Name `units` used when possibly not defined
- homeassistant/components/random/config_flow.py:113:82: warning[possibly-unresolved-reference] Name `units` used when possibly not defined
- homeassistant/components/template/config_flow.py:433:82: warning[possibly-unresolved-reference] Name `units` used when possibly not defined
- homeassistant/components/template/config_flow.py:433:82: warning[possibly-unresolved-reference] Name `units` used when possibly not defined
- homeassistant/components/template/config_flow.py:457:54: warning[possibly-unresolved-reference] Name `state_classes` used when possibly not defined
- homeassistant/components/template/config_flow.py:457:54: warning[possibly-unresolved-reference] Name `state_classes` used when possibly not defined
- homeassistant/components/zha/__init__.py:118:57: error[invalid-argument-type] Argument to function `async_register_firmware_info_provider` is incorrect: Expected `SyncHardwareFirmwareInfoModule | AsyncHardwareFirmwareInfoModule`, found `<module 'homeassistant.components.zha.homeassistant_hardware'>`
+ homeassistant/components/zha/__init__.py:118:57: error[invalid-argument-type] Argument to function `async_register_firmware_info_provider` is incorrect: Expected `HardwareFirmwareInfoModule`, found `<module 'homeassistant.components.zha.homeassistant_hardware'>`
+ homeassistant/core.py:1527:48: error[invalid-argument-type] Argument to function `_event_repr` is incorrect: Expected `EventType[Mapping[str, Any]] | str`, found `EventType[_DataT@async_fire_internal] | str`
+ homeassistant/core.py:1547:17: error[invalid-assignment] Object of type `Event[Mapping[str, Any]]` is not assignable to `Event[_DataT@async_fire_internal] | None`
+ homeassistant/core.py:1548:21: error[invalid-argument-type] Argument to bound method `__init__` is incorrect: Expected `EventType[Mapping[str, Any]] | str`, found `EventType[_DataT@async_fire_internal] | str`
- homeassistant/helpers/service_info/ssdp.py:39:39: error[no-matching-overload] No overload of function `field` matches arguments
- homeassistant/helpers/singleton.py:83:12: error[invalid-return-type] Return type does not match returned value: expected `(_FuncType, /) -> _FuncType`, found `Overload[(func: (HomeAssistant, /) -> Coroutine[Any, Any, _T@singleton]) -> (HomeAssistant, /) -> Coroutine[Any, Any, _T@singleton], (func: (HomeAssistant, /) -> _U@singleton) -> (HomeAssistant, /) -> _U@singleton]`
+ homeassistant/helpers/singleton.py:83:12: error[invalid-return-type] Return type does not match returned value: expected `(_FuncType, /) -> _FuncType`, found `Overload[(func: _FuncType) -> _FuncType, (func: _FuncType) -> _FuncType]`
+ homeassistant/util/hass_dict.pyi:146:5: error[type-assertion-failure] Argument does not have asserted type `dict[str, int]`
+ homeassistant/util/hass_dict.pyi:147:5: error[type-assertion-failure] Argument does not have asserted type `int`
+ homeassistant/util/hass_dict.pyi:155:62: warning[unused-ignore-comment] Unused blanket `type: ignore` directive
+ homeassistant/util/hass_dict.pyi:170:5: error[type-assertion-failure] Argument does not have asserted type `dict[str, int]`
No memory usage changes detected ✅

Copy link
Contributor

github-actions bot commented Oct 2, 2025

ecosystem-analyzer results

Lint rule Added Removed Changed
invalid-argument-type 5 5 4
unresolved-attribute 3 11 0
invalid-return-type 0 12 1
no-matching-overload 1 11 0
unused-ignore-comment 5 0 0
invalid-assignment 1 1 2
type-assertion-failure 3 0 0
redundant-cast 1 0 0
unsupported-operator 0 1 0
Total 19 41 7

Full report with detailed diff (timing results)

@dcreager dcreager force-pushed the dcreager/non-non-inferable branch from 6bc7ceb to 417caf0 Compare October 2, 2025 18:11
Base automatically changed from dcreager/non-inherited to main October 3, 2025 17:55
@dcreager dcreager force-pushed the dcreager/non-non-inferable branch from 417caf0 to 7b5684f Compare October 3, 2025 19:05
@dcreager
Copy link
Member Author

dcreager commented Oct 3, 2025

ecosystem analysis:

Several projects have removed diagnostics, which look like correct better understanding of the types involved:

  • hauntsaninja/boostedblob
  • homeassistant/core
  • cwltool
  • jax
  • meson
  • pytest
  • schema_salad
  • scikit-build-core
  • strawberry
  • werkzeug
  • xarray

Still looking at some of the new diagnostics. It also looks like we aren't expanding type aliases in as many places as before.

@dcreager dcreager force-pushed the dcreager/non-non-inferable branch from 7a69d29 to df385d2 Compare October 7, 2025 12:59
@dcreager dcreager force-pushed the dcreager/non-non-inferable branch from f11b47c to 74cc6b7 Compare October 7, 2025 16:54
@dcreager dcreager changed the base branch from main to dcreager/union-none October 7, 2025 16:54
Base automatically changed from dcreager/union-none to main October 7, 2025 17:33
@dcreager dcreager force-pushed the dcreager/non-non-inferable branch from 74cc6b7 to 6d12c00 Compare October 7, 2025 17:34
@dcreager dcreager force-pushed the dcreager/non-non-inferable branch from a2266c0 to 5f708ba Compare October 8, 2025 13:53
Copy link

codspeed-hq bot commented Oct 8, 2025

CodSpeed Performance Report

Merging #20677 will degrade performances by 4.28%

Comparing dcreager/non-non-inferable (c836146) with main (5e08e54)

Summary

⚡ 1 improvement
❌ 1 regression
✅ 19 untouched
⏩ 30 skipped1

⚠️ Please fix the performance issues or acknowledge them on CodSpeed.

Benchmarks breakdown

Mode Benchmark BASE HEAD Change
Instrumentation DateType 194.5 ms 203.1 ms -4.28%
Instrumentation hydra-zen 936.2 ms 884.9 ms +5.8%

Footnotes

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

Resolves conflicts by:
- Using TypeVarIdentity and BoundTypeVarIdentity for identity comparisons
- Updating InferableTypeVars to use BoundTypeVarIdentity instead of BoundTypeVarInstance
- Adding db parameter to all is_inferable() calls
- Making BoundTypeVarIdentity implement salsa::Update for use in tracked functions
- Updating SpecializationBuilder to use BoundTypeVarIdentity as map keys
- Converting inferable typevars collection to use identities

This fixes the regression where method calls on Top-materialized types were
failing with invalid-argument-type errors due to typevar identity mismatches.
Update DisplayBoundTypeVarInstance to DisplayBoundTypeVarIdentity and move
the display() method from BoundTypeVarInstance to BoundTypeVarIdentity.

Since the display implementation only uses the identity fields (name and
binding context), it makes more sense for it to operate on the identity
type directly. This also reduces coupling to the full BoundTypeVarInstance.

Updated all callers to use .identity(db).display(db) when displaying a
BoundTypeVarInstance.
Change ConstrainedTypeVar to store BoundTypeVarIdentity instead of
BoundTypeVarInstance. This makes constraints independent of typevar
materialization, which is appropriate since constraints should only care
about the logical identity of a typevar, not its specific bounds.

Updated:
- ConstrainedTypeVar::typevar field to BoundTypeVarIdentity
- ConstraintSet::range() and negated_range() to accept BoundTypeVarIdentity
- ConstrainedTypeVar::new_node() signature
- All callers in function.rs to pass .identity(db)
- Display code which now receives BoundTypeVarIdentity directly
* dcreager/typevar-identity:
  pre-commit
  clippy
  Update ConstraintSet to use BoundTypeVarIdentity
  clippy
  Move display method to BoundTypeVarIdentity
* main:
  [ty] bidirectional type inference using function return type annotations (#20528)
  [ty] use type context more aggressively to infer values ​​when constructing a `TypedDict` (#20806)
* dcreager/typevar-identity:
  [ty] bidirectional type inference using function return type annotations (#20528)
  [ty] use type context more aggressively to infer values ​​when constructing a `TypedDict` (#20806)
@dcreager dcreager changed the base branch from main to dcreager/typevar-identity October 12, 2025 13:08
dcreager added a commit that referenced this pull request Oct 13, 2025
…ing scope (#20822)

Generic classes are not allowed to bind or reference a typevar from an
enclosing scope:

```py
def f[T](x: T, y: T) -> None:
    class Ok[S]: ...
    # error: [invalid-generic-class]
    class Bad1[T]: ...
    # error: [invalid-generic-class]
    class Bad2(Iterable[T]): ...

class C[T]:
    class Ok1[S]: ...
    # error: [invalid-generic-class]
    class Bad1[T]: ...
    # error: [invalid-generic-class]
    class Bad2(Iterable[T]): ...
```

It does not matter if the class uses PEP 695 or legacy syntax. It does
not matter if the enclosing scope is a generic class or function. The
generic class cannot even _reference_ an enclosing typevar in its base
class list.

This PR adds diagnostics for these cases.

In addition, the PR adds better fallback behavior for generic classes
that violate this rule: any enclosing typevars are not included in the
class's generic context. (That ensures that we don't inadvertently try
to infer specializations for those typevars in places where we
shouldn't.) The `dulwich` ecosystem project has [examples of
this](https://github.com/jelmer/dulwich/blob/d912eaaffd60b4978ff92b91a8300e2cd234e0fc/dulwich/config.py#L251)
that were causing new false positives on #20677.

---------

Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com>
dcreager added a commit that referenced this pull request Oct 14, 2025
As part of #20598, we added `is_identical_to` methods to
`TypeVarInstance` and `BoundTypeVarInstance`, which compare when two
typevar instances refer to "the same" underlying typevar, even if we
have forced their lazy bounds/constraints as part of marking typevars as
inferable. (Doing so results in a different salsa interned struct ID,
since we've changed the contents of the `bounds_or_constraints` field.)

It turns out that marking typevars as inferable is not the only way that
we might force lazy bounds/constraints; it also happens when we
materialize a type containing a typevar. This surfaced as ecosystem
report failures on #20677.

That means that we need a more long-term fix to this problem.
(`is_identical_to`, and its underlying `original` field, were meant to
be a temporary fix until we removed the `MarkTypeVarsInferable` type
mapping.)

This PR extracts out a separate type (`TypeVarIdentity`) that only
includes the fields that actually inform whether two typevars are "the
same". All other properties of the typevar (default, bounds/constraints,
etc) still live in `TypeVarInstance`. Call sites that care about typevar
identity can now either store just `TypeVarIdentity` (if they never need
access to those other properties), or continue to store
`TypeVarInstance` but pull out its `identity` when performing those "are
they the same typevar" comparisons. (All of this also applies
respectively to `BoundTypeVar{Identity,Instance}`.) In particular,
constraint sets now work on `BoundTypeVarIdentity`, and generic contexts
still _store_ a `BoundTypeVarInstance` (since we might need access to
defaults when specializing), but are keyed on `BoundTypeVarIdentity`.
Base automatically changed from dcreager/typevar-identity to main October 14, 2025 00:09
…rable

* origin/main: (26 commits)
  [ty] Add separate type for typevar "identity" (#20813)
  [ty] Diagnostic for generic classes that reference typevars in enclosing scope (#20822)
  Update Python compatibility from 3.13 to 3.14 in README.md (#20852)
  [syntax-errors]: break outside loop F701 (#20556)
  [ty] Treat `Callable`s as bound-method descriptors in special cases (#20802)
  [ty] Do not bind self to non-positional parameters (#20850)
  Fix syntax error false positives on parenthesized context managers (#20846)
  [ty] Remove 'pre-release software' warning (#20817)
  Render unsupported syntax errors in formatter tests (#20777)
  [ty] Treat functions, methods, and dynamic types as function-like `Callable`s (#20842)
  [ty] Move logic for `super()` inference to a new `types::bound_super` submodule (#20840)
  [ty] Fix false-positive diagnostics on `super()` calls (#20814)
  [ty] Move `class_member` to `member` module (#20837)
  [`ruff`] Use DiagnosticTag for more flake8 and numpy rules (#20758)
  [ty] Prefer declared base class attribute over inferred attribute on subclass (#20764)
  [ty] Log files that are slow to type check (#20836)
  Update cargo-bins/cargo-binstall action to v1.15.7 (#20827)
  Update CodSpeedHQ/action action to v4.1.1 (#20828)
  Update Rust crate pyproject-toml to v0.13.7 (#20835)
  Update Rust crate anstream to v0.6.21 (#20829)
  ...
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ecosystem-analyzer internal An internal refactor or improvement ty Multi-file analysis & type inference

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant