Skip to content

Conversation

@hauntsaninja
Copy link
Collaborator

@hauntsaninja hauntsaninja commented Dec 30, 2025

Mypy does not narrow as much as it could, which results in false positives.

We would also like to narrow based on containment. The PR for that was previously reverted due to inconsistencies between narrowing via equality and via containment. This fixes the inconsistency on the equality side and paves the road for adding narrowing via containment. That is, we lay groundwork for fixing #17864 and fixing #17841

Fixes #18524
Fixes #20041
Fixes #17162
Fixes #16830
Fixes #13704
Fixes #7642
Fixes #3964

@github-actions

This comment has been minimized.

@hauntsaninja hauntsaninja force-pushed the narrowcontain branch 2 times, most recently from 6e89ffd to 9e351d1 Compare December 30, 2025 10:38
@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actions
Copy link
Contributor

Diff from mypy_primer, showing the effect of this PR on open source code:

beartype (https://github.com/beartype/beartype)
+ beartype/bite/collection/infercollectionitems.py:317: error: Unused "type: ignore" comment  [unused-ignore]
+ beartype/bite/collection/infercollectionitems.py:500: error: Unused "type: ignore" comment  [unused-ignore]
+ beartype/_util/hint/pep/proposal/pep484585/pep484585callable.py:280: error: Incompatible return value type (got "EllipsisType | tuple[Any, ...]", expected "tuple[Any, ...] | ParamSpec")  [return-value]

spark (https://github.com/apache/spark)
- python/pyspark/sql/pandas/types.py:754: error: Redundant cast to "Series[Any]"  [redundant-cast]
- python/pyspark/sql/pandas/types.py:757: error: Argument 1 to "apply" of "Series" has incompatible type "Callable[[Any], Any | NaTType]"; expected "Callable[..., str | bytes | date | datetime | timedelta | <15 more items> | None]"  [arg-type]
- python/pyspark/sql/pandas/types.py:757: error: Incompatible return value type (got "Any | NaTType", expected "str | bytes | date | datetime | timedelta | <15 more items> | None")  [return-value]
+ python/pyspark/pandas/typedef/typehints.py:163: error: Item "str" of "str | type | dtype[Any] | ExtensionDtype" has no attribute "__args__"  [union-attr]
+ python/pyspark/pandas/typedef/typehints.py:163: error: Item "type" of "str | type | dtype[Any] | ExtensionDtype" has no attribute "__args__"  [union-attr]
+ python/pyspark/pandas/typedef/typehints.py:163: error: Item "dtype[Any]" of "str | type | dtype[Any] | ExtensionDtype" has no attribute "__args__"  [union-attr]
+ python/pyspark/pandas/typedef/typehints.py:163: error: Item "ExtensionDtype" of "str | type | dtype[Any] | ExtensionDtype" has no attribute "__args__"  [union-attr]
+ python/pyspark/mllib/regression.py:282: error: Unused "type: ignore" comment  [unused-ignore]
+ python/pyspark/mllib/regression.py:282: error: Incompatible return value type (got "LogisticRegressionModel", expected "LM")  [return-value]
+ python/pyspark/mllib/regression.py:282: note: Error code "return-value" not covered by "type: ignore" comment

pydantic (https://github.com/pydantic/pydantic)
- pydantic/_internal/_generate_schema.py:525: error: Unused "type: ignore" comment  [unused-ignore]
- pydantic/main.py:1260: error: Invalid self argument "BaseModel" to attribute function "__repr_recursion__" with type "Callable[[Representation, Any], str]"  [misc]

mypy (https://github.com/python/mypy)
+ mypy/test/testtypes.py:198: error: Redundant call to get_proper_type()  [misc]
+ mypy/test/testtypes.py:202: error: Redundant call to get_proper_type()  [misc]
+ mypy/test/testargs.py:73: error: Statement is unreachable  [unreachable]
+ mypy/test/testargs.py:73: note: See https://mypy.rtfd.io/en/stable/_refs.html#code-unreachable for more info

altair (https://github.com/vega/altair)
+ altair/vegalite/v6/api.py:5068: error: Item "UndefinedType" of "Any | Sequence[VariableParameter | TopLevelSelectionParameter | SelectionParameter] | UndefinedType" has no attribute "__iter__" (not iterable)  [union-attr]

core (https://github.com/home-assistant/core)
+ homeassistant/components/alarmdecoder/config_flow.py:105: error: Need type annotation for "connection" (hint: "connection: dict[<type>, <type>] = ...")  [var-annotated]
+ homeassistant/components/alarmdecoder/config_flow.py:118: error: Cannot determine type of "device"  [has-type]
+ homeassistant/components/alarmdecoder/config_flow.py:127: error: Cannot determine type of "title"  [has-type]
+ homeassistant/components/alarmdecoder/config_flow.py:152: error: Cannot determine type of "schema"  [has-type]

zulip (https://github.com/zulip/zulip)
+ zerver/lib/validator.py:314: error: Redundant cast to "ResultT"  [redundant-cast]
- zerver/tests/test_zilencer_analytics.py:347: error: "RemoteZulipServer" has no attribute "id"  [attr-defined]
- zerver/tests/test_zilencer_analytics.py:586: error: "RealmCount" has no attribute "id"  [attr-defined]
- zerver/tests/test_zilencer_analytics.py:588: error: "InstallationCount" has no attribute "id"  [attr-defined]
+ analytics/views/stats.py:452: error: Redundant cast to "type[RemoteInstallationCount] | type[RemoteRealmCount]"  [redundant-cast]
+ analytics/views/stats.py:577: error: Incompatible return value type (got "QuerySet[RealmCount, RealmCount]", expected "QuerySet[CountT, CountT]")  [return-value]
+ analytics/views/stats.py:579: error: Incompatible return value type (got "QuerySet[UserCount, UserCount]", expected "QuerySet[CountT, CountT]")  [return-value]
+ analytics/views/stats.py:581: error: Incompatible return value type (got "QuerySet[StreamCount, StreamCount]", expected "QuerySet[CountT, CountT]")  [return-value]
+ analytics/views/stats.py:583: error: Incompatible return value type (got "QuerySet[InstallationCount, InstallationCount]", expected "QuerySet[CountT, CountT]")  [return-value]
+ analytics/views/stats.py:585: error: Incompatible return value type (got "QuerySet[RemoteInstallationCount, RemoteInstallationCount]", expected "QuerySet[CountT, CountT]")  [return-value]
+ analytics/views/stats.py:587: error: Incompatible return value type (got "QuerySet[RemoteRealmCount, RemoteRealmCount]", expected "QuerySet[CountT, CountT]")  [return-value]

schemathesis (https://github.com/schemathesis/schemathesis)
+ src/schemathesis/generation/coverage.py: note: In member "is_valid_for_location" of class "CoverageContext":
+ src/schemathesis/generation/coverage.py:258: error: Statement is unreachable  [unreachable]
+ src/schemathesis/generation/coverage.py: note: In member "leads_to_negative_test_case" of class "CoverageContext":
+ src/schemathesis/generation/coverage.py:264: error: Statement is unreachable  [unreachable]
+ src/schemathesis/generation/coverage.py: note: In member "will_be_serialized_to_string" of class "CoverageContext":
+ src/schemathesis/generation/coverage.py:272: error: Right operand of "and" is never evaluated  [unreachable]
+ src/schemathesis/generation/coverage.py: note: In function "_positive_string":
+ src/schemathesis/generation/coverage.py:958: error: Statement is unreachable  [unreachable]
+ src/schemathesis/generation/coverage.py: note: In function "_negative_format":
+ src/schemathesis/generation/coverage.py:1497: error: Statement is unreachable  [unreachable]

pytest (https://github.com/pytest-dev/pytest)
+ testing/test_collection.py:87: error: Item "None" of Module | None has no attribute "module"  [union-attr]
+ testing/test_collection.py:88: error: Item "None" of Module | None has no attribute "cls"  [union-attr]
+ testing/test_collection.py:89: error: Item "None" of Module | None has no attribute "instance"  [union-attr]
+ testing/code/test_excinfo.py:771: error: Redundant cast to "str"  [redundant-cast]

steam.py (https://github.com/Gobot1234/steam.py)
+ steam/ext/commands/converters.py:532: error: Unused "type: ignore" comment  [unused-ignore]
- steam/ext/commands/commands.py:245: error: Value of type "def [T] __new__(cls, *args: Any, **kwargs: Any) -> Never" is not indexable  [index]

pandas (https://github.com/pandas-dev/pandas)
+ pandas/core/arrays/period.py:354: error: Unused "type: ignore" comment  [unused-ignore]
+ pandas/core/construction.py:348: error: Incompatible return value type (got "ExtensionArray | ndarray[tuple[Any, ...], dtype[Any]]", expected "ExtensionArray")  [return-value]
+ pandas/core/arrays/timedeltas.py:339: error: Incompatible return value type (got "timedelta64[timedelta | int | None] | datetime64[date | int | None]", expected "timedelta64[timedelta | int | None]")  [return-value]
+ pandas/core/arrays/datetimes.py:547: error: No overload variant of "datetime64" matches argument types "signedinteger[_64Bit]", "Literal['s', 'ms', 'us', 'ns']"  [call-overload]
+ pandas/core/arrays/datetimes.py:547: note: Possible overload variants:
+ pandas/core/arrays/datetimes.py:547: note:     def [_DT64ItemT_co: date | int | None] __new__(cls, datetime64[_DT64ItemT_co], /) -> datetime64[_DT64ItemT_co]
+ pandas/core/arrays/datetimes.py:547: note:     def [_DT64ItemT_co: date | int | None, _AnyDT64Arg: (datetime, date, None)] __new__(cls, _AnyDT64Arg, /) -> datetime64[_AnyDT64Arg]
+ pandas/core/arrays/datetimes.py:547: note:     def [_DT64ItemT_co: date | int | None] __new__(cls, Literal['NAT', 'NaT', 'nat', b'NAT', b'NaT', b'nat'] | None = ..., Literal['Y', 'M', b'Y', b'M', 'W', 'D', b'W', b'D', 'h', 'm', 's', 'ms', 'us', 'μs', b'h', b'm', b's', b'ms', b'us', 'ns', 'ps', 'fs', 'as', b'ns', b'ps', b'fs', b'as'] | tuple[Literal['Y', 'M', b'Y', b'M', 'W', 'D', b'W', b'D', 'h', 'm', 's', 'ms', 'us', 'μs', b'h', b'm', b's', b'ms', b'us', 'ns', 'ps', 'fs', 'as', b'ns', b'ps', b'fs', b'as'], SupportsIndex] = ..., /) -> datetime64[None]
+ pandas/core/arrays/datetimes.py:547: note:     def [_DT64ItemT_co: date | int | None] __new__(cls, Literal['NOW', 'now', b'NOW', b'now'], Literal['h', 'm', 's', 'ms', 'us', 'μs', b'h', b'm', b's', b'ms', b'us'] | tuple[Literal['h', 'm', 's', 'ms', 'us', 'μs', b'h', b'm', b's', b'ms', b'us'], SupportsIndex] = ..., /) -> datetime64[datetime]
+ pandas/core/arrays/datetimes.py:547: note:     def [_DT64ItemT_co: date | int | None] __new__(cls, Literal['TODAY', 'today', b'TODAY', b'today'] | _HasDateAttributes, Literal['Y', 'M', b'Y', b'M', 'W', 'D', b'W', b'D'] | tuple[Literal['Y', 'M', b'Y', b'M', 'W', 'D', b'W', b'D'], SupportsIndex] = ..., /) -> datetime64[date]
+ pandas/core/arrays/datetimes.py:547: note:     def [_DT64ItemT_co: date | int | None] __new__(cls, int | bytes | str | date, Literal['ns', 'ps', 'fs', 'as', b'ns', b'ps', b'fs', b'as'] | tuple[Literal['ns', 'ps', 'fs', 'as', b'ns', b'ps', b'fs', b'as'], SupportsIndex], /) -> datetime64[int]
+ pandas/core/arrays/datetimes.py:547: note:     def [_DT64ItemT_co: date | int | None] __new__(cls, int | bytes | str | date, Literal['h', 'm', 's', 'ms', 'us', 'μs', b'h', b'm', b's', b'ms', b'us'] | tuple[Literal['h', 'm', 's', 'ms', 'us', 'μs', b'h', b'm', b's', b'ms', b'us'], SupportsIndex], /) -> datetime64[datetime]
+ pandas/core/arrays/datetimes.py:547: note:     def [_DT64ItemT_co: date | int | None] __new__(cls, int | bytes | str | date, Literal['Y', 'M', b'Y', b'M', 'W', 'D', b'W', b'D'] | tuple[Literal['Y', 'M', b'Y', b'M', 'W', 'D', b'W', b'D'], SupportsIndex], /) -> datetime64[date]
+ pandas/core/arrays/datetimes.py:547: note:     def [_DT64ItemT_co: date | int | None] __new__(cls, bytes | str | date | None, Literal['Y', 'M', b'Y', b'M', 'W', 'D', b'W', b'D', 'h', 'm', 's', 'ms', 'us', 'μs', b'h', b'm', b's', b'ms', b'us', 'ns', 'ps', 'fs', 'as', b'ns', b'ps', b'fs', b'as'] | tuple[Literal['Y', 'M', b'Y', b'M', 'W', 'D', b'W', b'D', 'h', 'm', 's', 'ms', 'us', 'μs', b'h', b'm', b's', b'ms', b'us', 'ns', 'ps', 'fs', 'as', b'ns', b'ps', b'fs', b'as'], SupportsIndex] = ..., /) -> datetime64[_DT64ItemT_co]
+ pandas/core/arrays/datetimelike.py:553: error: Argument 1 to "Timestamp" has incompatible type "object"; expected "integer[Any] | float | str | date | datetime | datetime64[date | int | None]"  [arg-type]
+ pandas/compat/pickle_compat.py:109: error: Too many arguments for "__new__" of "object"  [call-arg]

discord.py (https://github.com/Rapptz/discord.py)
+ discord/state.py:970: error: Unused "type: ignore" comment  [unused-ignore]
+ discord/message.py:2979: error: Incompatible types in assignment (expression has type "bool", variable has type "MessageFlags")  [assignment]
+ discord/interactions.py:1191: error: Incompatible types in assignment (expression has type "bool", variable has type "MessageFlags")  [assignment]
+ discord/channel.py:3048: error: Argument "stickers" to "handle_message_parameters" has incompatible type "Sequence[GuildSticker | StickerItem]"; expected "list[str | int] | None"  [arg-type]
+ discord/webhook/sync.py:1105: error: Argument "applied_tags" to "handle_message_parameters" has incompatible type "str | Snowflake"; expected "list[str | int] | None"  [arg-type]
+ discord/webhook/async_.py:1892: error: Argument "applied_tags" to "handle_message_parameters" has incompatible type "str | Snowflake"; expected "list[str | int] | None"  [arg-type]
+ discord/ext/tasks/__init__.py:765: error: Incompatible types in assignment (expression has type "time | Sequence[time]", variable has type "list[time]")  [assignment]

ibis (https://github.com/ibis-project/ibis)
- ibis/expr/types/relations.py:674: error: Incompatible return value type (got "Generator[Any, None, None]", expected "tuple[Value, ...]")  [return-value]
+ ibis/expr/types/relations.py:674: error: Incompatible return value type (got "Generator[Any | ibis.expr.operations.core.Value[Any, Any], None, None]", expected "tuple[ibis.expr.types.generic.Value, ...]")  [return-value]

rotki (https://github.com/rotki/rotki)
+ rotkehlchen/chain/evm/decoding/zerox/decoder.py:146: error: Unused "type: ignore" comment  [unused-ignore]
+ rotkehlchen/chain/ethereum/modules/sky/decoder.py:147: error: Unused "type: ignore" comment  [unused-ignore]

static-frame (https://github.com/static-frame/static-frame)
+ static_frame/core/util.py:419: error: Unused "type: ignore" comment  [unused-ignore]
+ static_frame/core/util.py:444: error: Unused "type: ignore" comment  [unused-ignore]
+ static_frame/core/util.py:446: error: Unused "type: ignore" comment  [unused-ignore]
+ static_frame/core/util.py:448: error: Unused "type: ignore" comment  [unused-ignore]
+ static_frame/core/util.py:449: error: Unused "type: ignore" comment  [unused-ignore]
+ static_frame/core/util.py:453: error: Unused "type: ignore" comment  [unused-ignore]
+ static_frame/core/util.py:455: error: Unused "type: ignore" comment  [unused-ignore]
+ static_frame/core/util.py:457: error: Unused "type: ignore" comment  [unused-ignore]
+ static_frame/core/util.py:459: error: Unused "type: ignore" comment  [unused-ignore]
+ static_frame/core/util.py:2307: error: Unused "type: ignore" comment  [unused-ignore]
+ static_frame/core/util.py:2307: error: "Hashable" has no attribute "start"  [attr-defined]
+ static_frame/core/util.py:2307: note: Error code "attr-defined" not covered by "type: ignore" comment
+ static_frame/core/util.py:2309: error: Unused "type: ignore" comment  [unused-ignore]
+ static_frame/core/util.py:2309: error: "Hashable" has no attribute "stop"  [attr-defined]
+ static_frame/core/util.py:2309: note: Error code "attr-defined" not covered by "type: ignore" comment
+ static_frame/core/util.py:2310: error: Unused "type: ignore" comment  [unused-ignore]
+ static_frame/core/util.py:2310: error: "Hashable" has no attribute "stop"  [attr-defined]
+ static_frame/core/util.py:2310: note: Error code "attr-defined" not covered by "type: ignore" comment
+ static_frame/core/util.py:2311: error: Unused "type: ignore" comment  [unused-ignore]
+ static_frame/core/util.py:2311: error: "Hashable" has no attribute "step"  [attr-defined]
+ static_frame/core/util.py:2311: note: Error code "attr-defined" not covered by "type: ignore" comment
+ static_frame/core/util.py:2312: error: Unused "type: ignore" comment  [unused-ignore]
+ static_frame/core/util.py:2312: error: "Hashable" has no attribute "step"  [attr-defined]
+ static_frame/core/util.py:2312: note: Error code "attr-defined" not covered by "type: ignore" comment
+ static_frame/core/node_fill_value.py:131: error: Unused "type: ignore" comment  [unused-ignore]
+ static_frame/core/display.py:483: error: Unused "type: ignore" comment  [unused-ignore]
+ static_frame/core/display.py:486: error: Unused "type: ignore" comment  [unused-ignore]
+ static_frame/core/display.py:517: error: Unused "type: ignore" comment  [unused-ignore]
+ static_frame/core/container_util.py:501: error: Unused "type: ignore" comment  [unused-ignore]
+ static_frame/core/container_util.py:502: error: Argument 1 to "to_index" of "IndexAutoConstructorFactory" has incompatible type "Iterable[TLabel] | type[IndexAutoFactory] | None"; expected "Iterable[TLabel]"  [arg-type]
+ static_frame/core/container_util.py:503: error: Argument "default_constructor" to "to_index" of "IndexAutoConstructorFactory" has incompatible type "Callable[..., IndexBase] | type[Index[Any]] | None"; expected "type[IndexBase]"  [arg-type]
+ static_frame/core/container_util.py:1037: error: Unused "type: ignore" comment  [unused-ignore]
+ static_frame/core/container_util.py:1072: error: Unused "type: ignore" comment  [unused-ignore]
+ static_frame/core/container_util.py:1075: error: Unused "type: ignore" comment  [unused-ignore]
+ static_frame/core/type_blocks.py:1630: error: Unused "type: ignore" comment  [unused-ignore]
+ static_frame/core/type_blocks.py:1631: error: Unused "type: ignore" comment  [unused-ignore]
+ static_frame/core/type_blocks.py:1751: error: Unused "type: ignore" comment  [unused-ignore]
+ static_frame/core/type_blocks.py:1752: error: Unused "type: ignore" comment  [unused-ignore]
+ static_frame/core/type_blocks.py:1756: error: Unused "type: ignore" comment  [unused-ignore]
+ static_frame/core/type_blocks.py:1833: error: Unused "type: ignore" comment  [unused-ignore]
+ static_frame/core/type_blocks.py:1834: error: Unused "type: ignore" comment  [unused-ignore]
+ static_frame/core/type_blocks.py:1881: error: Unused "type: ignore" comment  [unused-ignore]
+ static_frame/core/type_blocks.py:1883: error: Unused "type: ignore" comment  [unused-ignore]
+ static_frame/core/type_blocks.py:1922: error: Unused "type: ignore" comment  [unused-ignore]
+ static_frame/core/type_blocks.py:1923: error: Unused "type: ignore" comment  [unused-ignore]
+ static_frame/core/type_blocks.py:2717: error: Unused "type: ignore" comment  [unused-ignore]
+ static_frame/core/type_blocks.py:2718: error: Unused "type: ignore" comment  [unused-ignore]
+ static_frame/core/type_blocks.py:2720: error: Unused "type: ignore" comment  [unused-ignore]
+ static_frame/core/index.py:251: error: Unused "type: ignore" comment  [unused-ignore]
+ static_frame/core/index.py:689: error: Unused "type: ignore" comment  [unused-ignore]
+ static_frame/core/index.py:1023: error: Unused "type: ignore" comment  [unused-ignore]
+ static_frame/core/index.py:1024: error: Unused "type: ignore" comment  [unused-ignore]
+ static_frame/core/index.py:1025: error: Unused "type: ignore" comment  [unused-ignore]
+ static_frame/core/index.py:1028: error: Unused "type: ignore" comment  [unused-ignore]
+ static_frame/core/index.py:1117: error: Unused "type: ignore" comment  [unused-ignore]
+ static_frame/core/index_hierarchy.py:556: error: Unused "type: ignore" comment  [unused-ignore]
+ static_frame/core/index_hierarchy.py:557: error: Unused "type: ignore" comment  [unused-ignore]
+ static_frame/core/index_hierarchy.py:558: error: Unused "type: ignore" comment  [unused-ignore]
+ static_frame/core/index_hierarchy.py:579: error: Incompatible types in assignment (expression has type "list[ndarray[Any, Any]]", variable has type "ndarray[Any, Any]")  [assignment]
+ static_frame/core/index_hierarchy.py:1985: error: Unused "type: ignore" comment  [unused-ignore]
+ static_frame/core/index_hierarchy.py:1986: error: Unused "type: ignore" comment  [unused-ignore]
+ static_frame/core/index_hierarchy.py:2152: error: Unused "type: ignore" comment  [unused-ignore]
+ static_frame/core/index_hierarchy.py:2153: error: Unused "type: ignore" comment  [unused-ignore]
+ static_frame/core/index_hierarchy.py:2163: error: Unused "type: ignore" comment  [unused-ignore]
+ static_frame/core/index_hierarchy.py:2164: error: Unused "type: ignore" comment  [unused-ignore]
+ static_frame/core/index_hierarchy.py:2165: error: Unused "type: ignore" comment  [unused-ignore]
+ static_frame/core/index_hierarchy.py:2167: error: Unused "type: ignore" comment  [unused-ignore]
+ static_frame/core/index_hierarchy.py:2171: error: Unused "type: ignore" comment  [unused-ignore]
+ static_frame/core/index_hierarchy.py:2201: error: Unused "type: ignore" comment  [unused-ignore]
+ static_frame/core/index_hierarchy.py:2203: error: Unused "type: ignore" comment  [unused-ignore]
+ static_frame/core/index_hierarchy.py:2267: error: Unused "type: ignore" comment  [unused-ignore]
+ static_frame/core/series.py:2106: error: Unused "type: ignore" comment  [unused-ignore]
+ static_frame/core/pivot.py:248: error: Unused "type: ignore" comment  [unused-ignore]
+ static_frame/core/pivot.py:249: error: Unused "type: ignore" comment  [unused-ignore]
+ static_frame/core/pivot.py:250: error: Unused "type: ignore" comment  [unused-ignore]
+ static_frame/core/pivot.py:252: error: Unused "type: ignore" comment  [unused-ignore]
+ static_frame/core/pivot.py:253: error: Unused "type: ignore" comment  [unused-ignore]
+ static_frame/core/pivot.py:260: error: Unused "type: ignore" comment  [unused-ignore]
+ static_frame/core/frame.py:7157: error: Unused "type: ignore" comment  [unused-ignore]
+ static_frame/core/reduce.py:462: error: Unused "type: ignore" comment  [unused-ignore]
+ static_frame/core/reduce.py:463: error: Unused "type: ignore" comment  [unused-ignore]
+ static_frame/core/reduce.py:486: error: Unused "type: ignore" comment  [unused-ignore]
+ static_frame/core/reduce.py:487: error: Unused "type: ignore" comment  [unused-ignore]
+ static_frame/core/interface.py:1420: error: Incompatible types in assignment (expression has type "Bus[Any]", variable has type "TypeBlocks")  [assignment]
+ static_frame/core/interface.py:1423: error: Incompatible types in assignment (expression has type "Yarn[Any]", variable has type "TypeBlocks")  [assignment]
+ static_frame/core/interface.py:1430: error: Incompatible types in assignment (expression has type "Quilt", variable has type "TypeBlocks")  [assignment]
+ static_frame/core/interface.py:1432: error: Incompatible types in assignment (expression has type "Batch", variable has type "TypeBlocks")  [assignment]
+ static_frame/core/interface.py:1434: error: Incompatible types in assignment (expression has type "type[NPY] | type[NPZ]", variable has type "TypeBlocks")  [assignment]
+ static_frame/core/interface.py:1445: error: Incompatible types in assignment (expression has type "MemoryDisplay", variable has type "TypeBlocks")  [assignment]

cloud-init (https://github.com/canonical/cloud-init)
+ tests/unittests/sources/test_lxd.py:325: error: Statement is unreachable  [unreachable]

xarray (https://github.com/pydata/xarray)
+ xarray/tests/test_indexing.py: note: In member "test_lazily_indexed_array_setitem" of class "TestLazyArray":
+ xarray/tests/test_indexing.py:539: error: Incompatible types in assignment (expression has type "OuterIndexer", variable has type "BasicIndexer")  [assignment]
+ xarray/tests/test_indexing.py: note: At top level:

Copy link
Collaborator Author

@hauntsaninja hauntsaninja left a comment

Choose a reason for hiding this comment

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

Left some comments to help review.
I think for unreachable code, we might want to try something like #18707

if not exprs_in_type_calls:
return {}, {}

# type that is being compared to type(expr)
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

There is no logic change here, I just move stuff around. We call find_type_equals_check more often now (see below) and there are some expressions that are unsafe to pass into get_isinstance_type and we don't need to, so we now early return if there are no type call expressions.

There are some additional improvements possible here, will make in subsequent PR

)
if if_map == {} and else_map == {} and node is not None:
if_map, else_map = self.find_type_equals_check(node, expr_indices)
if node is not None:
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

As above, it is more common to have things in if_map and else_map now (see testNarrowingUsingTypeVar) so we call this always

)

should_coerce = any(should_coerce_inner(operand_types[i]) for i in chain_indices)
if coerce_only_in_literal_context:
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

No logic change to coerce_only_in_literal_context, just refactor to avoid unnecessary local function. (I do want to explore logic changes here in the future though)

operands[i], *conditional_types(expr_type, [target])
)
if if_map:
else_map = {}
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

No negative narrowing is the main difference between narrowing for value_targets and type_targets

if literal_hash(n1) == literal_hash(n2):
result[n1] = meet_types(m1[n1], m2[n2])
meet_result = meet_types(m1[n1], m2[n2])
if isinstance(meet_result, UninhabitedType):
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

This was a preexisting bug that gets exposed. We now use this logic for chained comparisons, in place of the bespoke logic previously

A, target = self.fx.def_alias_1(self.fx.a)
assert get_proper_type(A) == target
assert get_proper_type(target) == target
assert get_proper_type(A) == target
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

This is fun, from this assert mypy now knows that target is a proper type, making get_proper_type(target) redundant :-)

assert i.one == 1
assert i.two == None
assert i.const == 42
i = i
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

These tests have a "check initial values" then call __setattr__ then check subsequent values. We do a self assign to reset attribute narrowing

else:
reveal_type(x) # N: Revealed type is "builtins.str | None"
if x is '<string>':
reveal_type(x) # N: Revealed type is "Literal['<string>']"
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

The difference between is and == here is a little pre-existing. I will explore changing this in a subsequent PR

return None

def main(x: Union[str, int, None]):
if x == make_random_object():
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

This is actually more correct, since object could be None`. I changed the phrasing of the test case to make this clearer


def main(x: Optional[str]):
if x == 0:
reveal_type(x) # N: Revealed type is "builtins.str | None"
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I will explore making this unreachable in subsequent PR

@hauntsaninja hauntsaninja marked this pull request as ready for review December 31, 2025 05:01
@hauntsaninja
Copy link
Collaborator Author

hauntsaninja commented Dec 31, 2025

Primer is looking pretty good, with a few things I think maybe worth exploring for future PR (e.g. TypeType handling). My laptop is pretty noisy, this was maybe 1.5% hit on self check. I will explore optimisations once this is merged

hauntsaninja added a commit to hauntsaninja/mypy that referenced this pull request Jan 1, 2026
This relates to changes made in python#20492
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant