-
Notifications
You must be signed in to change notification settings - Fork 261
impl type guard for Ellipsis #650 #2253
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
|
Diff from mypy_primer, showing the effect of this PR on open source code: urllib3 (https://github.com/urllib3/urllib3)
- ERROR src/urllib3/util/timeout.py:128:16-79: Returned type `_TYPE_DEFAULT | float | None` is not assignable to declared return type `float | None` [bad-return]
- ERROR src/urllib3/util/timeout.py:150:19-24: Argument `_TYPE_DEFAULT | float` is not assignable to parameter `x` with type `Buffer | SupportsFloat | SupportsIndex | str` in function `float.__new__` [bad-argument-type]
- ERROR src/urllib3/util/timeout.py:158:16-26: `<=` is not supported between `_TYPE_DEFAULT` and `Literal[0]` [unsupported-operation]
- ERROR src/urllib3/util/timeout.py:270:24-34: Returned type `_TYPE_DEFAULT | float` is not assignable to declared return type `float | None` [bad-return]
- ERROR src/urllib3/util/timeout.py:271:30-84: No matching overload found for function `min` called with arguments: (float, _TYPE_DEFAULT | float) [no-matching-overload]
+ ERROR src/urllib3/util/timeout.py:271:23-85: No matching overload found for function `max` called with arguments: (Literal[0], float) [no-matching-overload]
- ERROR src/urllib3/util/timeout.py:271:31-71: `-` is not supported between `_TYPE_DEFAULT` and `float` [unsupported-operation]
- ERROR src/urllib3/util/timeout.py:273:27-67: `-` is not supported between `_TYPE_DEFAULT` and `float` [unsupported-operation]
- ::error file=src/urllib3/util/timeout.py,line=128,col=16,endLine=128,endColumn=79,title=Pyrefly bad-return::Returned type `_TYPE_DEFAULT | float | None` is not assignable to declared return type `float | None`
- ::error file=src/urllib3/util/timeout.py,line=150,col=19,endLine=150,endColumn=24,title=Pyrefly bad-argument-type::Argument `_TYPE_DEFAULT | float` is not assignable to parameter `x` with type `Buffer | SupportsFloat | SupportsIndex | str` in function `float.__new__`
- ::error file=src/urllib3/util/timeout.py,line=158,col=16,endLine=158,endColumn=26,title=Pyrefly unsupported-operation::`<=` is not supported between `_TYPE_DEFAULT` and `Literal[0]`%0A Argument `_TYPE_DEFAULT` is not assignable to parameter `value` with type `int` in function `int.__ge__`
- ::error file=src/urllib3/util/timeout.py,line=270,col=24,endLine=270,endColumn=34,title=Pyrefly bad-return::Returned type `_TYPE_DEFAULT | float` is not assignable to declared return type `float | None`
- ::error file=src/urllib3/util/timeout.py,line=271,col=30,endLine=271,endColumn=84,title=Pyrefly no-matching-overload::No matching overload found for function `min` called with arguments: (float, _TYPE_DEFAULT | float)%0A Possible overloads:%0A (arg1: SupportsRichComparisonT, arg2: SupportsRichComparisonT, /, *_args: SupportsRichComparisonT, *, key: None = None) -> SupportsRichComparisonT [closest match]%0A (arg1: _T, arg2: _T, /, *_args: _T, *, key: (_T) -> SupportsRichComparison) -> _T%0A (iterable: Iterable[SupportsRichComparisonT], /, *, key: None = None) -> SupportsRichComparisonT%0A (iterable: Iterable[_T], /, *, key: (_T) -> SupportsRichComparison) -> _T%0A (iterable: Iterable[SupportsRichComparisonT], /, *, key: None = None, default: _T) -> SupportsRichComparisonT | _T%0A (iterable: Iterable[_T1], /, *, key: (_T1) -> SupportsRichComparison, default: _T2) -> _T1 | _T2
+ ::error file=src/urllib3/util/timeout.py,line=271,col=23,endLine=271,endColumn=85,title=Pyrefly no-matching-overload::No matching overload found for function `max` called with arguments: (Literal[0], float)%0A Possible overloads:%0A (arg1: SupportsRichComparisonT, arg2: SupportsRichComparisonT, /, *_args: SupportsRichComparisonT, *, key: None = None) -> SupportsRichComparisonT [closest match]%0A (arg1: _T, arg2: _T, /, *_args: _T, *, key: (_T) -> SupportsRichComparison) -> _T%0A (iterable: Iterable[SupportsRichComparisonT], /, *, key: None = None) -> SupportsRichComparisonT%0A (iterable: Iterable[_T], /, *, key: (_T) -> SupportsRichComparison) -> _T%0A (iterable: Iterable[SupportsRichComparisonT], /, *, key: None = None, default: _T) -> SupportsRichComparisonT | _T%0A (iterable: Iterable[_T1], /, *, key: (_T1) -> SupportsRichComparison, default: _T2) -> _T1 | _T2
- ::error file=src/urllib3/util/timeout.py,line=271,col=31,endLine=271,endColumn=71,title=Pyrefly unsupported-operation::`-` is not supported between `_TYPE_DEFAULT` and `float`%0A Argument `_TYPE_DEFAULT` is not assignable to parameter `value` with type `float` in function `float.__rsub__`
- ::error file=src/urllib3/util/timeout.py,line=273,col=27,endLine=273,endColumn=67,title=Pyrefly unsupported-operation::`-` is not supported between `_TYPE_DEFAULT` and `float`%0A Argument `_TYPE_DEFAULT` is not assignable to parameter `value` with type `float` in function `float.__rsub__`
xarray (https://github.com/pydata/xarray)
- ERROR xarray/core/dataset.py:8274:23-28: No matching overload found for function `set.__init__` called with arguments: (Collection[Hashable] | EllipsisType) [no-matching-overload]
- ERROR xarray/core/groupby.py:1107:32-35: Argument `Collection[Hashable] | EllipsisType` is not assignable to parameter `iterable` with type `Iterable[Hashable]` in function `tuple.__new__` [bad-argument-type]
- ERROR xarray/core/utils.py:1025:24-29: No matching overload found for function `set.__init__` called with arguments: (Collection[Hashable] | EllipsisType | tuple[str]) [no-matching-overload]
- ERROR xarray/core/utils.py:1026:18-21: Argument `Collection[Hashable] | EllipsisType | tuple[str]` is not assignable to parameter `iterable` with type `Iterable[Hashable]` in function `tuple.__new__` [bad-argument-type]
- ERROR xarray/core/utils.py:1064:14-19: No matching overload found for function `set.__init__` called with arguments: (Collection[Hashable] | EllipsisType | set[Hashable]) [no-matching-overload]
- ERROR xarray/core/utils.py:1122:76-86: `in` is not supported between `Ellipsis` and `EllipsisType` [not-iterable]
- ERROR xarray/core/utils.py:1123:53-58: No matching overload found for function `set.__init__` called with arguments: (Collection[Hashable] | EllipsisType) [no-matching-overload]
- ERROR xarray/core/utils.py:1129:22-25: Argument `Collection[Hashable] | EllipsisType` is not assignable to parameter `iterable` with type `Iterable[Hashable]` in function `tuple.__new__` [bad-argument-type]
- ERROR xarray/namedarray/core.py:916:28-35: `object` is not assignable to variable `axis` with type `Sequence[int] | int | None` [bad-assignment]
+ ERROR xarray/namedarray/core.py:916:28-35: `int | object` is not assignable to variable `axis` with type `Sequence[int] | int | None` [bad-assignment]
- ::error file=xarray/core/dataset.py,line=8274,col=23,endLine=8274,endColumn=28,title=Pyrefly no-matching-overload::No matching overload found for function `set.__init__` called with arguments: (Collection[Hashable] | EllipsisType)%0A Possible overloads:%0A () -> None%0A (iterable: Iterable[Hashable], /) -> None [closest match]
- ::error file=xarray/core/groupby.py,line=1107,col=32,endLine=1107,endColumn=35,title=Pyrefly bad-argument-type::Argument `Collection[Hashable] | EllipsisType` is not assignable to parameter `iterable` with type `Iterable[Hashable]` in function `tuple.__new__`%0A Protocol `Iterable` requires attribute `__iter__`
- ::error file=xarray/core/utils.py,line=1025,col=24,endLine=1025,endColumn=29,title=Pyrefly no-matching-overload::No matching overload found for function `set.__init__` called with arguments: (Collection[Hashable] | EllipsisType | tuple[str])%0A Possible overloads:%0A () -> None%0A (iterable: Iterable[Hashable], /) -> None [closest match]
- ::error file=xarray/core/utils.py,line=1026,col=18,endLine=1026,endColumn=21,title=Pyrefly bad-argument-type::Argument `Collection[Hashable] | EllipsisType | tuple[str]` is not assignable to parameter `iterable` with type `Iterable[Hashable]` in function `tuple.__new__`%0A Protocol `Iterable` requires attribute `__iter__`
- ::error file=xarray/core/utils.py,line=1064,col=14,endLine=1064,endColumn=19,title=Pyrefly no-matching-overload::No matching overload found for function `set.__init__` called with arguments: (Collection[Hashable] | EllipsisType | set[Hashable])%0A Possible overloads:%0A () -> None%0A (iterable: Iterable[Hashable], /) -> None [closest match]
- ::error file=xarray/core/utils.py,line=1122,col=76,endLine=1122,endColumn=86,title=Pyrefly not-iterable::`in` is not supported between `Ellipsis` and `EllipsisType`
- ::error file=xarray/core/utils.py,line=1123,col=53,endLine=1123,endColumn=58,title=Pyrefly no-matching-overload::No matching overload found for function `set.__init__` called with arguments: (Collection[Hashable] | EllipsisType)%0A Possible overloads:%0A () -> None%0A (iterable: Iterable[EllipsisType | Hashable], /) -> None [closest match]
- ::error file=xarray/core/utils.py,line=1129,col=22,endLine=1129,endColumn=25,title=Pyrefly bad-argument-type::Argument `Collection[Hashable] | EllipsisType` is not assignable to parameter `iterable` with type `Iterable[Hashable]` in function `tuple.__new__`%0A Protocol `Iterable` requires attribute `__iter__`
- ::error file=xarray/namedarray/core.py,line=916,col=28,endLine=916,endColumn=35,title=Pyrefly bad-assignment::`object` is not assignable to variable `axis` with type `Sequence[int] | int | None`
+ ::error file=xarray/namedarray/core.py,line=916,col=28,endLine=916,endColumn=35,title=Pyrefly bad-assignment::`int | object` is not assignable to variable `axis` with type `Sequence[int] | int | None`
sphinx (https://github.com/sphinx-doc/sphinx)
- ERROR sphinx/theming.py:542:65-81: Object of class `EllipsisType` has no attribute `split` [missing-attribute]
- ERROR sphinx/theming.py:550:62-75: Object of class `EllipsisType` has no attribute `split` [missing-attribute]
+ ERROR sphinx/theming.py:542:53-87: No matching overload found for function `map.__new__` called with arguments: (type[map[_S]], Overload[
+ (self: LiteralString, chars: LiteralString | None = None, /) -> LiteralString
+ (self: str, chars: str | None = None, /) -> str
+ ], list[str] | Unknown) [no-matching-overload]
+ ERROR sphinx/theming.py:550:50-81: No matching overload found for function `map.__new__` called with arguments: (type[map[_S]], Overload[
+ (self: LiteralString, chars: LiteralString | None = None, /) -> LiteralString
+ (self: str, chars: str | None = None, /) -> str
+ ], list[str] | Unknown) [no-matching-overload]
- ::error file=sphinx/theming.py,line=542,col=65,endLine=542,endColumn=81,title=Pyrefly missing-attribute::Object of class `EllipsisType` has no attribute `split`
- ::error file=sphinx/theming.py,line=550,col=62,endLine=550,endColumn=75,title=Pyrefly missing-attribute::Object of class `EllipsisType` has no attribute `split`
+ ::error file=sphinx/theming.py,line=542,col=53,endLine=542,endColumn=87,title=Pyrefly no-matching-overload::No matching overload found for function `map.__new__` called with arguments: (type[map[_S]], Overload[%0A (self: LiteralString, chars: LiteralString | None = None, /) -> LiteralString%0A (self: str, chars: str | None = None, /) -> str%0A], list[str] | Unknown)%0A Possible overloads:%0A (cls: type[map[_S]], func: (_T1) -> _S, iterable: Iterable[_T1], /) -> map[_S] [closest match]%0A (cls: type[map[_S]], func: (_T1, _T2) -> _S, iterable: Iterable[_T1], iter2: Iterable[_T2], /) -> map[_S]%0A (cls: type[map[_S]], func: (_T1, _T2, _T3) -> _S, iterable: Iterable[_T1], iter2: Iterable[_T2], iter3: Iterable[_T3], /) -> map[_S]%0A (cls: type[map[_S]], func: (_T1, _T2, _T3, _T4) -> _S, iterable: Iterable[_T1], iter2: Iterable[_T2], iter3: Iterable[_T3], iter4: Iterable[_T4], /) -> map[_S]%0A (cls: type[map[_S]], func: (_T1, _T2, _T3, _T4, _T5) -> _S, iterable: Iterable[_T1], iter2: Iterable[_T2], iter3: Iterable[_T3], iter4: Iterable[_T4], iter5: Iterable[_T5], /) -> map[_S]%0A (cls: type[map[_S]], func: (...) -> _S, iterable: Iterable[Any], iter2: Iterable[Any], iter3: Iterable[Any], iter4: Iterable[Any], iter5: Iterable[Any], iter6: Iterable[Any], /, *iterables: Iterable[Any]) -> map[_S]
+ ::error file=sphinx/theming.py,line=550,col=50,endLine=550,endColumn=81,title=Pyrefly no-matching-overload::No matching overload found for function `map.__new__` called with arguments: (type[map[_S]], Overload[%0A (self: LiteralString, chars: LiteralString | None = None, /) -> LiteralString%0A (self: str, chars: str | None = None, /) -> str%0A], list[str] | Unknown)%0A Possible overloads:%0A (cls: type[map[_S]], func: (_T1) -> _S, iterable: Iterable[_T1], /) -> map[_S] [closest match]%0A (cls: type[map[_S]], func: (_T1, _T2) -> _S, iterable: Iterable[_T1], iter2: Iterable[_T2], /) -> map[_S]%0A (cls: type[map[_S]], func: (_T1, _T2, _T3) -> _S, iterable: Iterable[_T1], iter2: Iterable[_T2], iter3: Iterable[_T3], /) -> map[_S]%0A (cls: type[map[_S]], func: (_T1, _T2, _T3, _T4) -> _S, iterable: Iterable[_T1], iter2: Iterable[_T2], iter3: Iterable[_T3], iter4: Iterable[_T4], /) -> map[_S]%0A (cls: type[map[_S]], func: (_T1, _T2, _T3, _T4, _T5) -> _S, iterable: Iterable[_T1], iter2: Iterable[_T2], iter3: Iterable[_T3], iter4: Iterable[_T4], iter5: Iterable[_T5], /) -> map[_S]%0A (cls: type[map[_S]], func: (...) -> _S, iterable: Iterable[Any], iter2: Iterable[Any], iter3: Iterable[Any], iter4: Iterable[Any], iter5: Iterable[Any], iter6: Iterable[Any], /, *iterables: Iterable[Any]) -> map[_S]
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
This PR implements type guard narrowing for ellipsis literals (...) to support identity and equality checks as specified in issue #650. The implementation treats ... as a singleton and handles both the Ellipsis type and EllipsisType class for comprehensive narrowing support.
Changes:
- Added ellipsis narrowing logic for
is,is not,==, and!=operators - Extended narrowing to handle
EllipsisTypeclass in negative narrowing scenarios - Added test cases for ellipsis identity and equality checks
Reviewed changes
Copilot reviewed 5 out of 6 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| pyrefly/lib/alt/narrow.rs | Core narrowing logic for ellipsis guards, including is_ellipsis_class_type helper and updates to all four comparison operators |
| pyrefly/lib/test/narrow.rs | Added test cases for is ... and == ... / != ... narrowing |
| crates/pyrefly_types/src/literal.rs | Added LitSentinel struct (from stacked PR #2252) |
| crates/pyrefly_types/src/display.rs | Display formatting for Lit::Sentinel |
| crates/pyrefly_types/src/type_output.rs | Output handling for Lit::Sentinel |
| Cargo.lock | Checksum update for backtrace crate (automated) |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| testcase!( | ||
| test_ellipsis_is, | ||
| r#" | ||
| from types import EllipsisType | ||
| def f(x: object): | ||
| if x is ...: | ||
| y: EllipsisType = x | ||
| "#, | ||
| ); | ||
|
|
||
| testcase!( | ||
| test_ellipsis_eq, | ||
| r#" | ||
| from types import EllipsisType | ||
| def f(x: int | EllipsisType): | ||
| if x == ...: | ||
| y_ellipsis: EllipsisType = x | ||
| if x != ...: | ||
| y_int: int = x | ||
| "#, | ||
| ); |
Copilot
AI
Jan 29, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Test coverage is incomplete. The implementation supports is not ... narrowing (lines 560-595, 970-1008), but there's no test case validating this behavior. Consider adding a test case like:
def f(x: int | EllipsisType):
if x is not ...:
y_int: int = xto ensure the negative narrowing works correctly.
Summary
Fixes #650
this is stack on #2252
Implemented ellipsis guards (is, is not, ==, !=) by treating ... as a singleton and also handling EllipsisType as its class counterpart for negative narrowing.
Test Plan
Added tests that validate narrowing via assignment to types.EllipsisType/int.