impl type guard for Ellipsis #650#2253
impl type guard for Ellipsis #650#2253asukaminato0721 wants to merge 2 commits intofacebook:mainfrom
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.
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 | ||
| "#, | ||
| ); |
There was a problem hiding this comment.
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.