Skip to content

Conversation

mtshiba
Copy link
Contributor

@mtshiba mtshiba commented Sep 23, 2025

Summary

Implements bidirectional type inference using function return type annotations.

This PR was originally proposed to solve astral-sh/ty#1167, but this does not fully resolve it on its own.
Additionally, I believe we need to allow dataclasses to generate their own __new__ methods, use constructor return types ​​for inference, and a mechanism to discard type narrowing like & ~AlwaysFalsy if necessary (at a more general level than this PR).

Test Plan

mdtest/bidirectional.md is added.

@mtshiba mtshiba changed the title [ty] propagate the annotated return type of functions to the inference of expressions in return statements [ty] bidirectional type inference using function return type annotations Sep 23, 2025
Copy link
Contributor

github-actions bot commented Sep 23, 2025

Diagnostic diff on typing conformance tests

No changes detected when running ty on typing conformance tests ✅

Copy link
Contributor

github-actions bot commented Sep 23, 2025

mypy_primer results

Changes were detected when running on open source projects
spack (https://github.com/spack/spack)
- lib/spack/spack/installer.py:2185:27: error[invalid-argument-type] Argument to bound method `__init__` is incorrect: Expected `Iterable[Unknown]`, found `set[str] | None`
+ lib/spack/spack/installer.py:2185:27: error[invalid-argument-type] Argument to bound method `__init__` is incorrect: Expected `Iterable[str]`, found `set[str] | None`

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 `~None | Unknown`
+ 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`
+ src/werkzeug/routing/exceptions.py:107:20: error[no-matching-overload] No overload of function `max` matches arguments
- Found 382 diagnostics
+ Found 383 diagnostics

pytest (https://github.com/pytest-dev/pytest)
- testing/python/raises_group.py:33:12: error[invalid-return-type] Return type does not match returned value: expected `RaisesExc[Failed]`, found `RaisesExc[BaseException]`
- Found 490 diagnostics
+ Found 489 diagnostics

aiortc (https://github.com/aiortc/aiortc)
- src/aiortc/codecs/g711.py:65:20: error[invalid-return-type] Return type does not match returned value: expected `tuple[list[bytes], int]`, found `tuple[list[Unknown], None]`
+ src/aiortc/codecs/g711.py:65:20: error[invalid-return-type] Return type does not match returned value: expected `tuple[list[bytes], int]`, found `tuple[list[bytes], None]`
- src/aiortc/codecs/g722.py:73:20: error[invalid-return-type] Return type does not match returned value: expected `tuple[list[bytes], int]`, found `tuple[list[Unknown], None]`
+ src/aiortc/codecs/g722.py:73:20: error[invalid-return-type] Return type does not match returned value: expected `tuple[list[bytes], int]`, found `tuple[list[bytes], None]`
- src/aiortc/codecs/opus.py:73:20: error[invalid-return-type] Return type does not match returned value: expected `tuple[list[bytes], int]`, found `tuple[list[Unknown], None]`
+ src/aiortc/codecs/opus.py:73:20: error[invalid-return-type] Return type does not match returned value: expected `tuple[list[bytes], int]`, found `tuple[list[bytes], None]`

paasta (https://github.com/yelp/paasta)
- paasta_tools/cli/cmds/mesh_status.py:117:16: error[invalid-return-type] Return type does not match returned value: expected `tuple[int, list[str]]`, found `tuple[Unknown | None, list[Unknown | str]]`
+ paasta_tools/cli/cmds/mesh_status.py:117:16: error[invalid-return-type] Return type does not match returned value: expected `tuple[int, list[str]]`, found `tuple[Unknown | None, list[str]]`
- paasta_tools/tron_tools.py:547:16: error[invalid-return-type] Return type does not match returned value: expected `dict[str, FieldSelectorConfig]`, found `dict[Unknown | str, Unknown | dict[Unknown | str, Unknown | str]]`
+ paasta_tools/tron_tools.py:547:16: error[invalid-return-type] Return type does not match returned value: expected `dict[str, FieldSelectorConfig]`, found `dict[str, FieldSelectorConfig | dict[Unknown | str, Unknown | str]]`
- paasta_tools/utils.py:3416:12: error[invalid-return-type] Return type does not match returned value: expected `InstanceConfigDict`, found `(Unknown & ~None) | dict[str, Any]`
+ paasta_tools/utils.py:3416:12: error[invalid-return-type] Return type does not match returned value: expected `InstanceConfigDict`, found `InstanceConfigDict | (Unknown & ~None) | dict[str, Any]`

pyjwt (https://github.com/jpadilla/pyjwt)
+ jwt/api_jwt.py:77:16: error[missing-typed-dict-key] Missing required key 'verify_signature' in TypedDict `FullOptions` constructor
+ jwt/api_jwt.py:77:16: error[missing-typed-dict-key] Missing required key 'require' in TypedDict `FullOptions` constructor
+ jwt/api_jwt.py:77:16: error[missing-typed-dict-key] Missing required key 'strict_aud' in TypedDict `FullOptions` constructor
+ jwt/api_jwt.py:77:16: error[missing-typed-dict-key] Missing required key 'verify_aud' in TypedDict `FullOptions` constructor
+ jwt/api_jwt.py:77:16: error[missing-typed-dict-key] Missing required key 'verify_exp' in TypedDict `FullOptions` constructor
+ jwt/api_jwt.py:77:16: error[missing-typed-dict-key] Missing required key 'verify_iat' in TypedDict `FullOptions` constructor
+ jwt/api_jwt.py:77:16: error[missing-typed-dict-key] Missing required key 'verify_iss' in TypedDict `FullOptions` constructor
+ jwt/api_jwt.py:77:16: error[missing-typed-dict-key] Missing required key 'verify_jti' in TypedDict `FullOptions` constructor
+ jwt/api_jwt.py:77:16: error[missing-typed-dict-key] Missing required key 'verify_nbf' in TypedDict `FullOptions` constructor
+ jwt/api_jwt.py:77:16: error[missing-typed-dict-key] Missing required key 'verify_sub' in TypedDict `FullOptions` constructor
- Found 23 diagnostics
+ Found 33 diagnostics

sockeye (https://github.com/awslabs/sockeye)
- sockeye/data_io.py:1149:62: error[invalid-argument-type] Argument to function `__new__` is incorrect: Expected `Iterable[Unknown]`, found `list[tuple[int | float | None, int | float | None]] | None`
+ sockeye/data_io.py:1149:62: error[invalid-argument-type] Argument to function `__new__` is incorrect: Expected `Iterable[tuple[int | float | None, int | float | None]]`, found `list[tuple[int | float | None, int | float | None]] | None`
- sockeye/model.py:816:56: error[invalid-argument-type] Argument to function `__new__` is incorrect: Expected `Iterable[Unknown]`, found `list[int] | None`
+ sockeye/model.py:816:56: error[invalid-argument-type] Argument to function `__new__` is incorrect: Expected `Iterable[int]`, found `list[int] | None`
- test/unit/test_inference.py:177:114: error[invalid-argument-type] Argument to function `__new__` is incorrect: Expected `Iterable[Unknown]`, found `list[Any] | None`
+ test/unit/test_inference.py:177:114: error[invalid-argument-type] Argument to function `__new__` is incorrect: Expected `Iterable[Any]`, found `list[Any] | None`

pydantic (https://github.com/pydantic/pydantic)
- pydantic/_internal/_validators.py:161:16: error[invalid-return-type] Return type does not match returned value: expected `Pattern[bytes]`, found `Pattern[str]`
+ pydantic/_internal/_validators.py:161:16: error[invalid-return-type] Return type does not match returned value: expected `Pattern[bytes]`, found `Pattern[bytes | str]`

pylox (https://github.com/sco1/pylox)
+ pylox/builtins/py_builtins.py:175:32: error[invalid-argument-type] Argument to function `mean` is incorrect: Expected `Iterable[int | float]`, found `dict[Unknown, Unknown] | Unknown | deque[Unknown | None]`
+ pylox/builtins/py_builtins.py:192:34: error[invalid-argument-type] Argument to function `median` is incorrect: Expected `Iterable[int | float]`, found `dict[Unknown, Unknown] | Unknown | deque[Unknown | None]`
+ pylox/builtins/py_builtins.py:216:32: error[invalid-argument-type] Argument to function `mode` is incorrect: Expected `Iterable[int | float]`, found `dict[Unknown, Unknown] | Unknown | deque[Unknown | None]`
+ pylox/builtins/py_builtins.py:326:33: error[invalid-argument-type] Argument to function `stdev` is incorrect: Expected `Iterable[int | float]`, found `dict[Unknown, Unknown] | Unknown | deque[Unknown | None]`
- Found 22 diagnostics
+ Found 26 diagnostics

dragonchain (https://github.com/dragonchain/dragonchain)
- dragonchain/webserver/lib/transactions.py:131:12: error[invalid-return-type] Return type does not match returned value: expected `dict[str, str]`, found `dict[Unknown | str, Unknown | None]`
+ dragonchain/webserver/lib/transactions.py:131:12: error[invalid-return-type] Return type does not match returned value: expected `dict[str, str]`, found `dict[str, str | Unknown | None]`

urllib3 (https://github.com/urllib3/urllib3)
- src/urllib3/contrib/pyopenssl.py:397:16: error[invalid-return-type] Return type does not match returned value: expected `dict[str, list[Any]] | None`, found `dict[Unknown | str, Unknown | tuple[tuple[tuple[str, Unknown]]] | list[tuple[str, str]]]`
+ src/urllib3/contrib/pyopenssl.py:397:16: error[invalid-return-type] Return type does not match returned value: expected `dict[str, list[Any]] | None`, found `dict[str, list[Any] | tuple[tuple[tuple[str, Unknown]]] | list[tuple[str, str]]]`

sphinx (https://github.com/sphinx-doc/sphinx)
- sphinx/domains/cpp/_symbol.py:1183:20: error[invalid-return-type] Return type does not match returned value: expected `tuple[list[Symbol] | None, str]`, found `tuple[list[Unknown], None]`
+ sphinx/domains/cpp/_symbol.py:1183:20: error[invalid-return-type] Return type does not match returned value: expected `tuple[list[Symbol] | None, str]`, found `tuple[list[Symbol | (Unknown & ~None)], None]`

Expression (https://github.com/cognitedata/Expression)
- expression/core/result.py:198:24: error[invalid-return-type] Return type does not match returned value: expected `dict[str, _TSourceOut@Result | _TErrorOut@Result | Literal["ok", "error"]]`, found `dict[Unknown | str, Unknown | str | _TSourceOut@Result]`
- expression/core/result.py:203:24: error[invalid-return-type] Return type does not match returned value: expected `dict[str, _TSourceOut@Result | _TErrorOut@Result | Literal["ok", "error"]]`, found `dict[Unknown | str, Unknown | str | _TErrorOut@Result]`
- expression/extra/option/pipeline.py:91:19: error[invalid-argument-type] Argument to function `reduce` is incorrect: Expected `(def Some[_T1](value: _T1@Some) -> Option[_T1@Some], (Any, /) -> Option[Any], /) -> def Some[_T1](value: _T1@Some) -> Option[_T1@Some]`, found `def reducer(acc: (Any, /) -> Option[Any], fn: (Any, /) -> Option[Any]) -> (Any, /) -> Option[Any]`
- expression/extra/result/pipeline.py:96:19: error[invalid-argument-type] Argument to function `reduce` is incorrect: Expected `(def Ok[_TSource](value: _TSource@Ok) -> Result[_TSource@Ok, Any], (Any, /) -> Result[Any, Any], /) -> def Ok[_TSource](value: _TSource@Ok) -> Result[_TSource@Ok, Any]`, found `def reducer(acc: (Any, /) -> Result[Any, Any], fn: (Any, /) -> Result[Any, Any]) -> (Any, /) -> Result[Any, Any]`
- Found 211 diagnostics
+ Found 207 diagnostics

antidote (https://github.com/Finistere/antidote)
+ tests/lib/interface/test_conditions.py:193:59: error[invalid-argument-type] Argument to bound method `when` is incorrect: Expected `Predicate[Weighted] | Predicate[NeutralWeight] | Weighted | ... omitted 3 union elements`, found `Weight | None`
- Found 300 diagnostics
+ Found 301 diagnostics

freqtrade (https://github.com/freqtrade/freqtrade)
- freqtrade/optimize/optimize_reports/optimize_reports.py:492:9: error[invalid-argument-type] Argument to function `generate_tag_metrics` is incorrect: Expected `Literal["enter_tag", "exit_reason"] | list[Literal["enter_tag", "exit_reason"]]`, found `list[Unknown | str]`
- Found 402 diagnostics
+ Found 401 diagnostics

Tanjun (https://github.com/FasterSpeeding/Tanjun)
- tanjun/commands/menu.py:181:16: error[no-matching-overload] No overload of bound method `__init__` matches arguments
- tanjun/commands/menu.py:308:16: error[no-matching-overload] No overload of bound method `__init__` matches arguments
- Found 115 diagnostics
+ Found 113 diagnostics

xarray (https://github.com/pydata/xarray)
- xarray/backends/scipy_.py:326:16: error[invalid-return-type] Return type does not match returned value: expected `str | ReadBuffer[Unknown] | AbstractDataStore`, found `str | PathLike[Any] | ReadBuffer[Unknown] | ... omitted 3 union elements`
+ xarray/backends/scipy_.py:326:16: error[invalid-return-type] Return type does not match returned value: expected `str | ReadBuffer[Unknown] | AbstractDataStore`, found `str | ReadBuffer[Unknown] | AbstractDataStore | ... omitted 3 union elements`
- xarray/conventions.py:415:17: error[invalid-argument-type] Argument to function `decode_cf_variable` is incorrect: Expected `bool`, found `bool | Mapping[str, bool] | Unknown`
- xarray/conventions.py:416:17: error[invalid-argument-type] Argument to function `decode_cf_variable` is incorrect: Expected `bool`, found `bool | Mapping[str, bool] | Unknown`
- xarray/conventions.py:420:17: error[invalid-argument-type] Argument to function `decode_cf_variable` is incorrect: Expected `bool`, found `bool | (Mapping[str, bool] & ~AlwaysTruthy) | (Unknown & ~AlwaysTruthy)`
- xarray/conventions.py:421:17: error[invalid-argument-type] Argument to function `decode_cf_variable` is incorrect: Expected `bool | None`, found `bool | Mapping[str, bool] | None | Unknown`
- xarray/conventions.py:422:17: error[invalid-argument-type] Argument to function `decode_cf_variable` is incorrect: Expected `bool | CFTimedeltaCoder | None`, found `bool | CFTimedeltaCoder | Mapping[str, bool | CFTimedeltaCoder] | None | Unknown`
+ xarray/conventions.py:406:30: error[invalid-argument-type] Argument to function `_item_or_default` is incorrect: Expected `Mapping[Any, Literal[True] | Unknown] | Literal[True]`, found `bool | Mapping[str, bool]`
+ xarray/conventions.py:415:52: error[invalid-argument-type] Argument to function `_item_or_default` is incorrect: Expected `Mapping[Any, Literal[True] | Unknown] | Literal[True]`, found `bool | Mapping[str, bool]`
+ xarray/conventions.py:416:49: error[invalid-argument-type] Argument to function `_item_or_default` is incorrect: Expected `Mapping[Any, Literal[True] | Unknown] | Literal[True]`, found `bool | Mapping[str, bool]`
+ xarray/conventions.py:418:62: error[invalid-argument-type] Argument to function `_item_or_default` is incorrect: Expected `Mapping[Any, Literal[True] | Unknown] | Literal[True]`, found `bool | CFDatetimeCoder | Mapping[str, bool | CFDatetimeCoder]`
+ xarray/conventions.py:421:45: error[invalid-argument-type] Argument to function `_item_or_default` is incorrect: Expected `Mapping[Any, None | Unknown] | None`, found `bool | Mapping[str, bool] | None`
+ xarray/conventions.py:422:51: error[invalid-argument-type] Argument to function `_item_or_default` is incorrect: Expected `Mapping[Any, None | Unknown] | None`, found `bool | CFTimedeltaCoder | Mapping[str, bool | CFTimedeltaCoder] | None`
- Found 1611 diagnostics
+ Found 1612 diagnostics

meson (https://github.com/mesonbuild/meson)
- mesonbuild/scripts/symbolextractor.py:221:16: error[invalid-return-type] Return type does not match returned value: expected `tuple[list[str], str]`, found `tuple[list[Unknown | (str & ~AlwaysFalsy)], None]`
+ mesonbuild/scripts/symbolextractor.py:221:16: error[invalid-return-type] Return type does not match returned value: expected `tuple[list[str], str]`, found `tuple[list[str], None]`
- mesonbuild/utils/universal.py:1692:12: error[invalid-return-type] Return type does not match returned value: expected `list[str]`, found `list[Any | Sequence[Any]]`
- Found 886 diagnostics
+ Found 885 diagnostics

pandera (https://github.com/pandera-dev/pandera)
+ tests/mypy/pandas_modules/pandas_dataframe.py:35:12: error[no-matching-overload] No overload of bound method `pipe` matches arguments
- tests/mypy/pandas_modules/pandas_dataframe.py:41:12: error[invalid-return-type] Return type does not match returned value: expected `DataFrame[SchemaOut]`, found `DataFrame[Schema]`
+ tests/mypy/pandas_modules/pandas_dataframe.py:41:12: error[invalid-return-type] Return type does not match returned value: expected `DataFrame[SchemaOut]`, found `DataFrame[SchemaOut] | DataFrame[Schema]`
- Found 1592 diagnostics
+ Found 1593 diagnostics

trio (https://github.com/python-trio/trio)
- src/trio/_tests/test_testing_raisesgroup.py:28:12: error[invalid-return-type] Return type does not match returned value: expected `RaisesExc[AssertionError]`, found `RaisesExc[BaseException]`
- Found 649 diagnostics
+ Found 648 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`
- Found 381 diagnostics
+ Found 382 diagnostics

hydra-zen (https://github.com/mit-ll-responsible-ai/hydra-zen)
- src/hydra_zen/wrapper/_implementations.py:945:16: error[invalid-return-type] Return type does not match returned value: expected `DataClass_ | type[@Todo] | ListConfig | DictConfig`, found `@Todo | (((...) -> Any) & Top[dict[Unknown, Unknown]]) | (DataClass_ & Top[dict[Unknown, Unknown]]) | ... omitted 8 union elements`
+ src/hydra_zen/wrapper/_implementations.py:945:16: error[invalid-return-type] Return type does not match returned value: expected `DataClass_ | type[@Todo] | ListConfig | DictConfig`, found `@Todo | DataClass_ | type[@Todo] | ... omitted 6 union elements`

openlibrary (https://github.com/internetarchive/openlibrary)
- openlibrary/plugins/upstream/models.py:587:28: error[invalid-return-type] Return type does not match returned value: expected `list[Image]`, found `list[Unknown | (Edition & ~AlwaysTruthy & ~AlwaysFalsy)]`
+ openlibrary/plugins/upstream/models.py:587:28: error[invalid-return-type] Return type does not match returned value: expected `list[Image]`, found `list[Image | (Edition & ~AlwaysTruthy & ~AlwaysFalsy) | (Unknown & ~AlwaysFalsy)]`
- openlibrary/plugins/worksearch/code.py:436:34: error[invalid-argument-type] Argument to function `__new__` is incorrect: Expected `Iterable[Unknown]`, found `list[str] | None`
+ openlibrary/plugins/worksearch/code.py:436:34: error[invalid-argument-type] Argument to function `__new__` is incorrect: Expected `Iterable[str]`, found `list[str] | None`
- openlibrary/plugins/worksearch/code.py:436:61: error[invalid-argument-type] Argument to function `__new__` is incorrect: Expected `Iterable[Unknown]`, found `list[str] | None`
+ openlibrary/plugins/worksearch/code.py:436:61: error[invalid-argument-type] Argument to function `__new__` is incorrect: Expected `Iterable[str]`, found `list[str] | None`

altair (https://github.com/vega/altair)
+ tests/vegalite/v6/test_theme.py:453:17: error[invalid-key] Invalid key access on TypedDict `StyleConfigIndexKwds`: Unknown key "group-title"
+ tests/vegalite/v6/test_theme.py:454:17: error[invalid-key] Invalid key access on TypedDict `StyleConfigIndexKwds`: Unknown key "guide-label"
+ tests/vegalite/v6/test_theme.py:455:17: error[invalid-key] Invalid key access on TypedDict `StyleConfigIndexKwds`: Unknown key "guide-title"
- Found 1336 diagnostics
+ Found 1339 diagnostics

prefect (https://github.com/PrefectHQ/prefect)
+ src/prefect/server/models/block_documents.py:462:29: warning[unused-ignore-comment] Unused blanket `type: ignore` directive
- Found 3249 diagnostics
+ Found 3250 diagnostics

pandas-stubs (https://github.com/pandas-dev/pandas-stubs)
- tests/indexes/arithmetic/timedeltaindex/test_mul.py:21:12: error[invalid-return-type] Return type does not match returned value: expected `TimedeltaIndex`, found `Index[Any]`
+ tests/indexes/arithmetic/timedeltaindex/test_mul.py:21:12: error[invalid-return-type] Return type does not match returned value: expected `TimedeltaIndex`, found `TimedeltaIndex | Index[Any]`

bokeh (https://github.com/bokeh/bokeh)
- src/bokeh/core/property/alias.py:89:16: error[invalid-return-type] Return type does not match returned value: expected `list[PropertyDescriptor[T@Alias]]`, found `list[Unknown | AliasPropertyDescriptor[Unknown]]`
+ src/bokeh/core/property/alias.py:89:16: error[invalid-return-type] Return type does not match returned value: expected `list[PropertyDescriptor[T@Alias]]`, found `list[PropertyDescriptor[T@Alias] | AliasPropertyDescriptor[Unknown]]`
- src/bokeh/core/property/alias.py:103:16: error[invalid-return-type] Return type does not match returned value: expected `list[PropertyDescriptor[T@DeprecatedAlias]]`, found `list[Unknown | DeprecatedAliasPropertyDescriptor[Unknown]]`
+ src/bokeh/core/property/alias.py:103:16: error[invalid-return-type] Return type does not match returned value: expected `list[PropertyDescriptor[T@DeprecatedAlias]]`, found `list[PropertyDescriptor[T@DeprecatedAlias] | DeprecatedAliasPropertyDescriptor[Unknown]]`
- src/bokeh/layouts.py:117:26: error[invalid-argument-type] Argument to function `_handle_child_sizing` is incorrect: Expected `list[UIElement]`, found `list[UIElement | list[UIElement]]`
- src/bokeh/layouts.py:118:16: error[invalid-argument-type] Argument is incorrect: Expected `list[UIElement]`, found `list[UIElement | list[UIElement]]`
- src/bokeh/layouts.py:152:26: error[invalid-argument-type] Argument to function `_handle_child_sizing` is incorrect: Expected `list[UIElement]`, found `list[UIElement | list[UIElement]]`
- src/bokeh/layouts.py:153:19: error[invalid-argument-type] Argument is incorrect: Expected `list[UIElement]`, found `list[UIElement | list[UIElement]]`
- Found 469 diagnostics
+ Found 465 diagnostics

dd-trace-py (https://github.com/DataDog/dd-trace-py)
- ddtrace/contrib/internal/botocore/services/bedrock.py:303:12: error[invalid-return-type] Return type does not match returned value: expected `dict[str, list[str]]`, found `dict[Unknown | str, Unknown | list[Unknown] | list[Unknown | str | None]]`
+ ddtrace/contrib/internal/botocore/services/bedrock.py:303:12: error[invalid-return-type] Return type does not match returned value: expected `dict[str, list[str]]`, found `dict[str, list[str] | (@Todo & Top[list[Unknown]]) | list[Unknown] | list[Unknown | str | None]]`
- ddtrace/internal/coverage/instrumentation_py3_12.py:119:36: error[invalid-argument-type] Argument to bound method `__init__` is incorrect: Expected `Iterable[Unknown]`, found `tuple[str, ...] | None`
+ ddtrace/internal/coverage/instrumentation_py3_12.py:119:36: error[invalid-argument-type] Argument to bound method `__init__` is incorrect: Expected `Iterable[str]`, found `tuple[str, ...] | None`
- ddtrace/internal/coverage/instrumentation_py3_12.py:132:36: error[invalid-argument-type] Argument to bound method `__init__` is incorrect: Expected `Iterable[Unknown]`, found `tuple[str, ...] | None`
+ ddtrace/internal/coverage/instrumentation_py3_12.py:132:36: error[invalid-argument-type] Argument to bound method `__init__` is incorrect: Expected `Iterable[str]`, found `tuple[str, ...] | None`
- scripts/freshvenvs.py:230:24: error[invalid-return-type] Return type does not match returned value: expected `tuple[str, list[str]]`, found `tuple[None, list[Unknown]]`
+ scripts/freshvenvs.py:230:24: error[invalid-return-type] Return type does not match returned value: expected `tuple[str, list[str]]`, found `tuple[None, list[str]]`
+ tests/contrib/langgraph/conftest.py:344:16: error[missing-typed-dict-key] Missing required key 'which' in TypedDict `State` constructor
- Found 7576 diagnostics
+ Found 7577 diagnostics

ibis (https://github.com/ibis-project/ibis)
+ ibis/examples/gen_registry.py:329:27: warning[possibly-missing-attribute] Attribute `get` on type `dict[str, str] | None` may be missing
- ibis/expr/types/relations.py:4868:37: error[invalid-argument-type] Argument to function `_to_selector` is incorrect: Expected `Sequence[str | Selector | Column] | Selector | Column`, found `list[Iterable[str] | Selector]`
- ibis/util.py:109:16: error[invalid-return-type] Return type does not match returned value: expected `list[V@promote_list]`, found `list[Unknown | (V@promote_list & Top[dict[Unknown, Unknown]]) | (Iterable[V@promote_list] & Top[dict[Unknown, Unknown]])]`
+ ibis/util.py:109:16: error[invalid-return-type] Return type does not match returned value: expected `list[V@promote_list]`, found `list[V@promote_list | (Iterable[V@promote_list] & Top[dict[Unknown, Unknown]])]`
- ibis/util.py:115:16: error[invalid-return-type] Return type does not match returned value: expected `list[V@promote_list]`, found `list[Unknown | (V@promote_list & ~Top[list[Unknown]] & ~Top[dict[Unknown, Unknown]] & ~None) | (Iterable[V@promote_list] & ~Top[list[Unknown]] & ~Top[dict[Unknown, Unknown]])]`
+ ibis/util.py:115:16: error[invalid-return-type] Return type does not match returned value: expected `list[V@promote_list]`, found `list[V@promote_list | (Iterable[V@promote_list] & ~Top[list[Unknown]] & ~Top[dict[Unknown, Unknown]])]`

jax (https://github.com/google/jax)
- jax/_src/pallas/fuser/block_spec.py:933:7: error[invalid-argument-type] Argument to function `__new__` is incorrect: Expected `Iterable[Unknown]`, found `Sequence[@Todo | int | None] | None`
+ jax/_src/pallas/fuser/block_spec.py:933:7: error[invalid-argument-type] Argument to function `__new__` is incorrect: Expected `Iterable[@Todo | int | None]`, found `Sequence[@Todo | int | None] | None`
- jax/_src/pallas/fuser/block_spec.py:963:13: error[invalid-argument-type] Argument to function `__new__` is incorrect: Expected `Iterable[Unknown]`, found `Sequence[@Todo | int | None] | None`
+ jax/_src/pallas/fuser/block_spec.py:963:13: error[invalid-argument-type] Argument to function `__new__` is incorrect: Expected `Iterable[@Todo | int | None]`, found `Sequence[@Todo | int | None] | None`
- jax/_src/pallas/fuser/block_spec.py:1021:18: error[invalid-argument-type] Argument to function `__new__` is incorrect: Expected `Iterable[Unknown]`, found `Sequence[@Todo | int | None] | None`
+ jax/_src/pallas/fuser/block_spec.py:1021:18: error[invalid-argument-type] Argument to function `__new__` is incorrect: Expected `Iterable[@Todo | int | None]`, found `Sequence[@Todo | int | None] | None`
- jax/_src/pallas/fuser/block_spec.py:1080:34: error[invalid-argument-type] Argument to function `__new__` is incorrect: Expected `Iterable[Unknown]`, found `Sequence[@Todo | int | None] | None`
+ jax/_src/pallas/fuser/block_spec.py:1080:34: error[invalid-argument-type] Argument to function `__new__` is incorrect: Expected `Iterable[@Todo | int | None]`, found `Sequence[@Todo | int | None] | None`
- jax/_src/pallas/fuser/block_spec.py:1177:7: error[invalid-argument-type] Argument to function `__new__` is incorrect: Expected `Iterable[Unknown]`, found `Sequence[@Todo | int | None] | None`
+ jax/_src/pallas/fuser/block_spec.py:1177:7: error[invalid-argument-type] Argument to function `__new__` is incorrect: Expected `Iterable[@Todo | int | None]`, found `Sequence[@Todo | int | None] | None`
- jax/_src/pallas/fuser/block_spec.py:1336:34: error[invalid-argument-type] Argument to function `__new__` is incorrect: Expected `Iterable[Unknown]`, found `Sequence[@Todo | int | None] | None`
+ jax/_src/pallas/fuser/block_spec.py:1336:34: error[invalid-argument-type] Argument to function `__new__` is incorrect: Expected `Iterable[@Todo | int | None]`, found `Sequence[@Todo | int | None] | None`
- jax/_src/pallas/fuser/block_spec.py:1578:40: error[invalid-argument-type] Argument to function `__new__` is incorrect: Expected `Iterable[Unknown]`, found `Sequence[@Todo | int | None] | None`
+ jax/_src/pallas/fuser/block_spec.py:1578:40: error[invalid-argument-type] Argument to function `__new__` is incorrect: Expected `Iterable[@Todo | int | None]`, found `Sequence[@Todo | int | None] | None`
- 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`
- Found 2443 diagnostics
+ Found 2442 diagnostics

pandas (https://github.com/pandas-dev/pandas)
- pandas/io/common.py:1167:12: error[invalid-return-type] Return type does not match returned value: expected `tuple[str | BaseBuffer, bool, list[BaseBuffer]]`, found `tuple[_IOWrapper, Literal[True], list[Unknown | _IOWrapper]]`
+ pandas/io/common.py:1167:12: error[invalid-return-type] Return type does not match returned value: expected `tuple[str | BaseBuffer, bool, list[BaseBuffer]]`, found `tuple[_IOWrapper, Literal[True], list[BaseBuffer | _IOWrapper]]`
- pandas/tests/frame/methods/test_replace.py:24:12: error[invalid-return-type] Return type does not match returned value: expected `dict[str, list[int | str]]`, found `dict[Unknown | str, Unknown | list[int] | list[Unknown]]`
+ pandas/tests/frame/methods/test_replace.py:24:12: error[invalid-return-type] Return type does not match returned value: expected `dict[str, list[int | str]]`, found `dict[str, list[int | str] | list[int] | list[Unknown]]`
- pandas/tests/frame/methods/test_replace.py:29:12: error[invalid-return-type] Return type does not match returned value: expected `dict[str, list[int | float | str]]`, found `dict[Unknown | str, Unknown | list[int] | list[Unknown] | list[Unknown | str | int | float]]`
+ pandas/tests/frame/methods/test_replace.py:29:12: error[invalid-return-type] Return type does not match returned value: expected `dict[str, list[int | float | str]]`, found `dict[str, list[int | float | str] | list[int] | list[Unknown]]`

rotki (https://github.com/rotki/rotki)
+ rotkehlchen/assets/asset.py:172:16: error[invalid-return-type] Return type does not match returned value: expected `AssetWithSymbol`, found `AssetWithNameAndType`
+ rotkehlchen/assets/asset.py:184:16: error[invalid-return-type] Return type does not match returned value: expected `EvmToken`, found `CryptoAsset`
+ rotkehlchen/assets/asset.py:190:16: error[invalid-return-type] Return type does not match returned value: expected `SolanaToken`, found `CryptoAsset`
+ rotkehlchen/assets/asset.py:196:16: error[invalid-return-type] Return type does not match returned value: expected `AssetWithOracles`, found `AssetWithNameAndType`
- rotkehlchen/chain/evm/decoding/beefy_finance/decoder.py:251:16: error[invalid-return-type] Return type does not match returned value: expected `dict[@Todo, str]`, found `dict[Unknown, Literal["beefy_finance"]]`
- rotkehlchen/chain/evm/decoding/compound/v3/decoder.py:407:16: error[invalid-return-type] Return type does not match returned value: expected `dict[@Todo, str]`, found `dict[@Todo, Literal["compound-v3"]]`
- rotkehlchen/chain/evm/decoding/llamazip/decoder.py:81:16: error[invalid-return-type] Return type does not match returned value: expected `dict[@Todo, str]`, found `dict[Unknown, Literal["llamazip"]]`
- rotkehlchen/chain/evm/decoding/morpho/decoder.py:335:16: error[invalid-return-type] Return type does not match returned value: expected `dict[@Todo, str]`, found `dict[Unknown, Literal["morpho"]]`
- rotkehlchen/chain/evm/decoding/uniswap/v3/decoder.py:419:16: error[invalid-return-type] Return type does not match returned value: expected `dict[@Todo, str]`, found `dict[Unknown, Literal["uniswap-v3-router"]]`
- rotkehlchen/chain/evm/decoding/zerox/decoder.py:294:16: error[invalid-return-type] Return type does not match returned value: expected `dict[@Todo, str]`, found `dict[Unknown, Literal["0x"]]`
- rotkehlchen/tests/db/test_db.py:454:20: error[invalid-argument-type] Argument to bound method `__init__` is incorrect: Expected `Iterable[Unknown]`, found `list[EvmToken] | None`
+ rotkehlchen/tests/db/test_db.py:454:20: error[invalid-argument-type] Argument to bound method `__init__` is incorrect: Expected `Iterable[EvmToken]`, found `list[EvmToken] | None`
- rotkehlchen/tests/unit/globaldb/test_asset_updates.py:405:9: error[invalid-argument-type] Argument to bound method `_apply_single_version_update` is incorrect: Expected `dict[Asset, Literal["remote", "local"]] | None`, found `dict[Unknown | Asset, Unknown | str]`
- Found 1702 diagnostics
+ Found 1699 diagnostics
Memory usage changes were detected when running on open source projects
sphinx (https://github.com/sphinx-doc/sphinx)
-     memo metadata = ~40MB
+     memo metadata = ~42MB

@AlexWaygood AlexWaygood added the ty Multi-file analysis & type inference label Sep 23, 2025
sharkdp pushed a commit that referenced this pull request Sep 23, 2025
…ith `is_disjoint_from` (#20538)

## Summary

I found this bug while working on #20528.
The minimum reproducible code is:

```python
from __future__ import annotations

from typing import NamedTuple
from ty_extensions import is_disjoint_from, static_assert

class Path(NamedTuple):
    prev: Path | None
    key: str

static_assert(not is_disjoint_from(Path, Path))
```

A stack overflow occurs when a nominal instance type inherits from
`NamedTuple` and is defined recursively.
This PR fixes this bug.

## Test Plan

mdtest updated
@mtshiba mtshiba marked this pull request as ready for review September 24, 2025 07:28
Copy link
Contributor

github-actions bot commented Sep 24, 2025

ecosystem-analyzer results

Lint rule Added Removed Changed
invalid-argument-type 12 15 16
invalid-return-type 4 11 26
missing-typed-dict-key 11 0 0
invalid-key 3 0 0
no-matching-overload 3 0 0
possibly-missing-attribute 1 0 0
unused-ignore-comment 1 0 0
Total 35 26 42

Copy link
Member

@AlexWaygood AlexWaygood left a comment

Choose a reason for hiding this comment

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

This looks pretty good to me! Some small comments below. @dcreager and/or @ibraheemdev will probably be better reviewers, though; I haven't done much work on our bidirectional inference yet

mtshiba and others added 2 commits September 27, 2025 01:03
Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com>
Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com>
Co-Authored-By: Ibraheem Ahmed <ibraheem@ibraheem.ca>
Copy link

codspeed-hq bot commented Oct 6, 2025

CodSpeed Performance Report

Merging #20528 will not alter performance

Comparing mtshiba:bidi-return-type (e7b5c14) with main (11a9e7e)

Summary

✅ 21 untouched
⏩ 30 skipped1

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.

Comment on lines 327 to 329
def _(x: int | None):
# TODO: should be `int`
reveal_type(union_param(x)) # revealed: Unknown
Copy link
Member

Choose a reason for hiding this comment

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

Is this a regression?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is revealed as int | None in main. The change is due to the addition of a branch that does nothing when matching in SpecializationBuilder::infer.
As already mentioned, specialization between unions requires special handling, and falling back to the (Type::Union(_), _) => ... branch could result in incorrect specialization. The default mapping T = Unknown is suboptimal but not incorrect, and the mapping T = int | None is also suboptimal, so I'm not sure whether this should be considered a regression.

Specialization between unions will be a future work.

Copy link
Member

Choose a reason for hiding this comment

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

This is timely — I just added that same branch with an initial "better than nothing" heuristic in #20749.

Copy link
Member

Choose a reason for hiding this comment

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

(Note that the heuristic in 20749 will not handle @mtshiba's example, since it only engages when formal has precisely one top-level typevar, and actual has none.)

Copy link
Member

Choose a reason for hiding this comment

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

I think that's fine, this should be eventually fixed by @dcreager's constraint solver work, so I wouldn't worry too much about it.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

OK, I merged the latest changes and made a minor fix to prevent incorrect specialization.

https://github.com/mtshiba/ruff/blob/1b42e0576a4493b1c5576624e8764b2708de410c/crates/ty_python_semantic/src/types/generics.rs#L1191-L1201

@mtshiba
Copy link
Contributor Author

mtshiba commented Oct 7, 2025

I think I addressed all review comments.

codspeed reports a slight performance regression, but for the same reason as #20635, I believe it is difficult to avoid this.

The ecosystem reports are generally good, some false positive errors are due to TODOs in TypedDict and the unification solver.

@carljm
Copy link
Contributor

carljm commented Oct 10, 2025

I think this might need minor updating now that I merged #20806 (since it should fix a test TODO added in that PR)? @ibraheemdev feel free to merge this if it looks ready to you.

@ibraheemdev ibraheemdev enabled auto-merge (squash) October 11, 2025 00:34
@ibraheemdev
Copy link
Member

This looks great, thanks.

@ibraheemdev ibraheemdev merged commit dc64c08 into astral-sh:main Oct 11, 2025
40 checks passed
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.

5 participants