Skip to content

[ty] Reject ellipsis literals in odd places in type/annotation expressions#23611

Merged
AlexWaygood merged 6 commits intomainfrom
claude/fix-callable-type-validation-IAr5S
Feb 27, 2026
Merged

[ty] Reject ellipsis literals in odd places in type/annotation expressions#23611
AlexWaygood merged 6 commits intomainfrom
claude/fix-callable-type-validation-IAr5S

Conversation

@AlexWaygood
Copy link
Member

Summary

More low-hanging fruit to improve our conformance score

Test plan

mdtests

claude and others added 3 commits February 27, 2026 14:37
Ellipsis literals inside a `Callable` parameter list (e.g.,
`Callable[[...], int]`) are now rejected with an `invalid-type-form`
diagnostic. The valid gradual form `Callable[..., int]` (bare ellipsis
as the first argument) is unaffected.

https://claude.ai/code/session_01KDdJ3EZbcBiVeEADaLjPdy
Move the ellipsis rejection from the Callable-specific parameter list
loop to `infer_type_expression_no_store`, where it applies to all type
expression contexts. This is analogous to how int/bool/float literals
are rejected as type expressions.

To avoid double-reporting or false positives, also:
- Skip `infer_type_expression` for `...` in the `Concatenate` argument
  loop (the trailing `...` is valid there)
- Skip `infer_type_expression` for `...` in the tuple multi-element and
  single-element invalid-position handlers (they already report their
  own specific tuple error)
- Handle `EllipsisLiteral` in `infer_annotation_expression_impl` to
  preserve the existing behavior for type alias bodies (`type Foo = ...`)

https://claude.ai/code/session_01KDdJ3EZbcBiVeEADaLjPdy
@AlexWaygood AlexWaygood added ty Multi-file analysis & type inference ecosystem-analyzer labels Feb 27, 2026
@astral-sh-bot
Copy link

astral-sh-bot bot commented Feb 27, 2026

Typing conformance results

The percentage of diagnostics emitted that were expected errors decreased from 85.39% to 85.32%. The percentage of expected errors that received a diagnostic increased from 75.46% to 75.55%.

Summary

Metric Old New Diff Outcome
True Positives 824 825 +1 ⏫ (✅)
False Positives 141 142 +1 ⏫ (❌)
False Negatives 268 267 -1 ⏬ (✅)
Total Diagnostics 965 967 +2
Precision 85.39% 85.32% -0.07% ⏬ (❌)
Recall 75.46% 75.55% +0.09% ⏫ (✅)

True positives added

Details
Location Name Message
callables_annotation.py:59:14 invalid-type-form [...] is not a valid parameter list for Callable: Did you mean Callable[..., int]?

@astral-sh-bot
Copy link

astral-sh-bot bot commented Feb 27, 2026

mypy_primer results

Changes were detected when running on open source projects
packaging (https://github.com/pypa/packaging)
+ src/packaging/version.py:40:38: error[invalid-type-form] `[...]` is not a valid parameter list for `Callable`: Did you mean `Callable[..., object]`?
- Found 17 diagnostics
+ Found 18 diagnostics

spack (https://github.com/spack/spack)
- lib/spack/spack/detection/path.py:169:33: error[invalid-argument-type] Argument to function `dedupe_paths` is incorrect: Expected `list[str]`, found `Unknown | list[int | PathLike[str] | PathLike[bytes] | ... omitted 3 union elements]`
+ lib/spack/spack/detection/path.py:169:33: error[invalid-argument-type] Argument to function `dedupe_paths` is incorrect: Expected `list[str]`, found `Unknown | list[Unknown | int | str | ... omitted 3 union elements]`
- lib/spack/spack/llnl/util/filesystem.py:1668:35: error[invalid-argument-type] Argument to function `exists` is incorrect: Expected `int | str | bytes | PathLike[str] | PathLike[bytes]`, found `Sized | Unknown`
+ lib/spack/spack/llnl/util/filesystem.py:1668:35: error[invalid-argument-type] Argument to function `exists` is incorrect: Expected `int | str | bytes | PathLike[str] | PathLike[bytes]`, found `Unknown | Sized`
- lib/spack/spack/llnl/util/filesystem.py:1674:25: error[invalid-argument-type] Argument to function `move` is incorrect: Expected `str | PathLike[str]`, found `Sized | Unknown`
+ lib/spack/spack/llnl/util/filesystem.py:1674:25: error[invalid-argument-type] Argument to function `move` is incorrect: Expected `str | PathLike[str]`, found `Unknown | Sized`

aiortc (https://github.com/aiortc/aiortc)
- src/aiortc/sdp.py:446:52: error[invalid-argument-type] Argument is incorrect: Expected `str`, found `None | Unknown | str`
+ src/aiortc/sdp.py:446:52: error[invalid-argument-type] Argument is incorrect: Expected `str`, found `None | str | Unknown`

pylox (https://github.com/sco1/pylox)
- pylox/containers/array.py:146:9: error[invalid-assignment] Object of type `deque[None | Unknown]` is not assignable to attribute `fields` of type `dict[Unknown, Unknown]`
+ pylox/containers/array.py:146:9: error[invalid-assignment] Object of type `deque[Unknown | None]` is not assignable to attribute `fields` of type `dict[Unknown, Unknown]`

Expression (https://github.com/cognitedata/Expression)
- tests/test_compose.py:21:16: error[invalid-assignment] Object of type `(Never, /) -> Never` is not assignable to `(int, /) -> int`
- Found 205 diagnostics
+ Found 204 diagnostics

vision (https://github.com/pytorch/vision)
- test/test_transforms_v2.py:1551:49: error[invalid-argument-type] Argument to bound method `__init__` is incorrect: Expected `Number | Sequence[Unknown]`, found `Unknown | tuple[int, int, int, int] | int | tuple[int | float, int | float]`
+ test/test_transforms_v2.py:1551:49: error[invalid-argument-type] Argument to bound method `__init__` is incorrect: Expected `Number | Sequence[Unknown]`, found `Unknown | int | tuple[int | float, int | float] | tuple[int, int, int, int]`
- test/test_transforms_v2.py:1551:49: error[invalid-argument-type] Argument to bound method `__init__` is incorrect: Expected `Sequence[int | float] | None`, found `Unknown | tuple[int, int, int, int] | int | tuple[int | float, int | float]`
+ test/test_transforms_v2.py:1551:49: error[invalid-argument-type] Argument to bound method `__init__` is incorrect: Expected `Sequence[int | float] | None`, found `Unknown | int | tuple[int | float, int | float] | tuple[int, int, int, int]`
- test/test_transforms_v2.py:1551:49: error[invalid-argument-type] Argument to bound method `__init__` is incorrect: Expected `Sequence[int | float] | None`, found `Unknown | tuple[int, int, int, int] | int | tuple[int | float, int | float]`
+ test/test_transforms_v2.py:1551:49: error[invalid-argument-type] Argument to bound method `__init__` is incorrect: Expected `Sequence[int | float] | None`, found `Unknown | int | tuple[int | float, int | float] | tuple[int, int, int, int]`
- test/test_transforms_v2.py:1551:49: error[invalid-argument-type] Argument to bound method `__init__` is incorrect: Expected `InterpolationMode | int`, found `Unknown | tuple[int, int, int, int] | int | tuple[int | float, int | float]`
+ test/test_transforms_v2.py:1551:49: error[invalid-argument-type] Argument to bound method `__init__` is incorrect: Expected `InterpolationMode | int`, found `Unknown | int | tuple[int | float, int | float] | tuple[int, int, int, int]`
- test/test_transforms_v2.py:1551:49: error[invalid-argument-type] Argument to bound method `__init__` is incorrect: Expected `list[int | float] | None`, found `Unknown | tuple[int, int, int, int] | int | tuple[int | float, int | float]`
+ test/test_transforms_v2.py:1551:49: error[invalid-argument-type] Argument to bound method `__init__` is incorrect: Expected `list[int | float] | None`, found `Unknown | int | tuple[int | float, int | float] | tuple[int, int, int, int]`
- test/test_transforms_v2.py:1605:13: error[invalid-argument-type] Argument to bound method `__init__` is incorrect: Expected `Number | Sequence[Unknown]`, found `Unknown | tuple[int, int, int, int] | int | tuple[int | float, int | float]`
+ test/test_transforms_v2.py:1605:13: error[invalid-argument-type] Argument to bound method `__init__` is incorrect: Expected `Number | Sequence[Unknown]`, found `Unknown | int | tuple[int | float, int | float] | tuple[int, int, int, int]`
- test/test_transforms_v2.py:1605:13: error[invalid-argument-type] Argument to bound method `__init__` is incorrect: Expected `Sequence[int | float] | None`, found `Unknown | tuple[int, int, int, int] | int | tuple[int | float, int | float]`
+ test/test_transforms_v2.py:1605:13: error[invalid-argument-type] Argument to bound method `__init__` is incorrect: Expected `Sequence[int | float] | None`, found `Unknown | int | tuple[int | float, int | float] | tuple[int, int, int, int]`
- test/test_transforms_v2.py:1605:13: error[invalid-argument-type] Argument to bound method `__init__` is incorrect: Expected `Sequence[int | float] | None`, found `Unknown | tuple[int, int, int, int] | int | tuple[int | float, int | float]`
+ test/test_transforms_v2.py:1605:13: error[invalid-argument-type] Argument to bound method `__init__` is incorrect: Expected `Sequence[int | float] | None`, found `Unknown | int | tuple[int | float, int | float] | tuple[int, int, int, int]`
- test/test_transforms_v2.py:1692:45: error[invalid-argument-type] Argument to bound method `__init__` is incorrect: Expected `Number | Sequence[Unknown]`, found `Unknown | tuple[int, int, int, int] | int | tuple[int | float, int | float]`
+ test/test_transforms_v2.py:1692:45: error[invalid-argument-type] Argument to bound method `__init__` is incorrect: Expected `Number | Sequence[Unknown]`, found `Unknown | int | tuple[int | float, int | float] | tuple[int, int, int, int]`
- test/test_transforms_v2.py:1692:45: error[invalid-argument-type] Argument to bound method `__init__` is incorrect: Expected `Sequence[int | float] | None`, found `Unknown | tuple[int, int, int, int] | int | tuple[int | float, int | float]`
+ test/test_transforms_v2.py:1692:45: error[invalid-argument-type] Argument to bound method `__init__` is incorrect: Expected `Sequence[int | float] | None`, found `Unknown | int | tuple[int | float, int | float] | tuple[int, int, int, int]`
- test/test_transforms_v2.py:1692:45: error[invalid-argument-type] Argument to bound method `__init__` is incorrect: Expected `Sequence[int | float] | None`, found `Unknown | tuple[int, int, int, int] | int | tuple[int | float, int | float]`
+ test/test_transforms_v2.py:1692:45: error[invalid-argument-type] Argument to bound method `__init__` is incorrect: Expected `Sequence[int | float] | None`, found `Unknown | int | tuple[int | float, int | float] | tuple[int, int, int, int]`
- test/test_transforms_v2.py:1692:45: error[invalid-argument-type] Argument to bound method `__init__` is incorrect: Expected `InterpolationMode | int`, found `Unknown | tuple[int, int, int, int] | int | tuple[int | float, int | float]`
+ test/test_transforms_v2.py:1692:45: error[invalid-argument-type] Argument to bound method `__init__` is incorrect: Expected `InterpolationMode | int`, found `Unknown | int | tuple[int | float, int | float] | tuple[int, int, int, int]`
- test/test_transforms_v2.py:1747:45: error[invalid-argument-type] Argument to bound method `__init__` is incorrect: Expected `Number | Sequence[Unknown]`, found `Unknown | tuple[int, int, int, int] | int | tuple[int | float, int | float]`
+ test/test_transforms_v2.py:1747:45: error[invalid-argument-type] Argument to bound method `__init__` is incorrect: Expected `Number | Sequence[Unknown]`, found `Unknown | int | tuple[int | float, int | float] | tuple[int, int, int, int]`
- test/test_transforms_v2.py:1747:45: error[invalid-argument-type] Argument to bound method `__init__` is incorrect: Expected `Sequence[int | float] | None`, found `Unknown | tuple[int, int, int, int] | int | tuple[int | float, int | float]`
+ test/test_transforms_v2.py:1747:45: error[invalid-argument-type] Argument to bound method `__init__` is incorrect: Expected `Sequence[int | float] | None`, found `Unknown | int | tuple[int | float, int | float] | tuple[int, int, int, int]`
- test/test_transforms_v2.py:1747:45: error[invalid-argument-type] Argument to bound method `__init__` is incorrect: Expected `Sequence[int | float] | None`, found `Unknown | tuple[int, int, int, int] | int | tuple[int | float, int | float]`
+ test/test_transforms_v2.py:1747:45: error[invalid-argument-type] Argument to bound method `__init__` is incorrect: Expected `Sequence[int | float] | None`, found `Unknown | int | tuple[int | float, int | float] | tuple[int, int, int, int]`
- test/test_transforms_v2.py:1747:45: error[invalid-argument-type] Argument to bound method `__init__` is incorrect: Expected `InterpolationMode | int`, found `Unknown | tuple[int, int, int, int] | int | tuple[int | float, int | float]`
+ test/test_transforms_v2.py:1747:45: error[invalid-argument-type] Argument to bound method `__init__` is incorrect: Expected `InterpolationMode | int`, found `Unknown | int | tuple[int | float, int | float] | tuple[int, int, int, int]`

xarray (https://github.com/pydata/xarray)
+ xarray/core/indexing.py:427:40: error[invalid-type-form] `...` is not allowed in this context in a type expression
+ xarray/core/indexing.py:433:35: error[invalid-type-form] `...` is not allowed in this context in a type expression
- Found 1712 diagnostics
+ Found 1714 diagnostics

meson (https://github.com/mesonbuild/meson)
- mesonbuild/dependencies/cuda.py:137:76: error[invalid-argument-type] Argument to function `version_compare_many` is incorrect: Expected `str`, found `str | None | Unknown`
+ mesonbuild/dependencies/cuda.py:137:76: error[invalid-argument-type] Argument to function `version_compare_many` is incorrect: Expected `str`, found `Unknown | str | None`

discord.py (https://github.com/Rapptz/discord.py)
+ discord/app_commands/commands.py:149:92: error[invalid-type-form] `...` is not allowed in this context in a type expression
- Found 537 diagnostics
+ Found 538 diagnostics

mypy (https://github.com/python/mypy)
+ mypy/typeshed/stdlib/builtins.pyi:133:46: error[invalid-type-form] `...` is not allowed in this context in a type expression
+ mypy/typeshed/stdlib/builtins.pyi:134:77: error[invalid-type-form] `...` is not allowed in this context in a type expression
+ mypy/typeshed/stdlib/builtins.pyi:189:28: error[invalid-type-form] `...` is not allowed in this context in a type expression
+ mypy/typeshed/stdlib/builtins.pyi:203:38: error[invalid-type-form] `...` is not allowed in this context in a type expression
+ mypy/typeshed/stdlib/builtins.pyi:213:54: error[invalid-type-form] `...` is not allowed in this context in a type expression
+ mypy/typeshed/stdlib/builtins.pyi:218:66: error[invalid-type-form] `...` is not allowed in this context in a type expression
+ mypy/typeshed/stdlib/builtins.pyi:228:60: error[invalid-type-form] `...` is not allowed in this context in a type expression
+ mypy/typeshed/stdlib/builtins.pyi:235:68: error[invalid-type-form] `...` is not allowed in this context in a type expression
+ mypy/typeshed/stdlib/builtins.pyi:490:40: error[invalid-type-form] `...` is not allowed in this context in a type expression
+ mypy/typeshed/stdlib/builtins.pyi:530:40: error[invalid-type-form] `...` is not allowed in this context in a type expression
+ mypy/typeshed/stdlib/builtins.pyi:582:56: error[invalid-type-form] `...` is not allowed in this context in a type expression
+ mypy/typeshed/stdlib/builtins.pyi:625:56: error[invalid-type-form] `...` is not allowed in this context in a type expression
+ mypy/typeshed/stdlib/builtins.pyi:688:56: error[invalid-type-form] `...` is not allowed in this context in a type expression
+ mypy/typeshed/stdlib/builtins.pyi:735:56: error[invalid-type-form] `...` is not allowed in this context in a type expression
+ mypy/typeshed/stdlib/builtins.pyi:799:35: error[invalid-type-form] `...` is not allowed in this context in a type expression
+ mypy/typeshed/stdlib/builtins.pyi:801:37: error[invalid-type-form] `...` is not allowed in this context in a type expression
+ mypy/typeshed/stdlib/builtins.pyi:803:40: error[invalid-type-form] `...` is not allowed in this context in a type expression
+ mypy/typeshed/stdlib/builtins.pyi:828:78: error[invalid-type-form] `...` is not allowed in this context in a type expression
+ mypy/typeshed/stdlib/builtins.pyi:830:89: error[invalid-type-form] `...` is not allowed in this context in a type expression
+ mypy/typeshed/stdlib/builtins.pyi:832:72: error[invalid-type-form] `...` is not allowed in this context in a type expression
+ mypy/typeshed/stdlib/builtins.pyi:834:75: error[invalid-type-form] `...` is not allowed in this context in a type expression
+ mypy/typeshed/stdlib/builtins.pyi:836:69: error[invalid-type-form] `...` is not allowed in this context in a type expression
+ mypy/typeshed/stdlib/builtins.pyi:847:69: error[invalid-type-form] `...` is not allowed in this context in a type expression
+ mypy/typeshed/stdlib/builtins.pyi:952:80: error[invalid-type-form] `...` is not allowed in this context in a type expression
+ mypy/typeshed/stdlib/builtins.pyi:954:42: error[invalid-type-form] `...` is not allowed in this context in a type expression
+ mypy/typeshed/stdlib/builtins.pyi:955:42: error[invalid-type-form] `...` is not allowed in this context in a type expression
+ mypy/typeshed/stdlib/builtins.pyi:956:42: error[invalid-type-form] `...` is not allowed in this context in a type expression
+ mypy/typeshed/stdlib/builtins.pyi:957:42: error[invalid-type-form] `...` is not allowed in this context in a type expression
+ mypy/typeshed/stdlib/builtins.pyi:961:43: error[invalid-type-form] `...` is not allowed in this context in a type expression
+ mypy/typeshed/stdlib/builtins.pyi:961:68: error[invalid-type-form] `...` is not allowed in this context in a type expression
+ mypy/typeshed/stdlib/builtins.pyi:963:40: error[invalid-type-form] `...` is not allowed in this context in a type expression
+ mypy/typeshed/stdlib/builtins.pyi:963:70: error[invalid-type-form] `...` is not allowed in this context in a type expression
+ mypy/typeshed/stdlib/builtins.pyi:964:64: error[invalid-type-form] `...` is not allowed in this context in a type expression
+ mypy/typeshed/stdlib/builtins.pyi:965:65: error[invalid-type-form] `...` is not allowed in this context in a type expression
+ mypy/typeshed/stdlib/builtins.pyi:979:46: error[invalid-type-form] `...` is not allowed in this context in a type expression
+ mypy/typeshed/stdlib/builtins.pyi:981:30: error[invalid-type-form] `...` is not allowed in this context in a type expression
+ mypy/typeshed/stdlib/builtins.pyi:995:68: error[invalid-type-form] `...` is not allowed in this context in a type expression
+ mypy/typeshed/stdlib/builtins.pyi:1004:36: error[invalid-type-form] `...` is not allowed in this context in a type expression
+ mypy/typeshed/stdlib/builtins.pyi:1005:38: error[invalid-type-form] `...` is not allowed in this context in a type expression
+ mypy/typeshed/stdlib/builtins.pyi:1014:36: error[invalid-type-form] `...` is not allowed in this context in a type expression
+ mypy/typeshed/stdlib/builtins.pyi:1015:38: error[invalid-type-form] `...` is not allowed in this context in a type expression
+ mypy/typeshed/stdlib/builtins.pyi:1392:34: error[invalid-type-form] `...` is not allowed in this context in a type expression
+ mypy/typeshed/stdlib/builtins.pyi:1402:34: error[invalid-type-form] `...` is not allowed in this context in a type expression
+ mypy/typeshed/stdlib/builtins.pyi:1468:72: error[invalid-type-form] `...` is not allowed in this context in a type expression
+ mypy/typeshed/stdlib/builtins.pyi:1470:54: error[invalid-type-form] `...` is not allowed in this context in a type expression
+ mypy/typeshed/stdlib/builtins.pyi:1898:29: error[invalid-type-form] `...` is not allowed in this context in a type expression
+ mypy/typeshed/stdlib/builtins.pyi:1927:29: error[invalid-type-form] `...` is not allowed in this context in a type expression
+ mypy/typeshed/stdlib/builtins.pyi:1969:22: error[invalid-type-form] `...` is not allowed in this context in a type expression
+ mypy/typeshed/stdlib/builtins.pyi:2176:100: error[invalid-type-form] `...` is not allowed in this context in a type expression
+ mypy/typeshed/stdlib/builtins.pyi:2179:79: error[invalid-type-form] `...` is not allowed in this context in a type expression
+ mypy/typeshed/stdlib/builtins.pyi:2183:87: error[invalid-type-form] `...` is not allowed in this context in a type expression
+ mypy/typeshed/stdlib/builtins.pyi:2191:79: error[invalid-type-form] `...` is not allowed in this context in a type expression
+ mypy/typeshed/stdlib/builtins.pyi:2195:87: error[invalid-type-form] `...` is not allowed in this context in a type expression
+ mypy/typeshed/stdlib/builtins.pyi:2212:88: error[invalid-type-form] `...` is not allowed in this context in a type expression
+ mypy/typeshed/stdlib/builtins.pyi:2216:79: error[invalid-type-form] `...` is not allowed in this context in a type expression
+ mypy/typeshed/stdlib/builtins.pyi:2224:79: error[invalid-type-form] `...` is not allowed in this context in a type expression
+ mypy/typeshed/stdlib/typing.pyi:996:38: error[invalid-type-form] `...` is not allowed in this context in a type expression
+ mypy/typeshed/stdlib/typing.pyi:996:73: error[invalid-type-form] `...` is not allowed in this context in a type expression
+ mypy/typeshed/stdlib/typing.pyi:1003:54: error[invalid-type-form] `...` is not allowed in this context in a type expression
- Found 1755 diagnostics
+ Found 1814 diagnostics

setuptools (https://github.com/pypa/setuptools)
+ setuptools/_vendor/jaraco/functools/__init__.py:680:37: error[invalid-type-form] `...` is not allowed in this context in a type expression
+ setuptools/_vendor/jaraco/functools/__init__.py:680:67: error[invalid-type-form] `...` is not allowed in this context in a type expression
- Found 1119 diagnostics
+ Found 1121 diagnostics

prefect (https://github.com/PrefectHQ/prefect)
- src/prefect/input/run_input.py:672:20: error[invalid-return-type] Return type does not match returned value: expected `T@GetAutomaticInputHandler | AutomaticRunInput[T@GetAutomaticInputHandler]`, found `T@GetAutomaticInputHandler | AutomaticRunInput[T@GetAutomaticInputHandler] | Coroutine[Any, Any, T@GetAutomaticInputHandler | AutomaticRunInput[T@GetAutomaticInputHandler]]`
+ src/prefect/input/run_input.py:672:20: error[invalid-return-type] Return type does not match returned value: expected `T@GetAutomaticInputHandler | AutomaticRunInput[T@GetAutomaticInputHandler]`, found `Unknown | Coroutine[Any, Any, Unknown]`

scikit-build-core (https://github.com/scikit-build/scikit-build-core)
+ src/scikit_build_core/build/wheel.py:99:20: error[no-matching-overload] No overload of bound method `__init__` matches arguments
- Found 57 diagnostics
+ Found 58 diagnostics

dd-trace-py (https://github.com/DataDog/dd-trace-py)
- scripts/freshvenvs.py:343:69: error[invalid-argument-type] Argument to function `_versions_fully_cover_bounds` is incorrect: Expected `list[str]`, found `list[Version | Unknown] & ~AlwaysFalsy`
+ scripts/freshvenvs.py:343:69: error[invalid-argument-type] Argument to function `_versions_fully_cover_bounds` is incorrect: Expected `list[str]`, found `list[Unknown | Version] & ~AlwaysFalsy`
- setup.py:1280:5: error[invalid-argument-type] Argument to function `setup` is incorrect: Expected `_MutableDictLike[str, type[Command]]`, found `dict[Unknown | str, Unknown | <class 'CustomBuildExt'> | <class 'LibraryDownloader'> | ... omitted 3 union elements]`
+ setup.py:1280:5: error[invalid-argument-type] Argument to function `setup` is incorrect: Expected `_MutableDictLike[str, type[Command]]`, found `dict[str, <class 'CustomBuildExt'> | <class 'LibraryDownloader'> | <class 'CustomBuildRust'> | <class 'CleanLibraries'> | <class 'ExtensionHashes'>]`
- tests/tracer/test_span.py:193:29: error[invalid-argument-type] Argument to bound method `set_metric` is incorrect: Expected `int | float`, found `Span | Unknown | None | ... omitted 6 union elements`
+ tests/tracer/test_span.py:193:29: error[invalid-argument-type] Argument to bound method `set_metric` is incorrect: Expected `int | float`, found `int | float | complex | ... omitted 6 union elements`

django-stubs (https://github.com/typeddjango/django-stubs)
+ django-stubs/contrib/gis/gdal/geometries.pyi:118:55: error[invalid-type-form] `...` is not allowed in this context in a type expression
+ django-stubs/contrib/gis/gdal/geometries.pyi:121:44: error[invalid-type-form] `...` is not allowed in this context in a type expression
+ django-stubs/contrib/gis/gdal/geometries.pyi:121:50: error[invalid-type-form] `...` is not allowed in this context in a type expression
+ django-stubs/contrib/gis/gdal/geometries.pyi:123:43: error[invalid-type-form] `...` is not allowed in this context in a type expression
+ django-stubs/contrib/gis/gdal/geometries.pyi:123:49: error[invalid-type-form] `...` is not allowed in this context in a type expression
+ django-stubs/contrib/gis/gdal/geometries.pyi:143:50: error[invalid-type-form] `...` is not allowed in this context in a type expression
+ django-stubs/contrib/gis/gdal/geometries.pyi:143:56: error[invalid-type-form] `...` is not allowed in this context in a type expression
+ django-stubs/contrib/gis/gdal/geometries.pyi:143:62: error[invalid-type-form] `...` is not allowed in this context in a type expression
+ django-stubs/contrib/gis/gdal/geometries.pyi:145:49: error[invalid-type-form] `...` is not allowed in this context in a type expression
+ django-stubs/contrib/gis/gdal/geometries.pyi:158:36: error[invalid-type-form] `...` is not allowed in this context in a type expression
+ django-stubs/contrib/gis/gdal/geometries.pyi:160:35: error[invalid-type-form] `...` is not allowed in this context in a type expression
+ django-stubs/contrib/gis/geos/collections.pyi:11:36: error[invalid-type-form] `...` is not allowed in this context in a type expression
+ django-stubs/contrib/gis/geos/collections.pyi:13:35: error[invalid-type-form] `...` is not allowed in this context in a type expression
+ django-stubs/contrib/gis/geos/coordseq.pyi:10:49: error[invalid-type-form] `...` is not allowed in this context in a type expression
+ django-stubs/contrib/gis/geos/coordseq.pyi:32:35: error[invalid-type-form] `...` is not allowed in this context in a type expression
+ django-stubs/contrib/gis/geos/linestring.pyi:9:49: error[invalid-type-form] `...` is not allowed in this context in a type expression
+ django-stubs/contrib/gis/geos/linestring.pyi:12:44: error[invalid-type-form] `...` is not allowed in this context in a type expression
+ django-stubs/contrib/gis/geos/linestring.pyi:14:43: error[invalid-type-form] `...` is not allowed in this context in a type expression
+ django-stubs/contrib/gis/geos/point.pyi:26:38: error[invalid-type-form] `...` is not allowed in this context in a type expression
+ django-stubs/contrib/gis/geos/point.pyi:30:37: error[invalid-type-form] `...` is not allowed in this context in a type expression
+ django-stubs/contrib/gis/geos/polygon.pyi:18:50: error[invalid-type-form] `...` is not allowed in this context in a type expression
+ django-stubs/contrib/gis/geos/polygon.pyi:20:49: error[invalid-type-form] `...` is not allowed in this context in a type expression
- Found 502 diagnostics
+ Found 524 diagnostics

bokeh (https://github.com/bokeh/bokeh)
- src/bokeh/layouts.py:670:21: error[invalid-argument-type] Argument to bound method `__init__` is incorrect: Expected `Iterable[L@_parse_children_arg]`, found `tuple[L@_parse_children_arg | list[L@_parse_children_arg], ...]`
+ src/bokeh/layouts.py:670:16: error[invalid-return-type] Return type does not match returned value: expected `list[L@_parse_children_arg]`, found `list[L@_parse_children_arg | list[L@_parse_children_arg]]`

sympy (https://github.com/sympy/sympy)
- sympy/geometry/tests/test_util.py:129:55: error[invalid-argument-type] Argument to function `subsets` is incorrect: Expected `Sequence[Unknown]`, found `set[Point2D | Unknown]`
+ sympy/geometry/tests/test_util.py:129:55: error[invalid-argument-type] Argument to function `subsets` is incorrect: Expected `Sequence[Unknown]`, found `set[Unknown | Point2D]`

materialize (https://github.com/MaterializeInc/materialize)
+ misc/python/materialize/cli/mz_workload_anonymize.py:251:13: error[no-matching-overload] No overload of bound method `join` matches arguments
- Found 527 diagnostics
+ Found 528 diagnostics

scikit-learn (https://github.com/scikit-learn/scikit-learn)
- sklearn/utils/tests/test_multiclass.py:399:27: error[unresolved-attribute] Attribute `toarray` is not defined on `list[Unknown | list[Unknown | int]] & SparseABC`, `ndarray[tuple[Any, ...], dtype[Any]] & SparseABC`, `_NotAnArray & SparseABC`, `list[Unknown | int] & SparseABC`, `list[Unknown | str] & SparseABC`, `list[Unknown | list[Unknown | str]] & SparseABC`, `list[Unknown] & SparseABC`, `list[Unknown | int | float] & SparseABC`, `list[Unknown | list[Unknown]] & SparseABC`, `list[Unknown | tuple[()]] & SparseABC`, `list[Unknown | ndarray[tuple[Any, ...], dtype[Unknown]]] & SparseABC`, `list[Unknown | set[Unknown | int]] & SparseABC`, `list[Unknown | frozenset[int | Unknown]] & SparseABC`, `list[Unknown | dict[Unknown | int, Unknown | str]] & SparseABC` in union `(Unknown & SparseABC) | (list[Unknown | list[Unknown | int]] & SparseABC) | (ndarray[tuple[Any, ...], dtype[Any]] & SparseABC) | ... omitted 12 union elements`
+ sklearn/utils/tests/test_multiclass.py:399:27: error[unresolved-attribute] Attribute `toarray` is not defined on `list[Unknown | list[Unknown | int]] & SparseABC`, `ndarray[tuple[Any, ...], dtype[Any]] & SparseABC`, `_NotAnArray & SparseABC`, `list[Unknown | int] & SparseABC`, `list[Unknown | str] & SparseABC`, `list[Unknown | list[Unknown | str]] & SparseABC`, `list[Unknown] & SparseABC`, `list[Unknown | int | float] & SparseABC`, `list[Unknown | list[Unknown]] & SparseABC`, `list[Unknown | tuple[()]] & SparseABC`, `list[Unknown | ndarray[tuple[Any, ...], dtype[Unknown]]] & SparseABC`, `list[Unknown | set[Unknown | int]] & SparseABC`, `list[Unknown | frozenset[Unknown | int]] & SparseABC`, `list[Unknown | dict[Unknown | int, Unknown | str]] & SparseABC` in union `(Unknown & SparseABC) | (list[Unknown | list[Unknown | int]] & SparseABC) | (ndarray[tuple[Any, ...], dtype[Any]] & SparseABC) | ... omitted 12 union elements`

static-frame (https://github.com/static-frame/static-frame)
- static_frame/test/unit/test_bus.py:2137:26: error[unresolved-attribute] Attribute `to_pairs` is not defined on `ndarray[Any, Any]` in union `ndarray[Any, Any] | @Todo | Series[Any, Any]`
+ static_frame/test/unit/test_bus.py:2137:26: error[unresolved-attribute] Attribute `to_pairs` is not defined on `ndarray[Any, Any]` in union `@Todo | Series[Any, Any] | ndarray[Any, Any]`
- static_frame/test/unit/test_bus.py:2138:26: error[unresolved-attribute] Attribute `to_pairs` is not defined on `ndarray[Any, Any]` in union `ndarray[Any, Any] | @Todo | Series[Any, Any]`
+ static_frame/test/unit/test_bus.py:2138:26: error[unresolved-attribute] Attribute `to_pairs` is not defined on `ndarray[Any, Any]` in union `@Todo | Series[Any, Any] | ndarray[Any, Any]`
- static_frame/test/unit/test_bus.py:2166:26: error[unresolved-attribute] Attribute `to_pairs` is not defined on `ndarray[Any, Any]` in union `ndarray[Any, Any] | @Todo | Series[Any, Any]`
+ static_frame/test/unit/test_bus.py:2166:26: error[unresolved-attribute] Attribute `to_pairs` is not defined on `ndarray[Any, Any]` in union `@Todo | Series[Any, Any] | ndarray[Any, Any]`
- static_frame/test/unit/test_bus.py:2167:26: error[unresolved-attribute] Attribute `to_pairs` is not defined on `ndarray[Any, Any]` in union `ndarray[Any, Any] | @Todo | Series[Any, Any]`
+ static_frame/test/unit/test_bus.py:2167:26: error[unresolved-attribute] Attribute `to_pairs` is not defined on `ndarray[Any, Any]` in union `@Todo | Series[Any, Any] | ndarray[Any, Any]`

jax (https://github.com/google/jax)
- jax/_src/export/_export.py:1378:45: error[invalid-argument-type] Argument to function `_get_named_sharding` is incorrect: Expected `ShapedArray`, found `Unknown | AbstractValue`
+ jax/_src/export/_export.py:1378:45: error[invalid-argument-type] Argument to function `_get_named_sharding` is incorrect: Expected `ShapedArray`, found `AbstractValue | Unknown`
- jax/_src/pallas/hlo_interpreter.py:433:37: error[invalid-argument-type] Argument is incorrect: Expected `int`, found `int | Unknown | DynamicGridDim`
+ jax/_src/pallas/hlo_interpreter.py:433:37: error[invalid-argument-type] Argument is incorrect: Expected `int`, found `int | DynamicGridDim | Unknown`
- jax/_src/pallas/mosaic/interpret/interpret_pallas_call.py:1934:37: error[invalid-argument-type] Argument is incorrect: Expected `int`, found `int | Unknown | DynamicGridDim`
+ jax/_src/pallas/mosaic/interpret/interpret_pallas_call.py:1934:37: error[invalid-argument-type] Argument is incorrect: Expected `int`, found `int | DynamicGridDim | Unknown`
- jax/_src/pallas/pallas_call.py:862:37: error[invalid-argument-type] Argument is incorrect: Expected `int`, found `int | Unknown | DynamicGridDim`
+ jax/_src/pallas/pallas_call.py:862:37: error[invalid-argument-type] Argument is incorrect: Expected `int`, found `int | DynamicGridDim | Unknown`

pandas-stubs (https://github.com/pandas-dev/pandas-stubs)
- tests/series/test_series.py:2441:9: error[type-assertion-failure] Type `Unknown` does not match asserted type `Series[Timestamp]`
- Found 4707 diagnostics
+ Found 4706 diagnostics

core (https://github.com/home-assistant/core)
- homeassistant/components/teslemetry/climate.py:154:37: error[unresolved-attribute] Attribute `index` is not defined on `None` in union `Unknown | list[str | Unknown] | list[str] | None`
+ homeassistant/components/teslemetry/climate.py:154:37: error[unresolved-attribute] Attribute `index` is not defined on `None` in union `Unknown | list[Unknown | str] | list[str] | None`

colour (https://github.com/colour-science/colour)
+ colour/characterisation/aces_it.py:976:20: error[invalid-type-form] `...` is not allowed in this context in a type expression
+ colour/characterisation/aces_it.py:977:17: error[invalid-type-form] `...` is not allowed in this context in a type expression
- Found 454 diagnostics
+ Found 456 diagnostics

@astral-sh-bot
Copy link

astral-sh-bot bot commented Feb 27, 2026

Memory usage report

Memory usage unchanged ✅

@astral-sh-bot
Copy link

astral-sh-bot bot commented Feb 27, 2026

ecosystem-analyzer results

Lint rule Added Removed Changed
invalid-type-form 89 0 0
invalid-await 40 0 0
invalid-argument-type 0 1 0
invalid-return-type 1 0 0
Total 130 1 0

Full report with detailed diff (timing results)

Change from "Ellipsis literal (`...`) is not allowed in this context
in a type expression" to the shorter "`...` is not allowed in this
context in a type expression".

The subdiagnostic linking to the type-expression grammar is already
included via `report_invalid_type_expression` →
`add_type_expression_reference_link`.

https://claude.ai/code/session_01KDdJ3EZbcBiVeEADaLjPdy
When `...` is the sole element inside the parameter list of a
`Callable` type (i.e. `Callable[[...], int]`), add a subdiagnostic
suggesting the correct gradual form: `Callable[..., <return type>]`.

This is the only ellipsis-in-type-expression case where the user's
intent is unambiguous enough to warrant a suggestion. Other cases
(e.g. `Callable[[int, ...], R]`, `Union[int, ...]`, `list[...]`)
are left without suggestions since the intended type is unclear.

https://claude.ai/code/session_01KDdJ3EZbcBiVeEADaLjPdy
@AlexWaygood
Copy link
Member Author

There's a really surprising number of ecosystem hits here, but I think they're all true positives.

The ones in django-stubs and xarray look odd, but it's because they're doing this:

class F:
    def method(self) -> tuple[float, ...]: ...
    def tuple(self): ...

We interpret tuple in the return annotation of method as resolving to the class variable tuple, which is not the builtin tuple class, so ... is therefore invalid in the return annotation of method since you can only use it like that if you're specializing builtins.tuple.

The mypy hits are because we don't understand the tuple in mypy's vendored version of typeshed as being builtins.tuple.

@AlexWaygood AlexWaygood marked this pull request as ready for review February 27, 2026 17:16
@AlexWaygood AlexWaygood force-pushed the claude/fix-callable-type-validation-IAr5S branch from c566bdc to d86e0d9 Compare February 27, 2026 17:17
@carljm
Copy link
Contributor

carljm commented Feb 27, 2026

The conformance results here say we are adding a false positive -- but then don't list any details for that.

@carljm carljm removed their request for review February 27, 2026 20:23
@AlexWaygood
Copy link
Member Author

AlexWaygood commented Feb 27, 2026

The conformance results here say we are adding a false positive -- but then don't list any details for that.

Okay, I added some debug prints to the conformance script. We are indeed adding a new false positive, but the reason why the script got confused is that it's a line on which we already have a false-positive error (we now have 2 on that line, where we previously had only 1):

from typing import Callable

type GoodAlias2[S1, *S2, **S3] = Callable[S3, S1] | tuple[*S2]

# main: 1 false positive:
#
#   error[invalid-type-arguments]: Too many type arguments: expected 2, got 3`
#
# this PR adds:
#
#   error[invalid-type-form]: `...` is not allowed in this context in a type expression`
#
type GoodAlias3 = GoodAlias2[int, tuple[int, str], ...]

But the underlying cause of both these false positives is missing support for TypeVarTuple. Change the snippet to this, and both errors go away on this branch:

from typing import Callable

type GoodAlias2[S1, **S3] = Callable[S3, S1]
type GoodAlias3 = GoodAlias2[int, ...]

@AlexWaygood
Copy link
Member Author

For reference, the debug prints I added were:

diff --git a/scripts/conformance.py b/scripts/conformance.py
index a3680619da..87f96beab8 100644
--- a/scripts/conformance.py
+++ b/scripts/conformance.py
@@ -842,6 +842,12 @@ def main():
         python_version=args.python_version,
         extra_search_paths=extra_search_paths,
     )
+    import pprint
+    with open("old.txt", "w") as f:
+        pprint.pp(old, stream=f)
+
+    print()
+    print()
 
     new = collect_ty_diagnostics(
         ty_path=args.new_ty,
@@ -850,6 +856,8 @@ def main():
         python_version=args.python_version,
         extra_search_paths=extra_search_paths,
     )
+    with open("new.txt", "w") as f:
+        pprint.pp([diag.__replace__(source=Source.OLD) for diag in new], stream=f)
 
     grouped = group_diagnostics_by_key(
         old=old,

and then after running the script with that change applied, I ran git diff --no-index old.txt new.txt

Copy link
Member

@dcreager dcreager left a comment

Choose a reason for hiding this comment

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

👍

@AlexWaygood AlexWaygood merged commit 03af8b2 into main Feb 27, 2026
50 checks passed
@AlexWaygood AlexWaygood deleted the claude/fix-callable-type-validation-IAr5S branch February 27, 2026 21:48
carljm added a commit that referenced this pull request Mar 2, 2026
* main:
  [ty] Move binary expression logic out of `builder.rs` (#23649)
  [ty] Limit recursion depth when displaying self-referential function types (#23647)
  [ty] Move comparison logic out of `builder.rs` (#23646)
  Avoid inserting redundant `None` elements in UP045 (#23459)
  [ty] Add more ParamSpec validation for `P.args` and `P.kwargs` (#23640)
  [ty] Sync vendored typeshed stubs (#23642)
  [ty] Fix inference of `t.__mro__` if `t` is an instance of `type[Any]` (#23632)
  [ty] Detect inconsistent generic base class specializations (#23615)
  [ty] Validate type variable defaults don't reference later type parameters or type parameters out of scope (#23623)
  [ty] Ban nested `Required`/`NotRequired`, and ban them both outside of `TypedDict` fields (#23627)
  [ty] Reject generic metaclasses parameterized by type variables (#23628)
  Update default Python version examples (#23605)
  fix binops with NewType of float and Unknown (#23620)
  [ty] Reject functions with PEP-695 type parameters that shadow type parameters from enclosing scopes (#23619)
  [ty] Reject ellipsis literals in odd places in type/annotation expressions (#23611)
  [ty] hash-cons `UseDefMap` fields (#23283)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ecosystem-analyzer ty Multi-file analysis & type inference

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants