Skip to content

Comments

Fix union of T | type[T] failing to match the type[T] branch#2471

Closed
kinto0 wants to merge 2 commits intofacebook:mainfrom
kinto0:export-D93779356
Closed

Fix union of T | type[T] failing to match the type[T] branch#2471
kinto0 wants to merge 2 commits intofacebook:mainfrom
kinto0:export-D93779356

Conversation

@kinto0
Copy link
Contributor

@kinto0 kinto0 commented Feb 19, 2026

Summary:
When matching a value against a union containing both bare TypeVars and wrapped TypeVars (e.g. T | type[T]), the solver tried them in arbitrary order. If the bare T was tried first, it would eagerly pin the TypeVar to a type that violates bounds (e.g. type[Sub] instead of Sub), and since var-solving always returns Ok even on bound violations, the more specific type[T] branch was never tried.

The fix partitions var-containing union members into bare vars (T) and wrapped vars (type[T], list[T], etc.), trying wrapped vars first. Wrapped forms are more specific — they extract the inner type from the argument (e.g. type[T] extracts the instance type from a class object), producing better TypeVar solutions that are more likely to satisfy bounds.

fixes #2398

Differential Revision: D93779356

Summary: When a function parameter has type `T | type[T]` and a class (not an instance) is passed, pyrefly incorrectly matches against the `T` branch (solving T=type[Sub] which violates the bound) instead of the `type[T]` branch (which would correctly solve T=Sub). This test documents the bug.

Reviewed By: stroxler

Differential Revision: D93779358
Summary:
When matching a value against a union containing both bare TypeVars and wrapped TypeVars (e.g. `T | type[T]`), the solver tried them in arbitrary order. If the bare `T` was tried first, it would eagerly pin the TypeVar to a type that violates bounds (e.g. `type[Sub]` instead of `Sub`), and since var-solving always returns Ok even on bound violations, the more specific `type[T]` branch was never tried.

The fix partitions var-containing union members into bare vars (`T`) and wrapped vars (`type[T]`, `list[T]`, etc.), trying wrapped vars first. Wrapped forms are more specific — they extract the inner type from the argument (e.g. `type[T]` extracts the instance type from a class object), producing better TypeVar solutions that are more likely to satisfy bounds.

fixes facebook#2398

Differential Revision: D93779356
@meta-cla meta-cla bot added the cla signed label Feb 19, 2026
@meta-codesync
Copy link

meta-codesync bot commented Feb 19, 2026

@kinto0 has exported this pull request. If you are a Meta employee, you can view the originating Diff in D93779356.

@github-actions
Copy link

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

numpy-stl (https://github.com/WoLpH/numpy-stl)
- ERROR stl/base.py:500:55-67: No matching overload found for function `numpy._core.getlimits.finfo.__new__` called with arguments: (type[finfo[_FloatingT_co]], TypeAlias[float32, type[floating[_32Bit]]]) [no-matching-overload]

hydpy (https://github.com/hydpy-dev/hydpy)
- ERROR hydpy/core/modeltools.py:311:9-16: Overload signature `(self: Self@SubmodelProperty, obj: None, objtype: type[Model] | None) -> Self@SubmodelProperty` is not consistent with implementation signature `(self: Self@SubmodelProperty, obj: Model | None, objtype: type[Model] | None = None) -> TypeSubmodelInterface | Self@SubmodelProperty | None` [inconsistent-overload]
- ERROR hydpy/core/optiontools.py:159:9-16: Overload signature `(self: Self@OptionPropertyBase, obj: None, typ: type[Hashable]) -> Self@OptionPropertyBase` is not consistent with implementation signature `(self: Self@OptionPropertyBase, obj: Hashable | None, typ: type[Hashable]) -> TypeOptionContextBase | Self@OptionPropertyBase` [inconsistent-overload]

static-frame (https://github.com/static-frame/static-frame)
- ERROR static_frame/core/util.py:284:21-31: No matching overload found for function `numpy._core.getlimits.iinfo.__init__` called with arguments: (type[signedinteger[_64Bit]]) [no-matching-overload]

jax (https://github.com/google/jax)
- ERROR jax/_src/image/scale.py:76:57-69: No matching overload found for function `numpy._core.getlimits.finfo.__new__` called with arguments: (type[finfo[_FloatingT_co]], TypeAlias[float32, type[floating[_32Bit]]]) [no-matching-overload]
- ERROR jax/experimental/pallas/ops/tpu/splash_attention/splash_attention_mask_info.py:112:27-36: No matching overload found for function `numpy._core.getlimits.iinfo.__init__` called with arguments: (type[signedinteger[_8Bit]]) [no-matching-overload]
- ERROR jax/experimental/pallas/ops/tpu/splash_attention/splash_attention_mask_info.py:114:29-39: No matching overload found for function `numpy._core.getlimits.iinfo.__init__` called with arguments: (type[signedinteger[_16Bit]]) [no-matching-overload]
- ERROR jax/experimental/pallas/ops/tpu/splash_attention/splash_attention_mask_info.py:484:29-38: No matching overload found for function `numpy._core.getlimits.iinfo.__init__` called with arguments: (type[signedinteger[_8Bit]]) [no-matching-overload]
- ERROR jax/experimental/pallas/ops/tpu/splash_attention/splash_attention_mask_info.py:486:31-41: No matching overload found for function `numpy._core.getlimits.iinfo.__init__` called with arguments: (type[signedinteger[_16Bit]]) [no-matching-overload]

optuna (https://github.com/optuna/optuna)
- ERROR optuna/_gp/search_space.py:173:77-87: No matching overload found for function `numpy._core.getlimits.iinfo.__init__` called with arguments: (type[signedinteger[_32Bit]]) [no-matching-overload]
- ERROR optuna/storages/_rdb/alembic/versions/v3.0.0.c.py:34:25-37: No matching overload found for function `numpy._core.getlimits.finfo.__new__` called with arguments: (type[finfo[_FloatingT_co]], TypeAlias[float32, type[floating[_32Bit]]]) [no-matching-overload]
- ERROR optuna/storages/_rdb/alembic/versions/v3.0.0.c.py:35:25-37: No matching overload found for function `numpy._core.getlimits.finfo.__new__` called with arguments: (type[finfo[_FloatingT_co]], TypeAlias[float32, type[floating[_32Bit]]]) [no-matching-overload]
- ERROR optuna/storages/_rdb/alembic/versions/v3.0.0.c.py:119:28-65: No matching overload found for function `numpy._core.numeric.isclose` called with arguments: (Column[float], floating[Any]) [no-matching-overload]
+ ERROR optuna/storages/_rdb/alembic/versions/v3.0.0.c.py:119:28-65: No matching overload found for function `numpy._core.numeric.isclose` called with arguments: (Column[float], floating[_32Bit]) [no-matching-overload]
- ERROR optuna/storages/_rdb/alembic/versions/v3.0.0.c.py:123:28-65: No matching overload found for function `numpy._core.numeric.isclose` called with arguments: (Column[float], floating[Any]) [no-matching-overload]
+ ERROR optuna/storages/_rdb/alembic/versions/v3.0.0.c.py:123:28-65: No matching overload found for function `numpy._core.numeric.isclose` called with arguments: (Column[float], floating[_32Bit]) [no-matching-overload]
- ERROR optuna/storages/_rdb/alembic/versions/v3.0.0.d.py:34:25-37: No matching overload found for function `numpy._core.getlimits.finfo.__new__` called with arguments: (type[finfo[_FloatingT_co]], TypeAlias[float32, type[floating[_32Bit]]]) [no-matching-overload]
- ERROR optuna/storages/_rdb/alembic/versions/v3.0.0.d.py:35:25-37: No matching overload found for function `numpy._core.getlimits.finfo.__new__` called with arguments: (type[finfo[_FloatingT_co]], TypeAlias[float32, type[floating[_32Bit]]]) [no-matching-overload]
- ERROR optuna/storages/_rdb/alembic/versions/v3.0.0.d.py:127:26-50: No matching overload found for function `numpy._core.numeric.isclose` called with arguments: (Column[float], floating[Any]) [no-matching-overload]
+ ERROR optuna/storages/_rdb/alembic/versions/v3.0.0.d.py:127:26-50: No matching overload found for function `numpy._core.numeric.isclose` called with arguments: (Column[float], floating[_32Bit]) [no-matching-overload]
- ERROR optuna/storages/_rdb/alembic/versions/v3.0.0.d.py:129:28-52: No matching overload found for function `numpy._core.numeric.isclose` called with arguments: (Column[float], floating[Any]) [no-matching-overload]
+ ERROR optuna/storages/_rdb/alembic/versions/v3.0.0.d.py:129:28-52: No matching overload found for function `numpy._core.numeric.isclose` called with arguments: (Column[float], floating[_32Bit]) [no-matching-overload]
- ERROR tests/samplers_tests/test_cmaes.py:71:85-95: No matching overload found for function `numpy._core.getlimits.iinfo.__init__` called with arguments: (type[signedinteger[_32Bit]]) [no-matching-overload]
- ERROR tests/samplers_tests/test_cmaes.py:105:85-95: No matching overload found for function `numpy._core.getlimits.iinfo.__init__` called with arguments: (type[signedinteger[_32Bit]]) [no-matching-overload]

Copy link
Member

@samwgoldman samwgoldman left a comment

Choose a reason for hiding this comment

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

Review automatically exported from Phabricator review in Meta.

@meta-codesync
Copy link

meta-codesync bot commented Feb 20, 2026

This pull request has been merged in c249117.

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.

Incorrect error for numpy.iinfo(numpy.int32)

3 participants