Skip to content

Fix invalid contextual inference for generic call arguments - #21803

Open
rheard wants to merge 1 commit into
python:masterfrom
rheard:fix-19304
Open

Fix invalid contextual inference for generic call arguments#21803
rheard wants to merge 1 commit into
python:masterfrom
rheard:fix-19304

Conversation

@rheard

@rheard rheard commented Aug 2, 2026

Copy link
Copy Markdown
Contributor

Fixes:

Mypy can use the outer return context to specialize a generic callable before it has checked whether the actual arguments still fit the specialized signature.

This can produce false positives when the contextual type is wider than the actual argument supports. For example, in the issue repro the outer Iterable[int | str] context pushes Vec.__add__ toward accepting Vec[int | str], but the actual argument is Vec[int], and Vec is invariant.

This PR keeps the existing contextual inference path, but adds a guard before committing to the context-specialized callable. If the specialized argument types are not compatible with the actual arguments, mypy falls back to ordinary argument inference, but only when that ordinary inferred return type still satisfies the original outer context.

The fallback is intentionally narrow. Constructors and special synthetic signatures keep the old behavior, since broader fallback caused regressions in existing tests, including testAbstractTypeInADict and constructor/type-alias cases that started reporting Never-based expected types.

Regression tests cover:

  • invariant generic argument widening through Vec.__add__
  • a min(..., key=...)-style callback case where return context widens the item type too far

@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.

@randolf-scholz

Copy link
Copy Markdown
Contributor

You may want to check the results in sterliakov/mypy-issues#309

Your PR could probably fix a bunch of other issues like my attempt did back then (randolf-scholz#4). Though your approach seems much simpler. Best of luck!

@github-actions

Copy link
Copy Markdown
Contributor

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

kornia (https://github.com/kornia/kornia)
+ kornia/models/efficient_vit/nn/act.py:36: error: Unused "type: ignore" comment  [unused-ignore]

spark (https://github.com/apache/spark)
+ python/pyspark/ml/util.py:1169: error: Unused "type: ignore" comment  [unused-ignore]
+ python/pyspark/ml/classification.py:3990: error: Unused "type: ignore" comment  [unused-ignore]

prefect (https://github.com/PrefectHQ/prefect)
- src/prefect/utilities/callables/__init__.py:717: error: Incompatible types in assignment (expression has type "list[None]", variable has type "list[expr | None]")  [assignment]
- src/prefect/utilities/callables/__init__.py:717: note: "list" is invariant -- see https://mypy.readthedocs.io/en/stable/common_issues.html#variance
- src/prefect/utilities/callables/__init__.py:717: note: Consider using "Sequence" instead, which is covariant
- src/prefect/utilities/callables/__init__.py:719: error: Unsupported operand types for + ("list[None]" and "list[expr | None]")  [operator]
+ src/prefect/server/events/jinja_filters.py:86: error: Unused "type: ignore" comment  [unused-ignore]
- src/prefect/task_runners.py:425: error: Argument 1 to "submit" of "Executor" has incompatible type "Callable[[Callable[_P, _T], **_P], _T]"; expected "def (Callable[[Task[P, R], UUID | None, TaskRun | None, dict[str, Any] | None, PrefectFuture[Any] | Any | Iterable[PrefectFuture[Any] | Any] | None, Literal['state', 'result'], dict[str, set[RunInput]] | None, dict[str, Any] | None], R | State[Any] | None], /, **Any) -> Any | State[Any] | None"  [arg-type]
+ src/prefect/task_runners.py:425: error: Argument 1 to "submit" of "Executor" has incompatible type "Callable[[Callable[_P, _T], **_P], _T]"; expected "def [P] (Callable[[Task[P, R], UUID | None, TaskRun | None, dict[str, Any] | None, PrefectFuture[Any] | Any | Iterable[PrefectFuture[Any] | Any] | None, Literal['state', 'result'], dict[str, set[RunInput]] | None, dict[str, Any] | None], R | State[Any] | None], /, **Any) -> Any | State[Any] | None"  [arg-type]
- src/prefect/flow_engine.py:2338: error: Unsupported operand types for | ("dict[str, str]" and "dict[str, str | None]")  [operator]

colour (https://github.com/colour-science/colour)
- colour/io/luts/lut.py:1419: error: Argument 1 to "tile" has incompatible type "Buffer | _SupportsArray[dtype[Any]] | _NestedSequence[_SupportsArray[dtype[Any]]] | complex | bytes | str | _NestedSequence[complex | bytes | str] | None"; expected "_SupportsArray[dtype[signedinteger[_8Bit] | signedinteger[_16Bit] | signedinteger[_32Bit] | signedinteger[_64Bit] | unsignedinteger[_8Bit] | unsignedinteger[_16Bit] | unsignedinteger[_32Bit] | unsignedinteger[_64Bit] | Any]] | _NestedSequence[_SupportsArray[dtype[signedinteger[_8Bit] | signedinteger[_16Bit] | signedinteger[_32Bit] | signedinteger[_64Bit] | unsignedinteger[_8Bit] | unsignedinteger[_16Bit] | unsignedinteger[_32Bit] | unsignedinteger[_64Bit] | Any]]]"  [arg-type]
+ colour/io/luts/lut.py:1433: error: No overload variant of "pad" matches argument types "ndarray[Any, Any]", "tuple[int, Any | signedinteger[_8Bit] | signedinteger[_16Bit] | signedinteger[_32Bit] | signedinteger[_64Bit] | unsignedinteger[_8Bit] | unsignedinteger[_16Bit] | unsignedinteger[_32Bit] | unsignedinteger[_64Bit]]", "str", "float"  [call-overload]
+ colour/io/luts/lut.py:1433: note: Possible overload variants:
+ colour/io/luts/lut.py:1433: note:     def [ShapeT: tuple[int, ...], DTypeT: dtype[Any]] pad(array: ndarray[ShapeT, DTypeT], pad_width: _SupportsArray[dtype[integer[Any]]] | _NestedSequence[_SupportsArray[dtype[integer[Any]]]] | int | _NestedSequence[int] | dict[int, int] | dict[int, tuple[int, int]] | dict[int, int | tuple[int, int]], mode: Literal['constant', 'edge', 'linear_ramp', 'maximum', 'mean', 'median', 'minimum', 'reflect', 'symmetric', 'wrap', 'empty'] = ..., *, stat_length: _SupportsArray[dtype[integer[Any]]] | _NestedSequence[_SupportsArray[dtype[integer[Any]]]] | int | _NestedSequence[int] | None = ..., constant_values: Buffer | _SupportsArray[dtype[Any]] | _NestedSequence[_SupportsArray[dtype[Any]]] | complex | bytes | str | _NestedSequence[complex | bytes | str] = ..., end_values: Buffer | _SupportsArray[dtype[Any]] | _NestedSequence[_SupportsArray[dtype[Any]]] | complex | bytes | str | _NestedSequence[complex | bytes | str] = ..., reflect_type: Literal['odd', 'even'] = ...) -> ndarray[ShapeT, DTypeT]
+ colour/io/luts/lut.py:1433: note:     def [ScalarT: generic[Any]] pad(array: _SupportsArray[dtype[ScalarT]] | _NestedSequence[_SupportsArray[dtype[ScalarT]]], pad_width: _SupportsArray[dtype[integer[Any]]] | _NestedSequence[_SupportsArray[dtype[integer[Any]]]] | int | _NestedSequence[int] | dict[int, int] | dict[int, tuple[int, int]] | dict[int, int | tuple[int, int]], mode: Literal['constant', 'edge', 'linear_ramp', 'maximum', 'mean', 'median', 'minimum', 'reflect', 'symmetric', 'wrap', 'empty'] = ..., *, stat_length: _SupportsArray[dtype[integer[Any]]] | _NestedSequence[_SupportsArray[dtype[integer[Any]]]] | int | _NestedSequence[int] | None = ..., constant_values: Buffer | _SupportsArray[dtype[Any]] | _NestedSequence[_SupportsArray[dtype[Any]]] | complex | bytes | str | _NestedSequence[complex | bytes | str] = ..., end_values: Buffer | _SupportsArray[dtype[Any]] | _NestedSequence[_SupportsArray[dtype[Any]]] | complex | bytes | str | _NestedSequence[complex | bytes | str] = ..., reflect_type: Literal['odd', 'even'] = ...) -> ndarray[tuple[Any, ...], dtype[ScalarT]]
+ colour/io/luts/lut.py:1433: note:     def pad(array: Buffer | _SupportsArray[dtype[Any]] | _NestedSequence[_SupportsArray[dtype[Any]]] | complex | bytes | str | _NestedSequence[complex | bytes | str], pad_width: _SupportsArray[dtype[integer[Any]]] | _NestedSequence[_SupportsArray[dtype[integer[Any]]]] | int | _NestedSequence[int] | dict[int, int] | dict[int, tuple[int, int]] | dict[int, int | tuple[int, int]], mode: Literal['constant', 'edge', 'linear_ramp', 'maximum', 'mean', 'median', 'minimum', 'reflect', 'symmetric', 'wrap', 'empty'] = ..., *, stat_length: _SupportsArray[dtype[integer[Any]]] | _NestedSequence[_SupportsArray[dtype[integer[Any]]]] | int | _NestedSequence[int] | None = ..., constant_values: Buffer | _SupportsArray[dtype[Any]] | _NestedSequence[_SupportsArray[dtype[Any]]] | complex | bytes | str | _NestedSequence[complex | bytes | str] = ..., end_values: Buffer | _SupportsArray[dtype[Any]] | _NestedSequence[_SupportsArray[dtype[Any]]] | complex | bytes | str | _NestedSequence[complex | bytes | str] = ..., reflect_type: Literal['odd', 'even'] = ...) -> ndarray[tuple[Any, ...], dtype[Any]]
+ colour/io/luts/lut.py:1433: note:     def [ShapeT: tuple[int, ...], DTypeT: dtype[Any]] pad(array: ndarray[ShapeT, DTypeT], pad_width: _SupportsArray[dtype[integer[Any]]] | _NestedSequence[_SupportsArray[dtype[integer[Any]]]] | int | _NestedSequence[int] | dict[int, int] | dict[int, tuple[int, int]] | dict[int, int | tuple[int, int]], mode: _ModeFunc, **kwargs: Any) -> ndarray[ShapeT, DTypeT]
+ colour/io/luts/lut.py:1433: note:     def [ScalarT: generic[Any]] pad(array: _SupportsArray[dtype[ScalarT]] | _NestedSequence[_SupportsArray[dtype[ScalarT]]], pad_width: _SupportsArray[dtype[integer[Any]]] | _NestedSequence[_SupportsArray[dtype[integer[Any]]]] | int | _NestedSequence[int] | dict[int, int] | dict[int, tuple[int, int]] | dict[int, int | tuple[int, int]], mode: _ModeFunc, **kwargs: Any) -> ndarray[tuple[Any, ...], dtype[ScalarT]]
+ colour/io/luts/lut.py:1433: note:     def pad(array: Buffer | _SupportsArray[dtype[Any]] | _NestedSequence[_SupportsArray[dtype[Any]]] | complex | bytes | str | _NestedSequence[complex | bytes | str], pad_width: _SupportsArray[dtype[integer[Any]]] | _NestedSequence[_SupportsArray[dtype[integer[Any]]]] | int | _NestedSequence[int] | dict[int, int] | dict[int, tuple[int, int]] | dict[int, int | tuple[int, int]], mode: _ModeFunc, **kwargs: Any) -> ndarray[tuple[Any, ...], dtype[Any]]
- colour/io/luts/lut.py:1955: error: Argument 1 to "tile" has incompatible type "Buffer | _SupportsArray[dtype[Any]] | _NestedSequence[_SupportsArray[dtype[Any]]] | complex | bytes | str | _NestedSequence[complex | bytes | str] | None"; expected "_SupportsArray[dtype[signedinteger[_8Bit] | signedinteger[_16Bit] | signedinteger[_32Bit] | signedinteger[_64Bit] | unsignedinteger[_8Bit] | unsignedinteger[_16Bit] | unsignedinteger[_32Bit] | unsignedinteger[_64Bit]]] | _NestedSequence[_SupportsArray[dtype[signedinteger[_8Bit] | signedinteger[_16Bit] | signedinteger[_32Bit] | signedinteger[_64Bit] | unsignedinteger[_8Bit] | unsignedinteger[_16Bit] | unsignedinteger[_32Bit] | unsignedinteger[_64Bit]]]]"  [arg-type]
+ colour/io/luts/lut.py:1955: error: Argument 1 to "tile" has incompatible type "Buffer | _SupportsArray[dtype[Any]] | _NestedSequence[_SupportsArray[dtype[Any]]] | complex | bytes | str | _NestedSequence[complex | bytes | str] | None"; expected "_SupportsArray[dtype[Any]] | _NestedSequence[_SupportsArray[dtype[Any]]]"  [arg-type]

pandera (https://github.com/pandera-dev/pandera)
- tests/ibis/test_ibis_dtypes.py:81: error: Unsupported operand types for + ("list[type[String] | type[_PhysicalNumber]]" and "list[type[Map]]")  [operator]
- tests/ibis/test_ibis_dtypes.py:92: error: Unsupported operand types for + ("list[type[String] | type[_PhysicalNumber]]" and "list[type[Map]]")  [operator]

artigraph (https://github.com/artigraph/artigraph)
+ src/arti/graphs/__init__.py:181: error: Unused "type: ignore" comment  [unused-ignore]

pydantic (https://github.com/pydantic/pydantic)
- pydantic/main.py:126: error: Dict entry 1 has incompatible type "str": "Callable[[BaseModel, str, Any], dict[str, Any] | tuple[dict[str, Any], dict[str, Any] | None, set[str]] | Any]"; expected "str": "Callable[[BaseModel, str, Any], None]"  [dict-item]

rotki (https://github.com/rotki/rotki)
+ rotkehlchen/chain/evm/decoding/stakedao/decoder.py:316: error: Unused "type: ignore" comment  [unused-ignore]
+ rotkehlchen/tasks/historical_balances.py:184: error: Incompatible return value type (got "list[tuple[Bucket, EventDirection]]", expected "list[tuple[Bucket, Literal[EventDirection.IN, EventDirection.OUT]]]")  [return-value]
+ rotkehlchen/tasks/historical_balances.py:185: error: Unused "type: ignore" comment  [unused-ignore]
- rotkehlchen/tests/api/test_calendar.py:156: error: Unsupported operand types for | ("dict[str, list[dict[str, str]]]" and "dict[str, int]")  [operator]
- rotkehlchen/tests/api/test_calendar.py:168: error: Unsupported operand types for | ("dict[str, str]" and "dict[str, int]")  [operator]
- rotkehlchen/tests/api/test_calendar.py:180: error: Unsupported operand types for | ("dict[str, str]" and "dict[str, int]")  [operator]
- rotkehlchen/tests/api/test_calendar.py:192: error: Unsupported operand types for | ("dict[str, str]" and "dict[str, int]")  [operator]
- rotkehlchen/tests/api/test_calendar.py:229: error: Unsupported operand types for | ("dict[str, list[dict[str, ChecksumAddress]]]" and "dict[str, int]")  [operator]

scipy (https://github.com/scipy/scipy)
- scipy/stats/tests/test_sampling.py:155: error: Unsupported operand types for + ("list[tuple[tuple[int, ...], type[ValueError], str] | tuple[tuple[int, int], type[UNURANError], str]]" and "list[tuple[tuple[float, float], type[ValueError], str]]")  [operator]

sympy (https://github.com/sympy/sympy)
+ sympy/core/add.py:396: error: Unused "type: ignore" comment  [unused-ignore]
+ sympy/polys/compatibility.py:402: error: Unused "type: ignore" comment  [unused-ignore]

scikit-learn (https://github.com/scikit-learn/scikit-learn)
- sklearn/preprocessing/tests/test_polynomial.py:630: error: Unsupported operand types for + ("list[ABCMeta | None]" and "list[ABCMeta]")  [operator]
- sklearn/preprocessing/tests/test_polynomial.py:701: error: Unsupported operand types for + ("list[ABCMeta | None]" and "list[ABCMeta]")  [operator]
- sklearn/preprocessing/tests/test_function_transformer.py:124: error: Unsupported operand types for + ("list[ABCMeta | None]" and "list[ABCMeta]")  [operator]
- sklearn/metrics/tests/test_pairwise.py:946: error: Unsupported operand types for + ("list[overloaded function]" and "list[ABCMeta]")  [operator]
- sklearn/metrics/tests/test_pairwise.py:951: error: Unsupported operand types for + ("list[overloaded function]" and "list[ABCMeta]")  [operator]
- sklearn/metrics/tests/test_pairwise.py:964: error: Unsupported operand types for + ("list[overloaded function]" and "list[ABCMeta]")  [operator]
- sklearn/metrics/tests/test_pairwise.py:1052: error: Unsupported operand types for + ("list[overloaded function]" and "list[ABCMeta]")  [operator]
- sklearn/metrics/tests/test_pairwise.py:1057: error: Unsupported operand types for + ("list[overloaded function]" and "list[ABCMeta]")  [operator]
- sklearn/metrics/tests/test_pairwise.py:1083: error: Unsupported operand types for + ("list[overloaded function]" and "list[ABCMeta]")  [operator]
- sklearn/metrics/tests/test_pairwise.py:1107: error: Unsupported operand types for + ("list[overloaded function]" and "list[ABCMeta]")  [operator]
- sklearn/metrics/tests/test_pairwise.py:1112: error: Unsupported operand types for + ("list[overloaded function]" and "list[ABCMeta]")  [operator]
- sklearn/metrics/tests/test_pairwise.py:1138: error: Unsupported operand types for + ("list[overloaded function]" and "list[ABCMeta]")  [operator]
- sklearn/_loss/tests/test_loss.py:224: error: Unsupported operand types for + ("list[tuple[BaseLoss, list[float], list[float]]]" and "list[tuple[BaseLoss, object, list[Any]]]")  [operator]
- sklearn/_loss/tests/test_loss.py:236: error: Unsupported operand types for + ("list[tuple[BaseLoss, list[float], list[float]]]" and "list[tuple[BaseLoss, list[float], object]]")  [operator]
- sklearn/feature_selection/tests/test_variance_threshold.py:14: error: Unsupported operand types for + ("list[ABCMeta | None]" and "list[ABCMeta]")  [operator]
- sklearn/feature_selection/tests/test_variance_threshold.py:31: error: Unsupported operand types for + ("list[None]" and "list[ABCMeta]")  [operator]
- sklearn/feature_selection/tests/test_variance_threshold.py:47: error: Unsupported operand types for + ("list[ABCMeta | None]" and "list[ABCMeta]")  [operator]
- sklearn/feature_selection/tests/test_variance_threshold.py:61: error: Unsupported operand types for + ("list[ABCMeta | None]" and "list[ABCMeta]")  [operator]
- sklearn/utils/tests/test_sparsefuncs.py:451: error: No overload variant of "hstack" matches argument types "list[Any]", "str"  [call-overload]
- sklearn/utils/tests/test_sparsefuncs.py:451: note: Possible overload variants:
- sklearn/utils/tests/test_sparsefuncs.py:451: note:     def [T] hstack(blocks: Sequence[_CanStack[T]], format: None = ..., dtype: None = ...) -> T
- sklearn/utils/tests/test_sparsefuncs.py:451: note:     def [ScalarT: number[Any, Any] | numpy.bool[builtins.bool]] hstack(blocks: Sequence[sparray[ScalarT, tuple[int, int]]], format: Literal['bsr'], dtype: None = ...) -> bsr_array[ScalarT]
- sklearn/utils/tests/test_sparsefuncs.py:451: note:     def [ScalarT: number[Any, Any] | numpy.bool[builtins.bool]] hstack(blocks: Sequence[sparray[ScalarT, tuple[int, int]]], format: Literal['coo'], dtype: None = ...) -> coo_array[ScalarT, tuple[int, int]]
- sklearn/utils/tests/test_sparsefuncs.py:451: note:     def [ScalarT: number[Any, Any] | numpy.bool[builtins.bool]] hstack(blocks: Sequence[sparray[ScalarT, tuple[int, int]]], format: Literal['csc'], dtype: None = ...) -> csc_array[ScalarT]
- sklearn/utils/tests/test_sparsefuncs.py:451: note:     def [ScalarT: number[Any, Any] | numpy.bool[builtins.bool]] hstack(blocks: Sequence[sparray[ScalarT, tuple[int, int]]], format: Literal['csr'], dtype: None = ...) -> csr_array[ScalarT, tuple[int, int]]
- sklearn/utils/tests/test_sparsefuncs.py:451: note:     def [ScalarT: number[Any, Any] | numpy.bool[builtins.bool]] hstack(blocks: Sequence[sparray[ScalarT, tuple[int, int]]], format: Literal['dia'], dtype: None = ...) -> dia_array[ScalarT]
- sklearn/utils/tests/test_sparsefuncs.py:451: note:     def [ScalarT: number[Any, Any] | numpy.bool[builtins.bool]] hstack(blocks: Sequence[sparray[ScalarT, tuple[int, int]]], format: Literal['dok'], dtype: None = ...) -> dok_array[ScalarT, tuple[int, int]]
- sklearn/utils/tests/test_sparsefuncs.py:451: note:     def [ScalarT: number[Any, Any] | numpy.bool[builtins.bool]] hstack(blocks: Sequence[sparray[ScalarT, tuple[int, int]]], format: Literal['lil'], dtype: None = ...) -> lil_array[ScalarT]
- sklearn/utils/tests/test_sparsefuncs.py:451: note:     def [T] hstack(blocks: Sequence[_CanStackAs[numpy.bool[builtins.bool], T]], format: None = ..., *, dtype: type[builtins.bool] | type[numpy.bool[builtins.bool]] | dtype[numpy.bool[builtins.bool]] | HasDType[dtype[numpy.bool[builtins.bool]]] | Literal['bool', 'bool_', 'b1', '|b1', '?']) -> T
- sklearn/utils/tests/test_sparsefuncs.py:451: note:     def hstack(blocks: Sequence[sparray[Any, tuple[Any, ...]]], format: Literal['bsr'], dtype: type[builtins.bool] | type[numpy.bool[builtins.bool]] | dtype[numpy.bool[builtins.bool]] | HasDType[dtype[numpy.bool[builtins.bool]]] | Literal['bool', 'bool_', 'b1', '|b1', '?']) -> bsr_array[numpy.bool[builtins.bool]]
- sklearn/utils/tests/test_sparsefuncs.py:451: note:     def hstack(blocks: Sequence[sparray[Any, tuple[Any, ...]]], format: Literal['coo'], dtype: type[builtins.bool] | type[numpy.bool[builtins.bool]] | dtype[numpy.bool[builtins.bool]] | HasDType[dtype[numpy.bool[builtins.bool]]] | Literal['bool', 'bool_', 'b1', '|b1', '?']) -> coo_array[numpy.bool[builtins.bool], tuple[int, int]]
- sklearn/utils/tests/test_sparsefuncs.py:451: note:     def hstack(blocks: Sequence[sparray[Any, tuple[Any, ...]]], format: Literal['csc'], dtype: type[builtins.bool] | type[numpy.bool[builtins.bool]] | dtype[numpy.bool[builtins.bool]] | HasDType[dtype[numpy.bool[builtins.bool]]] | Literal['bool', 'bool_', 'b1', '|b1', '?']) -> csc_array[numpy.bool[builtins.bool]]
- sklearn/utils/tests/test_sparsefuncs.py:451: note:     def hstack(blocks: Sequence[sparray[Any, tuple[Any, ...]]], format: Literal['csr'], dtype: type[builtins.bool] | type[numpy.bool[builtins.bool]] | dtype[numpy.bool[builtins.bool]] | HasDType[dtype[numpy.bool[builtins.bool]]] | Literal['bool', 'bool_', 'b1', '|b1', '?']) -> csr_array[numpy.bool[builtins.bool], tuple[int, int]]
- sklearn/utils/tests/test_sparsefuncs.py:451: note:     def hstack(blocks: Sequence[sparray[Any, tuple[Any, ...]]], format: Literal['dia'], dtype: type[builtins.bool] | type[numpy.bool[builtins.bool]] | dtype[numpy.bool[builtins.bool]] | HasDType[dtype[numpy.bool[builtins.bool]]] | Literal['bool', 'bool_', 'b1', '|b1', '?']) -> dia_array[numpy.bool[builtins.bool]]
- sklearn/utils/tests/test_sparsefuncs.py:451: note:     def hstack(blocks: Sequence[sparray[Any, tuple[Any, ...]]], format: Literal['dok'], dtype: type[builtins.bool] | type[numpy.bool[builtins.bool]] | dtype[numpy.bool[builtins.bool]] | HasDType[dtype[numpy.bool[builtins.bool]]] | Literal['bool', 'bool_', 'b1', '|b1', '?']) -> dok_array[numpy.bool[builtins.bool], tuple[int, int]]
- sklearn/utils/tests/test_sparsefuncs.py:451: note:     def hstack(blocks: Sequence[sparray[Any, tuple[Any, ...]]], format: Literal['lil'], dtype: type[builtins.bool] | type[numpy.bool[builtins.bool]] | dtype[numpy.bool[builtins.bool]] | HasDType[dtype[numpy.bool[builtins.bool]]] | Literal['bool', 'bool_', 'b1', '|b1', '?']) -> lil_array[numpy.bool[builtins.bool]]
- sklearn/utils/tests/test_sparsefuncs.py:451: note:     def [T] hstack(blocks: Sequence[_CanStackAs[signedinteger[_32Bit | _64Bit], T]], format: None = ..., *, dtype: type[JustInt] | type[signedinteger[_64Bit]] | dtype[signedinteger[_64Bit]] | HasDType[dtype[signedinteger[_64Bit]]] | Literal['int_', 'int', 'intp', 'n', '<n', '>n']) -> T
- sklearn/utils/tests/test_sparsefuncs.py:451: note:     def hstack(blocks: Sequence[sparray[Any, tuple[Any, ...]]], format: Literal['bsr'], dtype: type[JustInt] | type[signedinteger[_64Bit]] | dtype[signedinteger[_64Bit]] | HasDType[dtype[signedinteger[_64Bit]]] | Literal['int_', 'int', 'intp', 'n', '<n', '>n']) -> bsr_array[signedinteger[_32Bit | _64Bit]]
- sklearn/utils/tests/test_sparsefuncs.py:451: note:     def hstack(blocks: Sequence[sparray[Any, tuple[Any, ...]]], format: Literal['coo'], dtype: type[JustInt] | type[signedinteger[_64Bit]] | dtype[signedinteger[_64Bit]] | HasDType[dtype[signedinteger[_64Bit]]] | Literal['int_', 'int', 'intp', 'n', '<n', '>n']) -> coo_array[signedinteger[_32Bit | _64Bit], tuple[int, int]]
- sklearn/utils/tests/test_sparsefuncs.py:451: note:     def hstack(blocks: Sequence[sparray[Any, tuple[Any, ...]]], format: Literal['csc'], dtype: type[JustInt] | type[signedinteger[_64Bit]] | dtype[signedinteger[_64Bit]] | HasDType[dtype[signedinteger[_64Bit]]] | Literal['int_', 'int', 'intp', 'n', '<n', '>n']) -> csc_array[signedinteger[_32Bit | _64Bit]]
- sklearn/utils/tests/test_sparsefuncs.py:451: note:     def hstack(blocks: Sequence[sparray[Any, tuple[Any, ...]]], format: Literal['csr'], dtype: type[JustInt] | type[signedinteger[_64Bit]] | dtype[signedinteger[_64Bit]] | HasDType[dtype[signedinteger[_64Bit]]] | Literal['int_', 'int', 'intp', 'n', '<n', '>n']) -> csr_array[signedinteger[_32Bit | _64Bit], tuple[int, int]]
- sklearn/utils/tests/test_sparsefuncs.py:451: note:     def hstack(blocks: Sequence[sparray[Any, tuple[Any, ...]]], format: Literal['dia'], dtype: type[JustInt] | type[signedinteger[_64Bit]] | dtype[signedinteger[_64Bit]] | HasDType[dtype[signedinteger[_64Bit]]] | Literal['int_', 'int', 'intp', 'n', '<n', '>n']) -> dia_array[signedinteger[_32Bit | _64Bit]]
- sklearn/utils/tests/test_sparsefuncs.py:451: note:     def hstack(blocks: Sequence[sparray[Any, tuple[Any, ...]]], format: Literal['dok'], dtype: type[JustInt] | type[signedinteger[_64Bit]] | dtype[signedinteger[_64Bit]] | HasDType[dtype[signedinteger[_64Bit]]] | Literal['int_', 'int', 'intp', 'n', '<n', '>n']) -> dok_array[signedinteger[_32Bit | _64Bit], tuple[int, int]]
- sklearn/utils/tests/test_sparsefuncs.py:451: note:     def hstack(blocks: Sequence[sparray[Any, tuple[Any, ...]]], format: Literal['lil'], dtype: type[JustInt] | type[signedinteger[_64Bit]] | dtype[signedinteger[_64Bit]] | HasDType[dtype[signedinteger[_64Bit]]] | Literal['int_', 'int', 'intp', 'n', '<n', '>n']) -> lil_array[signedinteger[_32Bit | _64Bit]]
- sklearn/utils/tests/test_sparsefuncs.py:451: note:     def [T] hstack(blocks: Sequence[_CanStackAs[float64, T]], format: None = ..., *, dtype: type[JustFloat] | type[floating[_64Bit]] | dtype[floating[_64Bit]] | HasDType[dtype[floating[_64Bit]]] | Literal['float64', 'double', 'float', 'f8', '<f8', '>f8', 'd']) -> T
- sklearn/utils/tests/test_sparsefuncs.py:451: note:     def hstack(blocks: Sequence[sparray[Any, tuple[Any, ...]]], format: Literal['bsr'], dtype: type[JustFloat] | type[floating[_64Bit]] | dtype[floating[_64Bit]] | HasDType[dtype[floating[_64Bit]]] | Literal['float64', 'double', 'float', 'f8', '<f8', '>f8', 'd']) -> bsr_array[float64]
- sklearn/utils/tests/test_sparsefuncs.py:451: note:     def hstack(blocks: Sequence[sparray[Any, tuple[Any, ...]]], format: Literal['coo'], dtype: type[JustFloat] | type[floating[_64Bit]] | dtype[floating[_64Bit]] | HasDType[dtype[floating[_64Bit]]] | Literal['float64', 'double', 'float', 'f8', '<f8', '>f8', 'd']) -> coo_array[float64, tuple[int, int]]
- sklearn/utils/tests/test_sparsefuncs.py:451: note:     def hstack(blocks: Sequence[sparray[Any, tuple[Any, ...]]], format: Literal['csc'], dtype: type[JustFloat] | type[floating[_64Bit]] | dtype[floating[_64Bit]] | HasDType[dtype[floating[_64Bit]]] | Literal['float64', 'double', 'float', 'f8', '<f8', '>f8', 'd']) -> csc_array[float64]
- sklearn/utils/tests/test_sparsefuncs.py:451: note:     def hstack(blocks: Sequence[sparray[Any, tuple[Any, ...]]], format: Literal['csr'], dtype: type[JustFloat] | type[floating[_64Bit]] | dtype[floating[_64Bit]] | HasDType[dtype[floating[_64Bit]]] | Literal['float64', 'double', 'float', 'f8', '<f8', '>f8', 'd']) -> csr_array[float64, tuple[int, int]]
- sklearn/utils/tests/test_sparsefuncs.py:451: note:     def hstack(blocks: Sequence[sparray[Any, tuple[Any, ...]]], format: Literal['dia'], dtype: type[JustFloat] | type[floating[_64Bit]] | dtype[floating[_64Bit]] | HasDType[dtype[floating[_64Bit]]] | Literal['float64', 'double', 'float', 'f8', '<f8', '>f8', 'd']) -> dia_array[float64]
- sklearn/utils/tests/test_sparsefuncs.py:451: note:     def hstack(blocks: Sequence[sparray[Any, tuple[Any, ...]]], format: Literal['dok'], dtype: type[JustFloat] | type[floating[_64Bit]] | dtype[floating[_64Bit]] | HasDType[dtype[floating[_64Bit]]] | Literal['float64', 'double', 'float', 'f8', '<f8', '>f8', 'd']) -> dok_array[float64, tuple[int, int]]
- sklearn/utils/tests/test_sparsefuncs.py:451: note:     def hstack(blocks: Sequence[sparray[Any, tuple[Any, ...]]], format: Literal['lil'], dtype: type[JustFloat] | type[floating[_64Bit]] | dtype[floating[_64Bit]] | HasDType[dtype[floating[_64Bit]]] | Literal['float64', 'double', 'float', 'f8', '<f8', '>f8', 'd']) -> lil_array[float64]
- sklearn/utils/tests/test_sparsefuncs.py:451: note:     def [T] hstack(blocks: Sequence[_CanStackAs[complex128, T]], format: None = ..., *, dtype: type[JustComplex] | type[complexfloating[_64Bit, _64Bit]] | dtype[complexfloating[_64Bit, _64Bit]] | HasDType[dtype[complexfloating[_64Bit, _64Bit]]] | Literal['complex', 'complex128', 'cdouble', 'c16', '<c16', '>c16', 'D']) -> T
- sklearn/utils/tests/test_sparsefuncs.py:451: note:     def hstack(blocks: Sequence[sparray[Any, tuple[Any, ...]]], format: Literal['bsr'], dtype: type[JustComplex] | type[complexfloating[_64Bit, _64Bit]] | dtype[complexfloating[_64Bit, _64Bit]] | HasDType[dtype[complexfloating[_64Bit, _64Bit]]] | Literal['complex', 'complex128', 'cdouble', 'c16', '<c16', '>c16', 'D']) -> bsr_array[complex128]
- sklearn/utils/tests/test_sparsefuncs.py:451: note:     def hstack(blocks: Sequence[sparray[Any, tuple[Any, ...]]], format: Literal['coo'], dtype: type[JustComplex] | type[complexfloating[_64Bit, _64Bit]] | dtype[complexfloating[_64Bit, _64Bit]] | HasDType[dtype[complexfloating[_64Bit, _64Bit]]] | Literal['complex', 'complex128', 'cdouble', 'c16', '<c16', '>c16', 'D']) -> coo_array[complex128, tuple[int, int]]
- sklearn/utils/tests/test_sparsefuncs.py:451: note:     def hstack(blocks: Sequence[sparray[Any, tuple[Any, ...]]], format: Literal['csc'], dtype: type[JustComplex] | type[complexfloating[_64Bit, _64Bit]] | dtype[complexfloating[_64Bit, _64Bit]] | HasDType[dtype[complexfloating[_64Bit, _64Bit]]] | Literal['complex', 'complex128', 'cdouble', 'c16', '<c16', '>c16', 'D']) -> csc_array[complex128]
- sklearn/utils/tests/test_sparsefuncs.py:451: note:     def hstack(blocks: Sequence[sparray[Any, tuple[Any, ...]]], format: Literal['csr'], dtype: type[JustComplex] | type[complexfloating[_64Bit, _64Bit]] | dtype[complexfloating[_64Bit, _64Bit]] | HasDType[dtype[complexfloating[_64Bit, _64Bit]]] | Literal['complex', 'complex128', 'cdouble', 'c16', '<c16', '>c16', 'D']) -> csr_array[complex128, tuple[int, int]]
- sklearn/utils/tests/test_sparsefuncs.py:451: note:     def hstack(blocks: Sequence[sparray[Any, tuple[Any, ...]]], format: Literal['dia'], dtype: type[JustComplex] | type[complexfloating[_64Bit, _64Bit]] | dtype[complexfloating[_64Bit, _64Bit]] | HasDType[dtype[complexfloating[_64Bit, _64Bit]]] | Literal['complex', 'complex128', 'cdouble', 'c16', '<c16', '>c16', 'D']) -> dia_array[complex128]
- sklearn/utils/tests/test_sparsefuncs.py:451: note:     def hstack(blocks: Sequence[sparray[Any, tuple[Any, ...]]], format: Literal['dok'], dtype: type[JustComplex] | type[complexfloating[_64Bit, _64Bit]] | dtype[complexfloating[_64Bit, _64Bit]] | HasDType[dtype[complexfloating[_64Bit, _64Bit]]] | Literal['complex', 'complex128', 'cdouble', 'c16', '<c16', '>c16', 'D']) -> dok_array[complex128, tuple[int, int]]

... (truncated 54 lines) ...

pandas (https://github.com/pandas-dev/pandas)
+ pandas/tests/dtypes/test_missing.py:861: error: Unused "type: ignore" comment  [unused-ignore]
+ pandas/tests/dtypes/test_missing.py:883: error: Unused "type: ignore" comment  [unused-ignore]
+ pandas/conftest.py:1746: error: Unused "type: ignore" comment  [unused-ignore]

xarray (https://github.com/pydata/xarray)
+ xarray/tests/test_dataarray.py:425: error: Unused "type: ignore" comment  [unused-ignore]

ibis (https://github.com/ibis-project/ibis)
- ibis/expr/operations/udf.py:144: error: Dict entry 1 has incompatible type "str": "InputType"; expected "str": "Argument"  [dict-item]
- ibis/expr/operations/udf.py:147: error: Dict entry 2 has incompatible type "str": "property"; expected "str": "Argument"  [dict-item]
- ibis/expr/operations/udf.py:148: error: Dict entry 3 has incompatible type "str": "FrozenDict[Never, Never]"; expected "str": "Argument"  [dict-item]
- ibis/expr/operations/udf.py:149: error: Dict entry 4 has incompatible type "str": "Namespace"; expected "str": "Argument"  [dict-item]
- ibis/expr/operations/udf.py:150: error: Dict entry 5 has incompatible type "str": "str"; expected "str": "Argument"  [dict-item]
- ibis/expr/operations/udf.py:151: error: Dict entry 6 has incompatible type "str": "str"; expected "str": "Argument"  [dict-item]

dd-trace-py (https://github.com/DataDog/dd-trace-py)
- ddtrace/testing/internal/pytest/plugin.py:1443: error: Unsupported operand types for + ("list[tuple[str, object]]" and "list[tuple[str, bool]]")  [operator]

alectryon (https://github.com/cpitclaudel/alectryon)
- alectryon/core.py:251: error: Argument 3 to "ungroup_by" has incompatible type "Callable[[T], K | None]"; expected "Callable[[T | U], K | None]"  [arg-type]

@rheard

rheard commented Aug 25, 2026

Copy link
Copy Markdown
Contributor Author

@randolf-scholz Thanks, that was very helpful! I was not familiar with mypy-issues.

It seems like this PR would fix a bunch of issues. The first issue in the report there, #21809, I actually commented on and already added a unit test to cover that edge case specifically. #5874 seems to be basically complaining about the root cause here.

However there is one thing that concerned me with that check: #16522. It seems the reported error has changed which worries me. The fallback I added was checking that its inferred return type satisfied the outer context, but an ambiguously inferred Never trivially satisfies that because Never is a subtype of everything.

I've added the same ambiguous-uninhabited/erased-type guards that contextual inference already uses, along with a regression test, and reran the checks.

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.

2 participants