Skip to content

Conversation

@tannguyencse19
Copy link
Contributor

Summary

Fix unsound isinstance negative narrowing by skipping else-branch refinement when the classinfo is a runtime type[T] value rather than a literal class/ tuple/union of classes.

Fixes #713

Test Plan

cargo test -p pyrefly -- test_isinstance_type_classinfo_no_negative_narrow

@meta-cla meta-cla bot added the cla signed label Jan 2, 2026
@kinto0 kinto0 requested a review from rchen152 January 5, 2026 18:15
@rchen152 rchen152 self-assigned this Jan 5, 2026
self.unions(res)
}

fn allow_negative_isinstance_classinfo(
Copy link
Contributor

Choose a reason for hiding this comment

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

Is it possible to specifically disallow type[T] rather than trying to enumerate everything that is allowed? This implementation makes me a bit nervous, since it seems easy to miss cases that we should allow.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Let me check that

@github-actions
Copy link

github-actions bot commented Jan 5, 2026

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

stone (https://github.com/dropbox/stone)
- ERROR stone/backends/python_rsrc/stone_base.py:105:75-90: Expected class object, got `Never` [invalid-argument]
- ERROR stone/backends/python_rsrc/stone_base.py:155:69-83: Expected class object, got `Never` [invalid-argument]

pandera (https://github.com/pandera-dev/pandera)
+ ERROR pandera/backends/ibis/base.py:168:17-36: Object of class `NoneType` has no attribute `rename` [missing-attribute]
+ ERROR pandera/backends/polars/base.py:57:34-50: Object of class `object` has no attribute `sample` [missing-attribute]
+ ERROR pandera/backends/polars/base.py:178:24-49: Object of class `NoneType` has no attribute `columns` [missing-attribute]
+ ERROR pandera/backends/polars/base.py:181:40-70: Object of class `NoneType` has no attribute `with_columns` [missing-attribute]
+ ERROR pandera/backends/polars/base.py:183:29-51: Object of class `NoneType` has no attribute `rows` [missing-attribute]
+ ERROR pandera/backends/polars/base.py:187:40-64: Object of class `NoneType` has no attribute `rename` [missing-attribute]
+ ERROR pandera/backends/polars/base.py:188:26-54: Cannot index into `object` [bad-index]

porcupine (https://github.com/Akuli/porcupine)
- ERROR porcupine/plugins/highlight/pygments_highlighter.py:51:12-52: Object of class `type` has no attribute `get_tokens_unprocessed` [missing-attribute]

pytest-robotframework (https://github.com/detachhead/pytest-robotframework)
- ERROR pytest_robotframework/__init__.py:136:35-37: `TracebackType | None` is not assignable to attribute `__traceback__` with type `Never` [bad-assignment]

strawberry (https://github.com/strawberry-graphql/strawberry)
+ ERROR strawberry/http/__init__.py:28:26-39: Object of class `NoneType` has no attribute `errors` [missing-attribute]
+ ERROR strawberry/http/__init__.py:28:41-58: Object of class `NoneType` has no attribute `extensions` [missing-attribute]
+ ERROR strawberry/http/__init__.py:30:17-28: Object of class `NoneType` has no attribute `data` [missing-attribute]
+ ERROR strawberry/schema/schema.py:540:12-25: Object of class `NoneType` has no attribute `errors` [missing-attribute]
+ ERROR strawberry/schema/schema.py:541:44-57: `list[GraphQLError] | object | Unknown` is not assignable to attribute `pre_execution_errors` with type `list[GraphQLError] | None` [bad-assignment]
+ ERROR strawberry/schema/schema.py:543:38-51: Argument `list[GraphQLError] | object | Unknown` is not assignable to parameter `errors` with type `list[GraphQLError]` in function `strawberry.schema.base.BaseSchema._process_errors` [bad-argument-type]
+ ERROR strawberry/schema/schema.py:545:63-76: Argument `list[GraphQLError] | object | Unknown | None` is not assignable to parameter `errors` with type `list[GraphQLError] | None` in function `strawberry.types.execution.ExecutionResult.__init__` [bad-argument-type]
+ ERROR strawberry/schema/schema.py:546:9-26: Object of class `NoneType` has no attribute `extensions` [missing-attribute]
+ ERROR strawberry/schema/schema.py:548:16-22: Returned type `ExecutionResult | NoneType | Unknown` is not assignable to declared return type `ExecutionResult` [bad-return]

pylint (https://github.com/pycqa/pylint)
+ ERROR pylint/checkers/async_checker.py:74:25-41: Object of class `NoneType` has no attribute `getattr` [missing-attribute]
+ ERROR pylint/checkers/async_checker.py:75:25-41: Object of class `NoneType` has no attribute `getattr` [missing-attribute]
+ ERROR pylint/checkers/typecheck.py:1940:25-41: Object of class `NoneType` has no attribute `getattr` [missing-attribute]
+ ERROR pylint/checkers/typecheck.py:1941:25-41: Object of class `NoneType` has no attribute `getattr` [missing-attribute]
+ ERROR pylint/checkers/typecheck.py:1957:69-82: Object of class `NoneType` has no attribute `name` [missing-attribute]

pandas (https://github.com/pandas-dev/pandas)
+ ERROR pandas/core/apply.py:774:35-38: Cannot set item in `MutableMapping[Hashable, ((...) -> Unknown) | list[AggFuncTypeBase] | str]` [unsupported-operation]
+ ERROR pandas/core/arrays/arrow/array.py:598:24-45: Object of class `ExtensionArray` has no attribute `__arrow_array__`
+ Object of class `ndarray` has no attribute `__arrow_array__` [missing-attribute]
+ ERROR pandas/core/arrays/arrow/array.py:665:73-78: Argument `ExtensionArray | ndarray[tuple[Any, ...], dtype[Any]] | Any` is not assignable to parameter `values` with type `Collection[Unknown]` in function `pandas.core.dtypes.cast.construct_1d_object_array_from_listlike` [bad-argument-type]
- ERROR pandas/core/arrays/arrow/array.py:1209:16-60: Returned type `ArrowExtensionArray | Unknown` is not assignable to declared return type `NAType | bool` [bad-return]
+ ERROR pandas/core/arrays/arrow/array.py:1209:16-60: Returned type `ArrowExtensionArray | NAType | NaTType | Timedelta | Timestamp | Unknown` is not assignable to declared return type `NAType | bool` [bad-return]
- ERROR pandas/core/arrays/arrow/array.py:1273:16-60: Returned type `ArrowExtensionArray | Unknown` is not assignable to declared return type `NAType | bool` [bad-return]
+ ERROR pandas/core/arrays/arrow/array.py:1273:16-60: Returned type `ArrowExtensionArray | NAType | NaTType | Timedelta | Timestamp | Unknown` is not assignable to declared return type `NAType | bool` [bad-return]
+ ERROR pandas/core/arrays/string_arrow.py:521:24-37: Object of class `NAType` has no attribute `astype`
+ Object of class `NaTType` has no attribute `astype`
+ Object of class `Timedelta` has no attribute `astype`
+ Object of class `Timestamp` has no attribute `astype` [missing-attribute]
- ERROR pandas/core/construction.py:605:24-30: No matching overload found for function `list.__init__` called with arguments: (ndarray[tuple[Any, ...], dtype[Any]] | object) [no-matching-overload]
+ ERROR pandas/core/construction.py:605:24-30: No matching overload found for function `list.__init__` called with arguments: (ExtensionArray | ndarray[tuple[Any, ...], dtype[Any]] | object) [no-matching-overload]
- ERROR pandas/core/construction.py:658:20-26: No matching overload found for function `list.__init__` called with arguments: (object) [no-matching-overload]
+ ERROR pandas/core/construction.py:658:20-26: No matching overload found for function `list.__init__` called with arguments: (ExtensionArray | object) [no-matching-overload]
- ERROR pandas/core/ops/array_ops.py:129:48-57: Argument `ExtensionArray | ndarray[tuple[int], dtype[object_]] | ndarray[tuple[int], dtype[Any]]` is not assignable to parameter `right` with type `ndarray[tuple[Any, ...], dtype[object_]]` in function `pandas._libs.ops.vec_compare` [bad-argument-type]
+ ERROR pandas/core/ops/array_ops.py:129:48-57: Argument `ExtensionArray | Index | ndarray[tuple[int], dtype[object_]] | ndarray[tuple[int], dtype[Any]] | Unknown` is not assignable to parameter `right` with type `ndarray[tuple[Any, ...], dtype[object_]]` in function `pandas._libs.ops.vec_compare` [bad-argument-type]
- ERROR pandas/plotting/_matplotlib/core.py:443:16-19: Returned type `list[tuple[int | ndarray[tuple[Any, ...], dtype[Any]] | slice[Any, Any, Any]]]` is not assignable to declared return type `bool | list[tuple[int, ...]]` [bad-return]
+ ERROR pandas/plotting/_matplotlib/core.py:443:16-19: Returned type `list[tuple[int | ndarray[tuple[Any, ...], dtype[Any]] | slice[Any, Any, Any] | Unknown]]` is not assignable to declared return type `bool | list[tuple[int, ...]]` [bad-return]
+ ERROR pandas/tests/tseries/offsets/test_ticks.py:261:16-39: Object of class `Tick` has no attribute `_as_pd_timedelta` [missing-attribute]
+ ERROR pandas/tests/tseries/offsets/test_ticks.py:268:16-39: Object of class `Tick` has no attribute `_as_pd_timedelta` [missing-attribute]

static-frame (https://github.com/static-frame/static-frame)
+ ERROR static_frame/core/container_util.py:1332:22-33: Type `int` is not iterable [not-iterable]
+ ERROR static_frame/core/container_util.py:1771:49-60: Type `int` is not iterable [not-iterable]
+ ERROR static_frame/core/frame.py:2124:30-39: Argument `StringIO | str` is not assignable to parameter `fp` with type `SupportsRead[bytes | str]` in function `json.load` [bad-argument-type]
+ ERROR static_frame/core/frame.py:2170:30-39: Argument `StringIO | str` is not assignable to parameter `fp` with type `SupportsRead[bytes | str]` in function `json.load` [bad-argument-type]
+ ERROR static_frame/core/frame.py:2215:30-39: Argument `StringIO | str` is not assignable to parameter `fp` with type `SupportsRead[bytes | str]` in function `json.load` [bad-argument-type]
+ ERROR static_frame/core/frame.py:2256:30-39: Argument `StringIO | str` is not assignable to parameter `fp` with type `SupportsRead[bytes | str]` in function `json.load` [bad-argument-type]
+ ERROR static_frame/core/frame.py:2297:30-39: Argument `StringIO | str` is not assignable to parameter `fp` with type `SupportsRead[bytes | str]` in function `json.load` [bad-argument-type]
+ ERROR static_frame/core/frame.py:2329:30-39: Argument `StringIO | str` is not assignable to parameter `fp` with type `SupportsRead[bytes | str]` in function `json.load` [bad-argument-type]
+ ERROR static_frame/core/frame.py:4682:53-64: Type `int` is not iterable [not-iterable]
+ ERROR static_frame/core/frame.py:4684:67-87: `not in` is not supported between `int` and `int` [not-iterable]
+ ERROR static_frame/core/frame.py:6202:61-72: Type `int` is not iterable [not-iterable]
+ ERROR static_frame/core/index.py:941:31-44: No matching overload found for function `list.__init__` called with arguments: (int | list[int]) [no-matching-overload]
+ ERROR static_frame/core/index_hierarchy.py:1338:32-58: No matching overload found for function `map.__new__` called with arguments: (type[map[_S]], BoundMethod[Self@IndexHierarchy, (self: Self@IndexHierarchy, depth_level: TDepthLevelSpecifier = 0, /) -> ndarray[Any, Any]], int | list[int]) [no-matching-overload]
+ ERROR static_frame/core/index_hierarchy.py:1676:42-80: No matching overload found for function `zip.__new__` called with arguments: (type[zip[_T_co]], int | list[int], range) [no-matching-overload]
+ ERROR static_frame/core/index_hierarchy.py:1676:66-77: Argument `int | list[int]` is not assignable to parameter `obj` with type `Sized` in function `len` [bad-argument-type]
+ ERROR static_frame/core/index_hierarchy.py:1707:25-56: No matching overload found for function `map.__new__` called with arguments: (type[map[Index[Any]]], BoundMethod[list[Index[Any]], Overload[(self: list[Index[Any]], i: SupportsIndex, /) -> Index[Any], (self: list[Index[Any]], s: slice[Any, Any, Any], /) -> list[Index[Any]]]], int | list[int]) [no-matching-overload]
+ ERROR static_frame/core/index_hierarchy.py:1743:20-22: Argument `int | list[int]` is not assignable to parameter `obj` with type `Sized` in function `len` [bad-argument-type]
+ ERROR static_frame/core/index_hierarchy.py:1744:23-29: Object of class `int` has no attribute `pop` [missing-attribute]
+ ERROR static_frame/core/index_hierarchy.py:1858:24-28: No matching overload found for function `sorted` called with arguments: (int | list[int]) [no-matching-overload]
+ ERROR static_frame/core/index_hierarchy.py:2547:20-22: Argument `int | list[int]` is not assignable to parameter `obj` with type `Sized` in function `len` [bad-argument-type]
+ ERROR static_frame/core/index_hierarchy.py:2548:23-29: Object of class `int` has no attribute `pop` [missing-attribute]
+ ERROR static_frame/core/series.py:1154:25-33: `int | integer[Any] | list[int] | ndarray[Any, Any] | slice[Any, Any, Any] | None` is not assignable to variable `iloc_many` with type `list[int] | ndarray[Any, Any] | slice[Any, Any, Any] | None` [bad-assignment]
+ ERROR static_frame/core/type_blocks.py:1534:21-24: Argument `int | integer[Any] | list[int] | ndarray[Any, Any] | slice[Any, Any, Any]` is not assignable to parameter `__key` with type `list[int] | ndarray[tuple[Any, ...], dtype[Any]] | slice[Any, Any, Any]` in function `arraykit.BlockIndex.iter_contiguous` [bad-argument-type]
+ ERROR static_frame/core/yarn.py:498:21-28: Argument `int | ndarray[Any, dtype[signedinteger[_64Bit]]]` is not assignable to parameter `indexer` with type `ndarray[Any, dtype[signedinteger[_64Bit]]] | None` in function `Yarn.__init__` [bad-argument-type]

optuna (https://github.com/optuna/optuna)
+ ERROR tests/visualization_tests/test_slice.py:85:16-25: Cannot index into `Axes` [bad-index]

kopf (https://github.com/nolar/kopf)
+ ERROR kopf/_cogs/structs/dicts.py:244:19-23: Type of yielded value `(Iterable[Iterable[_T] | _T] & KubernetesModel) | (Iterable[_T] & KubernetesModel) | (KubernetesModel & _T)` is not assignable to declared return type `_T` [invalid-yield]

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Narrowing for isinstance and type[T] is unsound

2 participants