[red-knot] MDTest: Use custom class names instead of builtins (#16269)

## Summary

Follow up on the discussion
[here](https://github.com/astral-sh/ruff/pull/16121#discussion_r1962973298).
Replace builtin classes with custom placeholder names, which should
hopefully make the tests a bit easier to understand.

I carefully renamed things one after the other, to make sure that there
is no functional change in the tests.
This commit is contained in:
David Peter 2025-02-20 13:25:55 +01:00 committed by GitHub
parent fc6b03c8da
commit 8198668fc3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 359 additions and 282 deletions

View File

@ -16,31 +16,38 @@ most common case involves implementing these methods for the same type:
```py ```py
from __future__ import annotations from __future__ import annotations
class EqReturnType: ...
class NeReturnType: ...
class LtReturnType: ...
class LeReturnType: ...
class GtReturnType: ...
class GeReturnType: ...
class A: class A:
def __eq__(self, other: A) -> int: def __eq__(self, other: A) -> EqReturnType:
return 42 return EqReturnType()
def __ne__(self, other: A) -> bytearray: def __ne__(self, other: A) -> NeReturnType:
return bytearray() return NeReturnType()
def __lt__(self, other: A) -> str: def __lt__(self, other: A) -> LtReturnType:
return "42" return LtReturnType()
def __le__(self, other: A) -> bytes: def __le__(self, other: A) -> LeReturnType:
return b"42" return LeReturnType()
def __gt__(self, other: A) -> list: def __gt__(self, other: A) -> GtReturnType:
return [42] return GtReturnType()
def __ge__(self, other: A) -> set: def __ge__(self, other: A) -> GeReturnType:
return {42} return GeReturnType()
reveal_type(A() == A()) # revealed: int reveal_type(A() == A()) # revealed: EqReturnType
reveal_type(A() != A()) # revealed: bytearray reveal_type(A() != A()) # revealed: NeReturnType
reveal_type(A() < A()) # revealed: str reveal_type(A() < A()) # revealed: LtReturnType
reveal_type(A() <= A()) # revealed: bytes reveal_type(A() <= A()) # revealed: LeReturnType
reveal_type(A() > A()) # revealed: list reveal_type(A() > A()) # revealed: GtReturnType
reveal_type(A() >= A()) # revealed: set reveal_type(A() >= A()) # revealed: GeReturnType
``` ```
## Rich Comparison Dunder Implementations for Other Class ## Rich Comparison Dunder Implementations for Other Class
@ -51,33 +58,40 @@ type:
```py ```py
from __future__ import annotations from __future__ import annotations
class EqReturnType: ...
class NeReturnType: ...
class LtReturnType: ...
class LeReturnType: ...
class GtReturnType: ...
class GeReturnType: ...
class A: class A:
def __eq__(self, other: B) -> int: def __eq__(self, other: B) -> EqReturnType:
return 42 return EqReturnType()
def __ne__(self, other: B) -> bytearray: def __ne__(self, other: B) -> NeReturnType:
return bytearray() return NeReturnType()
def __lt__(self, other: B) -> str: def __lt__(self, other: B) -> LtReturnType:
return "42" return LtReturnType()
def __le__(self, other: B) -> bytes: def __le__(self, other: B) -> LeReturnType:
return b"42" return LeReturnType()
def __gt__(self, other: B) -> list: def __gt__(self, other: B) -> GtReturnType:
return [42] return GtReturnType()
def __ge__(self, other: B) -> set: def __ge__(self, other: B) -> GeReturnType:
return {42} return GeReturnType()
class B: ... class B: ...
reveal_type(A() == B()) # revealed: int reveal_type(A() == B()) # revealed: EqReturnType
reveal_type(A() != B()) # revealed: bytearray reveal_type(A() != B()) # revealed: NeReturnType
reveal_type(A() < B()) # revealed: str reveal_type(A() < B()) # revealed: LtReturnType
reveal_type(A() <= B()) # revealed: bytes reveal_type(A() <= B()) # revealed: LeReturnType
reveal_type(A() > B()) # revealed: list reveal_type(A() > B()) # revealed: GtReturnType
reveal_type(A() >= B()) # revealed: set reveal_type(A() >= B()) # revealed: GeReturnType
``` ```
## Reflected Comparisons ## Reflected Comparisons
@ -89,55 +103,64 @@ these methods will be ignored here because they require a mismatched operand typ
```py ```py
from __future__ import annotations from __future__ import annotations
class EqReturnType: ...
class NeReturnType: ...
class LtReturnType: ...
class LeReturnType: ...
class GtReturnType: ...
class GeReturnType: ...
class A: class A:
def __eq__(self, other: B) -> int: def __eq__(self, other: B) -> EqReturnType:
return 42 return EqReturnType()
def __ne__(self, other: B) -> bytearray: def __ne__(self, other: B) -> NeReturnType:
return bytearray() return NeReturnType()
def __lt__(self, other: B) -> str: def __lt__(self, other: B) -> LtReturnType:
return "42" return LtReturnType()
def __le__(self, other: B) -> bytes: def __le__(self, other: B) -> LeReturnType:
return b"42" return LeReturnType()
def __gt__(self, other: B) -> list: def __gt__(self, other: B) -> GtReturnType:
return [42] return GtReturnType()
def __ge__(self, other: B) -> set: def __ge__(self, other: B) -> GeReturnType:
return {42} return GeReturnType()
class Unrelated: ...
class B: class B:
# To override builtins.object.__eq__ and builtins.object.__ne__ # To override builtins.object.__eq__ and builtins.object.__ne__
# TODO these should emit an invalid override diagnostic # TODO these should emit an invalid override diagnostic
def __eq__(self, other: str) -> B: def __eq__(self, other: Unrelated) -> B:
return B() return B()
def __ne__(self, other: str) -> B: def __ne__(self, other: Unrelated) -> B:
return B() return B()
# Because `object.__eq__` and `object.__ne__` accept `object` in typeshed, # Because `object.__eq__` and `object.__ne__` accept `object` in typeshed,
# this can only happen with an invalid override of these methods, # this can only happen with an invalid override of these methods,
# but we still support it. # but we still support it.
reveal_type(B() == A()) # revealed: int reveal_type(B() == A()) # revealed: EqReturnType
reveal_type(B() != A()) # revealed: bytearray reveal_type(B() != A()) # revealed: NeReturnType
reveal_type(B() < A()) # revealed: list reveal_type(B() < A()) # revealed: GtReturnType
reveal_type(B() <= A()) # revealed: set reveal_type(B() <= A()) # revealed: GeReturnType
reveal_type(B() > A()) # revealed: str reveal_type(B() > A()) # revealed: LtReturnType
reveal_type(B() >= A()) # revealed: bytes reveal_type(B() >= A()) # revealed: LeReturnType
class C: class C:
def __gt__(self, other: C) -> int: def __gt__(self, other: C) -> EqReturnType:
return 42 return 42
def __ge__(self, other: C) -> bytearray: def __ge__(self, other: C) -> NeReturnType:
return bytearray() return NeReturnType()
reveal_type(C() < C()) # revealed: int reveal_type(C() < C()) # revealed: EqReturnType
reveal_type(C() <= C()) # revealed: bytearray reveal_type(C() <= C()) # revealed: NeReturnType
``` ```
## Reflected Comparisons with Subclasses ## Reflected Comparisons with Subclasses
@ -149,6 +172,13 @@ than `A`.
```py ```py
from __future__ import annotations from __future__ import annotations
class EqReturnType: ...
class NeReturnType: ...
class LtReturnType: ...
class LeReturnType: ...
class GtReturnType: ...
class GeReturnType: ...
class A: class A:
def __eq__(self, other: A) -> A: def __eq__(self, other: A) -> A:
return A() return A()
@ -169,32 +199,32 @@ class A:
return A() return A()
class B(A): class B(A):
def __eq__(self, other: A) -> int: def __eq__(self, other: A) -> EqReturnType:
return 42 return EqReturnType()
def __ne__(self, other: A) -> bytearray: def __ne__(self, other: A) -> NeReturnType:
return bytearray() return NeReturnType()
def __lt__(self, other: A) -> str: def __lt__(self, other: A) -> LtReturnType:
return "42" return LtReturnType()
def __le__(self, other: A) -> bytes: def __le__(self, other: A) -> LeReturnType:
return b"42" return LeReturnType()
def __gt__(self, other: A) -> list: def __gt__(self, other: A) -> GtReturnType:
return [42] return GtReturnType()
def __ge__(self, other: A) -> set: def __ge__(self, other: A) -> GeReturnType:
return {42} return GeReturnType()
reveal_type(A() == B()) # revealed: int reveal_type(A() == B()) # revealed: EqReturnType
reveal_type(A() != B()) # revealed: bytearray reveal_type(A() != B()) # revealed: NeReturnType
reveal_type(A() < B()) # revealed: list reveal_type(A() < B()) # revealed: GtReturnType
reveal_type(A() <= B()) # revealed: set reveal_type(A() <= B()) # revealed: GeReturnType
reveal_type(A() > B()) # revealed: str reveal_type(A() > B()) # revealed: LtReturnType
reveal_type(A() >= B()) # revealed: bytes reveal_type(A() >= B()) # revealed: LeReturnType
``` ```
## Reflected Comparisons with Subclass But Falls Back to LHS ## Reflected Comparisons with Subclass But Falls Back to LHS

View File

@ -147,33 +147,40 @@ of the dunder methods.)
```py ```py
from __future__ import annotations from __future__ import annotations
class EqReturnType: ...
class NeReturnType: ...
class LtReturnType: ...
class LeReturnType: ...
class GtReturnType: ...
class GeReturnType: ...
class A: class A:
def __eq__(self, o: object) -> str: def __eq__(self, o: object) -> EqReturnType:
return "hello" return EqReturnType()
def __ne__(self, o: object) -> bytes: def __ne__(self, o: object) -> NeReturnType:
return b"world" return NeReturnType()
def __lt__(self, o: A) -> bytearray: def __lt__(self, o: A) -> LtReturnType:
return bytearray() return LtReturnType()
def __le__(self, o: A) -> memoryview: def __le__(self, o: A) -> LeReturnType:
return memoryview(b"") return LeReturnType()
def __gt__(self, o: A) -> tuple: def __gt__(self, o: A) -> GtReturnType:
return (1, 2, 3) return GtReturnType()
def __ge__(self, o: A) -> list: def __ge__(self, o: A) -> GeReturnType:
return [1, 2, 3] return GeReturnType()
a = (A(), A()) a = (A(), A())
reveal_type(a == a) # revealed: bool reveal_type(a == a) # revealed: bool
reveal_type(a != a) # revealed: bool reveal_type(a != a) # revealed: bool
reveal_type(a < a) # revealed: bytearray | Literal[False] reveal_type(a < a) # revealed: LtReturnType | Literal[False]
reveal_type(a <= a) # revealed: memoryview | Literal[True] reveal_type(a <= a) # revealed: LeReturnType | Literal[True]
reveal_type(a > a) # revealed: tuple | Literal[False] reveal_type(a > a) # revealed: GtReturnType | Literal[False]
reveal_type(a >= a) # revealed: list | Literal[True] reveal_type(a >= a) # revealed: GeReturnType | Literal[True]
# If lexicographic comparison is finished before comparing A() # If lexicographic comparison is finished before comparing A()
b = ("1_foo", A()) b = ("1_foo", A())
@ -186,11 +193,13 @@ reveal_type(b <= c) # revealed: Literal[True]
reveal_type(b > c) # revealed: Literal[False] reveal_type(b > c) # revealed: Literal[False]
reveal_type(b >= c) # revealed: Literal[False] reveal_type(b >= c) # revealed: Literal[False]
class LtReturnTypeOnB: ...
class B: class B:
def __lt__(self, o: B) -> set: def __lt__(self, o: B) -> LtReturnTypeOnB:
return set() return set()
reveal_type((A(), B()) < (A(), B())) # revealed: bytearray | set | Literal[False] reveal_type((A(), B()) < (A(), B())) # revealed: LtReturnType | LtReturnTypeOnB | Literal[False]
``` ```
#### Special Handling of Eq and NotEq in Lexicographic Comparisons #### Special Handling of Eq and NotEq in Lexicographic Comparisons

View File

@ -241,30 +241,34 @@ suites:
`except` suite ran to completion `except` suite ran to completion
```py ```py
def could_raise_returns_str() -> str: class A: ...
return "foo" class B: ...
class C: ...
def could_raise_returns_bytes() -> bytes: def could_raise_returns_A() -> A:
return b"foo" return A()
def could_raise_returns_bool() -> bool: def could_raise_returns_B() -> B:
return True return B()
def could_raise_returns_C() -> C:
return C()
x = 1 x = 1
try: try:
reveal_type(x) # revealed: Literal[1] reveal_type(x) # revealed: Literal[1]
x = could_raise_returns_str() x = could_raise_returns_A()
reveal_type(x) # revealed: str reveal_type(x) # revealed: A
except TypeError: except TypeError:
reveal_type(x) # revealed: Literal[1] | str reveal_type(x) # revealed: Literal[1] | A
x = could_raise_returns_bytes() x = could_raise_returns_B()
reveal_type(x) # revealed: bytes reveal_type(x) # revealed: B
x = could_raise_returns_bool() x = could_raise_returns_C()
reveal_type(x) # revealed: bool reveal_type(x) # revealed: C
finally: finally:
# TODO: should be `Literal[1] | str | bytes | bool` # TODO: should be `Literal[1] | A | B | C`
reveal_type(x) # revealed: str | bool reveal_type(x) # revealed: A | C
x = 2 x = 2
reveal_type(x) # revealed: Literal[2] reveal_type(x) # revealed: Literal[2]
@ -282,53 +286,56 @@ x = 1
try: try:
reveal_type(x) # revealed: Literal[1] reveal_type(x) # revealed: Literal[1]
x = could_raise_returns_str() x = could_raise_returns_A()
reveal_type(x) # revealed: str reveal_type(x) # revealed: A
except TypeError: except TypeError:
reveal_type(x) # revealed: Literal[1] | str reveal_type(x) # revealed: Literal[1] | A
x = could_raise_returns_bytes() x = could_raise_returns_B()
reveal_type(x) # revealed: bytes reveal_type(x) # revealed: B
x = could_raise_returns_bool() x = could_raise_returns_C()
reveal_type(x) # revealed: bool reveal_type(x) # revealed: C
finally: finally:
# TODO: should be `Literal[1] | str | bytes | bool` # TODO: should be `Literal[1] | A | B | C`
reveal_type(x) # revealed: str | bool reveal_type(x) # revealed: A | C
reveal_type(x) # revealed: str | bool reveal_type(x) # revealed: A | C
``` ```
An example with multiple `except` branches and a `finally` branch: An example with multiple `except` branches and a `finally` branch:
```py ```py
def could_raise_returns_memoryview() -> memoryview: class D: ...
return memoryview(b"") class E: ...
def could_raise_returns_bytearray() -> bytearray: def could_raise_returns_D() -> D:
return bytearray() return D()
def could_raise_returns_E() -> E:
return E()
x = 1 x = 1
try: try:
reveal_type(x) # revealed: Literal[1] reveal_type(x) # revealed: Literal[1]
x = could_raise_returns_str() x = could_raise_returns_A()
reveal_type(x) # revealed: str reveal_type(x) # revealed: A
except TypeError: except TypeError:
reveal_type(x) # revealed: Literal[1] | str reveal_type(x) # revealed: Literal[1] | A
x = could_raise_returns_bytes() x = could_raise_returns_B()
reveal_type(x) # revealed: bytes reveal_type(x) # revealed: B
x = could_raise_returns_bool() x = could_raise_returns_C()
reveal_type(x) # revealed: bool reveal_type(x) # revealed: C
except ValueError: except ValueError:
reveal_type(x) # revealed: Literal[1] | str reveal_type(x) # revealed: Literal[1] | A
x = could_raise_returns_memoryview() x = could_raise_returns_D()
reveal_type(x) # revealed: memoryview reveal_type(x) # revealed: D
x = could_raise_returns_bytearray() x = could_raise_returns_E()
reveal_type(x) # revealed: bytearray reveal_type(x) # revealed: E
finally: finally:
# TODO: should be `Literal[1] | str | bytes | bool | memoryview | bytearray` # TODO: should be `Literal[1] | A | B | C | D | E`
reveal_type(x) # revealed: str | bool | bytearray reveal_type(x) # revealed: A | C | E
reveal_type(x) # revealed: str | bool | bytearray reveal_type(x) # revealed: A | C | E
``` ```
## Combining `except`, `else` and `finally` branches ## Combining `except`, `else` and `finally` branches
@ -338,84 +345,93 @@ control flow could have jumped to the `finally` suite from partway through the `
an exception raised *there*. an exception raised *there*.
```py ```py
def could_raise_returns_str() -> str: class A: ...
return "foo" class B: ...
class C: ...
class D: ...
class E: ...
def could_raise_returns_bytes() -> bytes: def could_raise_returns_A() -> A:
return b"foo" return A()
def could_raise_returns_bool() -> bool: def could_raise_returns_B() -> B:
return True return B()
def could_raise_returns_memoryview() -> memoryview: def could_raise_returns_C() -> C:
return memoryview(b"") return C()
def could_raise_returns_bytearray() -> bytearray: def could_raise_returns_D() -> D:
return bytearray() return D()
def could_raise_returns_E() -> E:
return E()
x = 1 x = 1
try: try:
reveal_type(x) # revealed: Literal[1] reveal_type(x) # revealed: Literal[1]
x = could_raise_returns_str() x = could_raise_returns_A()
reveal_type(x) # revealed: str reveal_type(x) # revealed: A
except TypeError: except TypeError:
reveal_type(x) # revealed: Literal[1] | str reveal_type(x) # revealed: Literal[1] | A
x = could_raise_returns_bytes() x = could_raise_returns_B()
reveal_type(x) # revealed: bytes reveal_type(x) # revealed: B
x = could_raise_returns_bool() x = could_raise_returns_C()
reveal_type(x) # revealed: bool reveal_type(x) # revealed: C
else: else:
reveal_type(x) # revealed: str reveal_type(x) # revealed: A
x = could_raise_returns_memoryview() x = could_raise_returns_D()
reveal_type(x) # revealed: memoryview reveal_type(x) # revealed: D
x = could_raise_returns_bytearray() x = could_raise_returns_E()
reveal_type(x) # revealed: bytearray reveal_type(x) # revealed: E
finally: finally:
# TODO: should be `Literal[1] | str | bytes | bool | memoryview | bytearray` # TODO: should be `Literal[1] | A | B | C | D | E`
reveal_type(x) # revealed: bool | bytearray reveal_type(x) # revealed: C | E
reveal_type(x) # revealed: bool | bytearray reveal_type(x) # revealed: C | E
``` ```
The same again, this time with multiple `except` branches: The same again, this time with multiple `except` branches:
```py ```py
def could_raise_returns_range() -> range: class F: ...
return range(42) class G: ...
def could_raise_returns_slice() -> slice: def could_raise_returns_F() -> F:
return slice(None) return F()
def could_raise_returns_G() -> G:
return G()
x = 1 x = 1
try: try:
reveal_type(x) # revealed: Literal[1] reveal_type(x) # revealed: Literal[1]
x = could_raise_returns_str() x = could_raise_returns_A()
reveal_type(x) # revealed: str reveal_type(x) # revealed: A
except TypeError: except TypeError:
reveal_type(x) # revealed: Literal[1] | str reveal_type(x) # revealed: Literal[1] | A
x = could_raise_returns_bytes() x = could_raise_returns_B()
reveal_type(x) # revealed: bytes reveal_type(x) # revealed: B
x = could_raise_returns_bool() x = could_raise_returns_C()
reveal_type(x) # revealed: bool reveal_type(x) # revealed: C
except ValueError: except ValueError:
reveal_type(x) # revealed: Literal[1] | str reveal_type(x) # revealed: Literal[1] | A
x = could_raise_returns_memoryview() x = could_raise_returns_D()
reveal_type(x) # revealed: memoryview reveal_type(x) # revealed: D
x = could_raise_returns_bytearray() x = could_raise_returns_E()
reveal_type(x) # revealed: bytearray reveal_type(x) # revealed: E
else: else:
reveal_type(x) # revealed: str reveal_type(x) # revealed: A
x = could_raise_returns_range() x = could_raise_returns_F()
reveal_type(x) # revealed: range reveal_type(x) # revealed: F
x = could_raise_returns_slice() x = could_raise_returns_G()
reveal_type(x) # revealed: slice reveal_type(x) # revealed: G
finally: finally:
# TODO: should be `Literal[1] | str | bytes | bool | memoryview | bytearray | range | slice` # TODO: should be `Literal[1] | A | B | C | D | E | F | G`
reveal_type(x) # revealed: bool | bytearray | slice reveal_type(x) # revealed: C | E | G
reveal_type(x) # revealed: bool | bytearray | slice reveal_type(x) # revealed: C | E | G
``` ```
## Nested `try`/`except` blocks ## Nested `try`/`except` blocks
@ -429,92 +445,101 @@ a suite containing statements that could possibly raise exceptions, which would
jumping out of that suite prior to the suite running to completion. jumping out of that suite prior to the suite running to completion.
```py ```py
def could_raise_returns_str() -> str: class A: ...
return "foo" class B: ...
class C: ...
class D: ...
class E: ...
class F: ...
class G: ...
class H: ...
class I: ...
class J: ...
class K: ...
def could_raise_returns_bytes() -> bytes: def could_raise_returns_A() -> A:
return b"foo" return A()
def could_raise_returns_bool() -> bool: def could_raise_returns_B() -> B:
return True return B()
def could_raise_returns_memoryview() -> memoryview: def could_raise_returns_C() -> C:
return memoryview(b"") return C()
def could_raise_returns_property() -> property: def could_raise_returns_D() -> D:
return property() return D()
def could_raise_returns_range() -> range: def could_raise_returns_E() -> E:
return range(42) return E()
def could_raise_returns_slice() -> slice: def could_raise_returns_F() -> F:
return slice(None) return F()
def could_raise_returns_super() -> super: def could_raise_returns_G() -> G:
return super() return G()
def could_raise_returns_bytearray() -> bytearray: def could_raise_returns_H() -> H:
return bytearray() return H()
class Foo: ... def could_raise_returns_I() -> I:
class Bar: ... return I()
def could_raise_returns_Foo() -> Foo: def could_raise_returns_J() -> J:
return Foo() return J()
def could_raise_returns_Bar() -> Bar: def could_raise_returns_K() -> K:
return Bar() return K()
x = 1 x = 1
try: try:
try: try:
reveal_type(x) # revealed: Literal[1] reveal_type(x) # revealed: Literal[1]
x = could_raise_returns_str() x = could_raise_returns_A()
reveal_type(x) # revealed: str reveal_type(x) # revealed: A
except TypeError: except TypeError:
reveal_type(x) # revealed: Literal[1] | str reveal_type(x) # revealed: Literal[1] | A
x = could_raise_returns_bytes() x = could_raise_returns_B()
reveal_type(x) # revealed: bytes reveal_type(x) # revealed: B
x = could_raise_returns_bool() x = could_raise_returns_C()
reveal_type(x) # revealed: bool reveal_type(x) # revealed: C
except ValueError: except ValueError:
reveal_type(x) # revealed: Literal[1] | str reveal_type(x) # revealed: Literal[1] | A
x = could_raise_returns_memoryview() x = could_raise_returns_D()
reveal_type(x) # revealed: memoryview reveal_type(x) # revealed: D
x = could_raise_returns_property() x = could_raise_returns_E()
reveal_type(x) # revealed: property reveal_type(x) # revealed: E
else: else:
reveal_type(x) # revealed: str reveal_type(x) # revealed: A
x = could_raise_returns_range() x = could_raise_returns_F()
reveal_type(x) # revealed: range reveal_type(x) # revealed: F
x = could_raise_returns_slice() x = could_raise_returns_G()
reveal_type(x) # revealed: slice reveal_type(x) # revealed: G
finally: finally:
# TODO: should be `Literal[1] | str | bytes | bool | memoryview | property | range | slice` # TODO: should be `Literal[1] | A | B | C | D | E | F | G`
reveal_type(x) # revealed: bool | property | slice reveal_type(x) # revealed: C | E | G
x = 2 x = 2
reveal_type(x) # revealed: Literal[2] reveal_type(x) # revealed: Literal[2]
reveal_type(x) # revealed: Literal[2] reveal_type(x) # revealed: Literal[2]
except: except:
reveal_type(x) # revealed: Literal[1, 2] | str | bytes | bool | memoryview | property | range | slice reveal_type(x) # revealed: Literal[1, 2] | A | B | C | D | E | F | G
x = could_raise_returns_super() x = could_raise_returns_H()
reveal_type(x) # revealed: super reveal_type(x) # revealed: H
x = could_raise_returns_bytearray() x = could_raise_returns_I()
reveal_type(x) # revealed: bytearray reveal_type(x) # revealed: I
else: else:
reveal_type(x) # revealed: Literal[2] reveal_type(x) # revealed: Literal[2]
x = could_raise_returns_Foo() x = could_raise_returns_J()
reveal_type(x) # revealed: Foo reveal_type(x) # revealed: J
x = could_raise_returns_Bar() x = could_raise_returns_K()
reveal_type(x) # revealed: Bar reveal_type(x) # revealed: K
finally: finally:
# TODO: should be `Literal[1, 2] | str | bytes | bool | memoryview | property | range | slice | super | bytearray | Foo | Bar` # TODO: should be `Literal[1, 2] | A | B | C | D | E | F | G | H | I | J | K`
reveal_type(x) # revealed: bytearray | Bar reveal_type(x) # revealed: I | K
# Either one `except` branch or the `else` # Either one `except` branch or the `else`
# must have been taken and completed to get here: # must have been taken and completed to get here:
reveal_type(x) # revealed: bytearray | Bar reveal_type(x) # revealed: I | K
``` ```
## Nested scopes inside `try` blocks ## Nested scopes inside `try` blocks
@ -523,50 +548,56 @@ Shadowing a variable in an inner scope has no effect on type inference of the va
in the outer scope: in the outer scope:
```py ```py
def could_raise_returns_str() -> str: class A: ...
return "foo" class B: ...
class C: ...
class D: ...
class E: ...
def could_raise_returns_bytes() -> bytes: def could_raise_returns_A() -> A:
return b"foo" return A()
def could_raise_returns_range() -> range: def could_raise_returns_B() -> B:
return range(42) return B()
def could_raise_returns_bytearray() -> bytearray: def could_raise_returns_C() -> C:
return bytearray() return C()
def could_raise_returns_memoryview() -> memoryview: def could_raise_returns_D() -> D:
return memoryview(b"") return D()
def could_raise_returns_E() -> E:
return E()
x = 1 x = 1
try: try:
def foo(param=could_raise_returns_str()): def foo(param=could_raise_returns_A()):
x = could_raise_returns_str() x = could_raise_returns_A()
try: try:
reveal_type(x) # revealed: str reveal_type(x) # revealed: A
x = could_raise_returns_bytes() x = could_raise_returns_B()
reveal_type(x) # revealed: bytes reveal_type(x) # revealed: B
except: except:
reveal_type(x) # revealed: str | bytes reveal_type(x) # revealed: A | B
x = could_raise_returns_bytearray() x = could_raise_returns_C()
reveal_type(x) # revealed: bytearray reveal_type(x) # revealed: C
x = could_raise_returns_memoryview() x = could_raise_returns_D()
reveal_type(x) # revealed: memoryview reveal_type(x) # revealed: D
finally: finally:
# TODO: should be `str | bytes | bytearray | memoryview` # TODO: should be `A | B | C | D`
reveal_type(x) # revealed: bytes | memoryview reveal_type(x) # revealed: B | D
reveal_type(x) # revealed: bytes | memoryview reveal_type(x) # revealed: B | D
x = foo x = foo
reveal_type(x) # revealed: Literal[foo] reveal_type(x) # revealed: Literal[foo]
except: except:
reveal_type(x) # revealed: Literal[1] | Literal[foo] reveal_type(x) # revealed: Literal[1] | Literal[foo]
class Bar: class Bar:
x = could_raise_returns_range() x = could_raise_returns_E()
reveal_type(x) # revealed: range reveal_type(x) # revealed: E
x = Bar x = Bar
reveal_type(x) # revealed: Literal[Bar] reveal_type(x) # revealed: Literal[Bar]

View File

@ -183,25 +183,32 @@ for x in Test():
## Union type as iterable and union type as iterator ## Union type as iterable and union type as iterator
```py ```py
class TestIter: class Result1A: ...
def __next__(self) -> int | Exception: class Result1B: ...
return 42 class Result2A: ...
class Result2B: ...
class Result3: ...
class Result4: ...
class TestIter1:
def __next__(self) -> Result1A | Result1B:
return Result1B()
class TestIter2: class TestIter2:
def __next__(self) -> str | tuple[int, int]: def __next__(self) -> Result2A | Result2B:
return "42" return Result2B()
class TestIter3: class TestIter3:
def __next__(self) -> bytes: def __next__(self) -> Result3:
return b"42" return Result3()
class TestIter4: class TestIter4:
def __next__(self) -> memoryview: def __next__(self) -> Result4:
return memoryview(b"42") return Result4()
class Test: class Test:
def __iter__(self) -> TestIter | TestIter2: def __iter__(self) -> TestIter1 | TestIter2:
return TestIter() return TestIter1()
class Test2: class Test2:
def __iter__(self) -> TestIter3 | TestIter4: def __iter__(self) -> TestIter3 | TestIter4:
@ -209,7 +216,7 @@ class Test2:
def _(flag: bool): def _(flag: bool):
for x in Test() if flag else Test2(): for x in Test() if flag else Test2():
reveal_type(x) # revealed: int | Exception | str | tuple[int, int] | bytes | memoryview reveal_type(x) # revealed: Result1A | Result1B | Result2A | Result2B | Result3 | Result4
``` ```
## Union type as iterable where one union element has no `__iter__` method ## Union type as iterable where one union element has no `__iter__` method