Skip to content

Fix TypeIs narrowing before isinstance#21622

Open
nkgotcode wants to merge 4 commits into
python:masterfrom
nkgotcode:fix/typeis-isinstance-negative-narrowing
Open

Fix TypeIs narrowing before isinstance#21622
nkgotcode wants to merge 4 commits into
python:masterfrom
nkgotcode:fix/typeis-isinstance-negative-narrowing

Conversation

@nkgotcode

@nkgotcode nkgotcode commented Jun 17, 2026

Copy link
Copy Markdown

Fixes #21508.

This keeps isinstance narrowing based on the currently narrowed expression
type. When a previous TypeIs check has already removed one side of a union,
an elif isinstance(...) check against a runtime generic alias no longer
widens the expression back to its original type.

The change also adds regression coverage for a TypeIs false branch followed
by isinstance(obj, Slice).

Tests:

  • pytest -n0 mypy/test/testcheck.py::TypeCheckSuite::check-typeis.test -q
  • pytest -n0 mypy/test/testcheck.py::TypeCheckSuite::check-isinstance.test -q
  • python -m mypy --config-file mypy_self_check.ini --no-incremental -p mypy

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@nkgotcode nkgotcode force-pushed the fix/typeis-isinstance-negative-narrowing branch from 11637e1 to 0b2c0c1 Compare June 18, 2026 16:16
@github-actions

Copy link
Copy Markdown
Contributor

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

steam.py (https://github.com/Gobot1234/steam.py)
+ steam/http.py:95: error: Item "str" of "Any | str" has no attribute "host"  [union-attr]
+ steam/http.py:103: error: Item "str" of "Any | str" has no attribute "host"  [union-attr]
+ steam/ext/csgo/state.py:217: error: Invalid index type "int | Any" for "dict[AssetID, Future[CasketItem]]"; expected type "AssetID"  [index]

prefect (https://github.com/PrefectHQ/prefect)
+ src/prefect/states.py:227: error: "BaseException" has no attribute "request"  [attr-defined]
+ src/prefect/_internal/states.py:93: error: "BaseException" has no attribute "request"  [attr-defined]
- src/prefect/server/events/schemas/automations.py:609: error: Item "dict[str, str | list[str]]" of "ResourceSpecification | dict[str, str | list[str]]" has no attribute "matches_every_resource_of_kind"  [union-attr]
+ src/prefect/server/events/schemas/automations.py:609: error: Item "dict[str, str | list[str]]" of "ResourceSpecification | dict[str, str | list[str]] | Any" has no attribute "matches_every_resource_of_kind"  [union-attr]
- src/prefect/testing/utilities.py:281: error: Argument 1 to "aread" of "ResultStore" has incompatible type "str | None"; expected "str"  [arg-type]
+ src/prefect/testing/utilities.py:281: error: Argument 1 to "aread" of "ResultStore" has incompatible type "Any | str | None"; expected "str"  [arg-type]
- src/prefect/testing/utilities.py:298: error: Argument 1 to "read_block_document" of "BlocksDocumentAsyncClient" has incompatible type "UUID | None"; expected "UUID"  [arg-type]
+ src/prefect/testing/utilities.py:298: error: Argument 1 to "read_block_document" of "BlocksDocumentAsyncClient" has incompatible type "Any | UUID | None"; expected "UUID"  [arg-type]

strawberry (https://github.com/strawberry-graphql/strawberry)
+ strawberry/http/async_base_view.py:384: error: Item "ExecutionResult" of "ExecutionResult | list[ExecutionResult]" has no attribute "initial_result"  [union-attr]
+ strawberry/http/async_base_view.py:384: error: Item "list[ExecutionResult]" of "ExecutionResult | list[ExecutionResult]" has no attribute "initial_result"  [union-attr]
+ strawberry/http/async_base_view.py:386: error: Item "ExecutionResult" of "ExecutionResult | list[ExecutionResult]" has no attribute "initial_result"  [union-attr]
+ strawberry/http/async_base_view.py:386: error: Item "list[ExecutionResult]" of "ExecutionResult | list[ExecutionResult]" has no attribute "initial_result"  [union-attr]
+ strawberry/http/async_base_view.py:388: error: Item "ExecutionResult" of "ExecutionResult | list[ExecutionResult]" has no attribute "initial_result"  [union-attr]
+ strawberry/http/async_base_view.py:388: error: Item "list[ExecutionResult]" of "ExecutionResult | list[ExecutionResult]" has no attribute "initial_result"  [union-attr]
+ strawberry/http/async_base_view.py:390: error: Item "ExecutionResult" of "ExecutionResult | list[ExecutionResult]" has no attribute "initial_result"  [union-attr]
+ strawberry/http/async_base_view.py:390: error: Item "list[ExecutionResult]" of "ExecutionResult | list[ExecutionResult]" has no attribute "initial_result"  [union-attr]
+ strawberry/http/async_base_view.py:394: error: Item "ExecutionResult" of "ExecutionResult | list[ExecutionResult]" has no attribute "initial_result"  [union-attr]
+ strawberry/http/async_base_view.py:394: error: Item "list[ExecutionResult]" of "ExecutionResult | list[ExecutionResult]" has no attribute "initial_result"  [union-attr]
+ strawberry/http/async_base_view.py:396: error: Item "ExecutionResult" of "ExecutionResult | list[ExecutionResult]" has no attribute "subsequent_results"  [union-attr]
+ strawberry/http/async_base_view.py:396: error: Item "list[ExecutionResult]" of "ExecutionResult | list[ExecutionResult]" has no attribute "subsequent_results"  [union-attr]

mypy (https://github.com/python/mypy)
+ mypy/ipc.py:345: error: Returning Any from function declared to return "str"  [no-any-return]
+ mypy/ipc.py:345: note: See https://mypy.rtfd.io/en/stable/_refs.html#code-no-any-return for more info

zulip (https://github.com/zulip/zulip)
+ zerver/lib/export.py:543: error: Need type annotation for "updates" (hint: "updates: dict[<type>, <type>] = ...")  [var-annotated]

altair (https://github.com/vega/altair)
+ altair/vegalite/v6/api.py:4377: error: Argument 3 to "_repeat_names" has incompatible type "Chart | RepeatChart | ConcatChart | HConcatChart | VConcatChart | FacetChart | LayerChart"; expected "Chart | LayerChart"  [arg-type]

pandera (https://github.com/pandera-dev/pandera)
+ pandera/engines/geopandas_engine.py:71: error: Item "dtype[generic[Any]]" of "dtype[generic[Any]] | ExtensionDtype | Series[Any] | Any" has no attribute "to_dict"  [union-attr]
+ pandera/engines/geopandas_engine.py:71: error: Item "ExtensionDtype" of "dtype[generic[Any]] | ExtensionDtype | Series[Any] | Any" has no attribute "to_dict"  [union-attr]
- pandera/engines/geopandas_engine.py:118: error: Unused "type: ignore" comment  [unused-ignore]
+ pandera/engines/geopandas_engine.py:181: error: Item "None" of "Series[Any] | DataFrame | Any | Any | None" has no attribute "crs"  [union-attr]
+ pandera/engines/geopandas_engine.py:185: error: Item "None" of "Series[Any] | DataFrame | Any | Any | None" has no attribute "crs"  [union-attr]
+ pandera/engines/geopandas_engine.py:190: error: Item "None" of "Series[Any] | DataFrame | Any | Any | None" has no attribute "columns"  [union-attr]
+ pandera/engines/geopandas_engine.py:191: error: Value of type "Series[Any] | DataFrame | Any | Any | None" is not indexable  [index]
+ pandera/engines/geopandas_engine.py:192: error: Value of type "Series[Any] | DataFrame | Any | Any | None" is not indexable  [index]

sympy (https://github.com/sympy/sympy)
+ sympy/series/residues.py:78: error: "Expr" has no attribute "exp"  [attr-defined]
+ sympy/sets/ordinals.py:46: error: Argument 2 to "OmegaPower" has incompatible type "object"; expected "Integer | int"  [arg-type]
+ sympy/sets/ordinals.py:139: error: Argument 1 to "convert" of "Ordinal" has incompatible type "object"; expected "Integer | int"  [arg-type]
+ sympy/simplify/simplify.py:2075: error: "Basic" has no attribute "base"  [attr-defined]
+ sympy/simplify/simplify.py:2078: error: "Basic" has no attribute "exp"  [attr-defined]
+ sympy/matrices/matrixbase.py:3213: error: "Expr" has no attribute "p"  [attr-defined]
+ sympy/matrices/matrixbase.py:3218: error: "Expr" has no attribute "p"  [attr-defined]
+ sympy/matrices/matrixbase.py:3223: error: "Expr" has no attribute "p"  [attr-defined]
+ sympy/functions/elementary/trigonometric.py:852: error: "Expr" has no attribute "q"  [attr-defined]
+ sympy/functions/elementary/trigonometric.py:853: error: "Expr" has no attribute "p"  [attr-defined]
+ sympy/functions/elementary/trigonometric.py:853: error: "Expr" has no attribute "q"  [attr-defined]
+ sympy/functions/elementary/trigonometric.py:854: error: "Expr" has no attribute "q"  [attr-defined]
+ sympy/functions/elementary/trigonometric.py:858: error: "Expr" has no attribute "q"  [attr-defined]
+ sympy/functions/elementary/trigonometric.py:865: error: "Expr" has no attribute "q"  [attr-defined]
+ sympy/functions/elementary/trigonometric.py:869: error: "Expr" has no attribute "q"  [attr-defined]
+ sympy/functions/elementary/trigonometric.py:872: error: "Expr" has no attribute "p"  [attr-defined]
+ sympy/tensor/array/expressions/from_matrix_to_array.py:67: error: "Basic" has no attribute "base"  [attr-defined]
+ sympy/tensor/array/expressions/from_matrix_to_array.py:68: error: "Basic" has no attribute "exp"  [attr-defined]
+ sympy/tensor/array/expressions/from_matrix_to_array.py:71: error: "Basic" has no attribute "exp"  [attr-defined]
+ sympy/tensor/array/expressions/from_matrix_to_array.py:73: error: "Basic" has no attribute "exp"  [attr-defined]
+ sympy/tensor/array/expressions/from_matrix_to_array.py:76: error: "Basic" has no attribute "exp"  [attr-defined]

pytest (https://github.com/pytest-dev/pytest)
+ src/_pytest/unittest.py:647: error: Incompatible return value type (got "tuple[type[Any], Any, TracebackType | None]", expected "tuple[type[BaseException], BaseException, TracebackType] | tuple[None, None, None]")  [return-value]
+ src/_pytest/unittest.py:647: error: "BaseException" has no attribute "value"  [attr-defined]

typeshed-stats (https://github.com/AlexWaygood/typeshed-stats)
+ src/typeshed_stats/gather.py:433: error: Returning Any from function declared to return "Mapping[str, object]"  [no-any-return]

jax (https://github.com/google/jax)
+ jax/experimental/mosaic/gpu/layouts.py:154: error: Argument 1 to "get" of "SwizzleTransformAttr" has incompatible type "MemRefTransform | Any"; expected "int"  [arg-type]
+ jax/_src/pallas/mosaic_gpu/core.py:1348: error: Unsupported operand types for + ("int" and "Slice")  [operator]
+ jax/_src/pallas/mosaic_gpu/core.py:1348: note: Right operand is of type "int | Array | Any | Slice"

sphinx (https://github.com/sphinx-doc/sphinx)
+ sphinx/search/__init__.py:625:52: error: Value of type "Node" is not indexable  [index]

pytest-robotframework (https://github.com/detachhead/pytest-robotframework)
+ pytest_robotframework/_internal/api.py:102: error: "BaseException" has no attribute "message"  [attr-defined]
+ pytest_robotframework/_internal/api.py:104: error: "BaseException" has no attribute "syntax"  [attr-defined]

@nkgotcode

nkgotcode commented Jun 18, 2026

Copy link
Copy Markdown
Author

The current mypy_primer diff looks like the stricter narrowing intended by this change. I checked the mypy/ipc.py item locally and it does not reproduce under the branch self-check: .venv/bin/python -m mypy --config-file mypy_self_check.ini --no-incremental -p mypy -> Success: no issues found in 196 source files. The GitHub own-code type-check jobs are also green. Targeted suites still pass: check-typeis.test -> 63 passed, check-isinstance.test -> 186 passed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

TypeIs with isinstance fails to narrow

1 participant