From 8198668fc34283eeff9be6dae337a7b38e3d498b Mon Sep 17 00:00:00 2001 From: David Peter Date: Thu, 20 Feb 2025 13:25:55 +0100 Subject: [PATCH] [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. --- .../comparison/instances/rich_comparison.md | 188 +++++---- .../resources/mdtest/comparison/tuples.md | 45 ++- .../mdtest/exception/control_flow.md | 377 ++++++++++-------- .../resources/mdtest/loops/for.md | 31 +- 4 files changed, 359 insertions(+), 282 deletions(-) diff --git a/crates/red_knot_python_semantic/resources/mdtest/comparison/instances/rich_comparison.md b/crates/red_knot_python_semantic/resources/mdtest/comparison/instances/rich_comparison.md index 70f4427af7..9f5475072a 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/comparison/instances/rich_comparison.md +++ b/crates/red_knot_python_semantic/resources/mdtest/comparison/instances/rich_comparison.md @@ -16,31 +16,38 @@ most common case involves implementing these methods for the same type: ```py from __future__ import annotations +class EqReturnType: ... +class NeReturnType: ... +class LtReturnType: ... +class LeReturnType: ... +class GtReturnType: ... +class GeReturnType: ... + class A: - def __eq__(self, other: A) -> int: - return 42 + def __eq__(self, other: A) -> EqReturnType: + return EqReturnType() - def __ne__(self, other: A) -> bytearray: - return bytearray() + def __ne__(self, other: A) -> NeReturnType: + return NeReturnType() - def __lt__(self, other: A) -> str: - return "42" + def __lt__(self, other: A) -> LtReturnType: + return LtReturnType() - def __le__(self, other: A) -> bytes: - return b"42" + def __le__(self, other: A) -> LeReturnType: + return LeReturnType() - def __gt__(self, other: A) -> list: - return [42] + def __gt__(self, other: A) -> GtReturnType: + return GtReturnType() - def __ge__(self, other: A) -> set: - return {42} + def __ge__(self, other: A) -> GeReturnType: + return GeReturnType() -reveal_type(A() == A()) # revealed: int -reveal_type(A() != A()) # revealed: bytearray -reveal_type(A() < A()) # revealed: str -reveal_type(A() <= A()) # revealed: bytes -reveal_type(A() > A()) # revealed: list -reveal_type(A() >= A()) # revealed: set +reveal_type(A() == A()) # revealed: EqReturnType +reveal_type(A() != A()) # revealed: NeReturnType +reveal_type(A() < A()) # revealed: LtReturnType +reveal_type(A() <= A()) # revealed: LeReturnType +reveal_type(A() > A()) # revealed: GtReturnType +reveal_type(A() >= A()) # revealed: GeReturnType ``` ## Rich Comparison Dunder Implementations for Other Class @@ -51,33 +58,40 @@ type: ```py from __future__ import annotations +class EqReturnType: ... +class NeReturnType: ... +class LtReturnType: ... +class LeReturnType: ... +class GtReturnType: ... +class GeReturnType: ... + class A: - def __eq__(self, other: B) -> int: - return 42 + def __eq__(self, other: B) -> EqReturnType: + return EqReturnType() - def __ne__(self, other: B) -> bytearray: - return bytearray() + def __ne__(self, other: B) -> NeReturnType: + return NeReturnType() - def __lt__(self, other: B) -> str: - return "42" + def __lt__(self, other: B) -> LtReturnType: + return LtReturnType() - def __le__(self, other: B) -> bytes: - return b"42" + def __le__(self, other: B) -> LeReturnType: + return LeReturnType() - def __gt__(self, other: B) -> list: - return [42] + def __gt__(self, other: B) -> GtReturnType: + return GtReturnType() - def __ge__(self, other: B) -> set: - return {42} + def __ge__(self, other: B) -> GeReturnType: + return GeReturnType() class B: ... -reveal_type(A() == B()) # revealed: int -reveal_type(A() != B()) # revealed: bytearray -reveal_type(A() < B()) # revealed: str -reveal_type(A() <= B()) # revealed: bytes -reveal_type(A() > B()) # revealed: list -reveal_type(A() >= B()) # revealed: set +reveal_type(A() == B()) # revealed: EqReturnType +reveal_type(A() != B()) # revealed: NeReturnType +reveal_type(A() < B()) # revealed: LtReturnType +reveal_type(A() <= B()) # revealed: LeReturnType +reveal_type(A() > B()) # revealed: GtReturnType +reveal_type(A() >= B()) # revealed: GeReturnType ``` ## Reflected Comparisons @@ -89,55 +103,64 @@ these methods will be ignored here because they require a mismatched operand typ ```py from __future__ import annotations +class EqReturnType: ... +class NeReturnType: ... +class LtReturnType: ... +class LeReturnType: ... +class GtReturnType: ... +class GeReturnType: ... + class A: - def __eq__(self, other: B) -> int: - return 42 + def __eq__(self, other: B) -> EqReturnType: + return EqReturnType() - def __ne__(self, other: B) -> bytearray: - return bytearray() + def __ne__(self, other: B) -> NeReturnType: + return NeReturnType() - def __lt__(self, other: B) -> str: - return "42" + def __lt__(self, other: B) -> LtReturnType: + return LtReturnType() - def __le__(self, other: B) -> bytes: - return b"42" + def __le__(self, other: B) -> LeReturnType: + return LeReturnType() - def __gt__(self, other: B) -> list: - return [42] + def __gt__(self, other: B) -> GtReturnType: + return GtReturnType() - def __ge__(self, other: B) -> set: - return {42} + def __ge__(self, other: B) -> GeReturnType: + return GeReturnType() + +class Unrelated: ... class B: # To override builtins.object.__eq__ and builtins.object.__ne__ # TODO these should emit an invalid override diagnostic - def __eq__(self, other: str) -> B: + def __eq__(self, other: Unrelated) -> B: return B() - def __ne__(self, other: str) -> B: + def __ne__(self, other: Unrelated) -> B: return B() # Because `object.__eq__` and `object.__ne__` accept `object` in typeshed, # this can only happen with an invalid override of these methods, # but we still support it. -reveal_type(B() == A()) # revealed: int -reveal_type(B() != A()) # revealed: bytearray +reveal_type(B() == A()) # revealed: EqReturnType +reveal_type(B() != A()) # revealed: NeReturnType -reveal_type(B() < A()) # revealed: list -reveal_type(B() <= A()) # revealed: set +reveal_type(B() < A()) # revealed: GtReturnType +reveal_type(B() <= A()) # revealed: GeReturnType -reveal_type(B() > A()) # revealed: str -reveal_type(B() >= A()) # revealed: bytes +reveal_type(B() > A()) # revealed: LtReturnType +reveal_type(B() >= A()) # revealed: LeReturnType class C: - def __gt__(self, other: C) -> int: + def __gt__(self, other: C) -> EqReturnType: return 42 - def __ge__(self, other: C) -> bytearray: - return bytearray() + def __ge__(self, other: C) -> NeReturnType: + return NeReturnType() -reveal_type(C() < C()) # revealed: int -reveal_type(C() <= C()) # revealed: bytearray +reveal_type(C() < C()) # revealed: EqReturnType +reveal_type(C() <= C()) # revealed: NeReturnType ``` ## Reflected Comparisons with Subclasses @@ -149,6 +172,13 @@ than `A`. ```py from __future__ import annotations +class EqReturnType: ... +class NeReturnType: ... +class LtReturnType: ... +class LeReturnType: ... +class GtReturnType: ... +class GeReturnType: ... + class A: def __eq__(self, other: A) -> A: return A() @@ -169,32 +199,32 @@ class A: return A() class B(A): - def __eq__(self, other: A) -> int: - return 42 + def __eq__(self, other: A) -> EqReturnType: + return EqReturnType() - def __ne__(self, other: A) -> bytearray: - return bytearray() + def __ne__(self, other: A) -> NeReturnType: + return NeReturnType() - def __lt__(self, other: A) -> str: - return "42" + def __lt__(self, other: A) -> LtReturnType: + return LtReturnType() - def __le__(self, other: A) -> bytes: - return b"42" + def __le__(self, other: A) -> LeReturnType: + return LeReturnType() - def __gt__(self, other: A) -> list: - return [42] + def __gt__(self, other: A) -> GtReturnType: + return GtReturnType() - def __ge__(self, other: A) -> set: - return {42} + def __ge__(self, other: A) -> GeReturnType: + return GeReturnType() -reveal_type(A() == B()) # revealed: int -reveal_type(A() != B()) # revealed: bytearray +reveal_type(A() == B()) # revealed: EqReturnType +reveal_type(A() != B()) # revealed: NeReturnType -reveal_type(A() < B()) # revealed: list -reveal_type(A() <= B()) # revealed: set +reveal_type(A() < B()) # revealed: GtReturnType +reveal_type(A() <= B()) # revealed: GeReturnType -reveal_type(A() > B()) # revealed: str -reveal_type(A() >= B()) # revealed: bytes +reveal_type(A() > B()) # revealed: LtReturnType +reveal_type(A() >= B()) # revealed: LeReturnType ``` ## Reflected Comparisons with Subclass But Falls Back to LHS diff --git a/crates/red_knot_python_semantic/resources/mdtest/comparison/tuples.md b/crates/red_knot_python_semantic/resources/mdtest/comparison/tuples.md index 273572104e..425b438e5d 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/comparison/tuples.md +++ b/crates/red_knot_python_semantic/resources/mdtest/comparison/tuples.md @@ -147,33 +147,40 @@ of the dunder methods.) ```py from __future__ import annotations +class EqReturnType: ... +class NeReturnType: ... +class LtReturnType: ... +class LeReturnType: ... +class GtReturnType: ... +class GeReturnType: ... + class A: - def __eq__(self, o: object) -> str: - return "hello" + def __eq__(self, o: object) -> EqReturnType: + return EqReturnType() - def __ne__(self, o: object) -> bytes: - return b"world" + def __ne__(self, o: object) -> NeReturnType: + return NeReturnType() - def __lt__(self, o: A) -> bytearray: - return bytearray() + def __lt__(self, o: A) -> LtReturnType: + return LtReturnType() - def __le__(self, o: A) -> memoryview: - return memoryview(b"") + def __le__(self, o: A) -> LeReturnType: + return LeReturnType() - def __gt__(self, o: A) -> tuple: - return (1, 2, 3) + def __gt__(self, o: A) -> GtReturnType: + return GtReturnType() - def __ge__(self, o: A) -> list: - return [1, 2, 3] + def __ge__(self, o: A) -> GeReturnType: + return GeReturnType() a = (A(), A()) 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: memoryview | Literal[True] -reveal_type(a > a) # revealed: tuple | Literal[False] -reveal_type(a >= a) # revealed: list | Literal[True] +reveal_type(a < a) # revealed: LtReturnType | Literal[False] +reveal_type(a <= a) # revealed: LeReturnType | Literal[True] +reveal_type(a > a) # revealed: GtReturnType | Literal[False] +reveal_type(a >= a) # revealed: GeReturnType | Literal[True] # If lexicographic comparison is finished before comparing 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] +class LtReturnTypeOnB: ... + class B: - def __lt__(self, o: B) -> set: + def __lt__(self, o: B) -> LtReturnTypeOnB: 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 diff --git a/crates/red_knot_python_semantic/resources/mdtest/exception/control_flow.md b/crates/red_knot_python_semantic/resources/mdtest/exception/control_flow.md index 871689ee77..3357427dae 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/exception/control_flow.md +++ b/crates/red_knot_python_semantic/resources/mdtest/exception/control_flow.md @@ -241,30 +241,34 @@ suites: `except` suite ran to completion ```py -def could_raise_returns_str() -> str: - return "foo" +class A: ... +class B: ... +class C: ... -def could_raise_returns_bytes() -> bytes: - return b"foo" +def could_raise_returns_A() -> A: + return A() -def could_raise_returns_bool() -> bool: - return True +def could_raise_returns_B() -> B: + return B() + +def could_raise_returns_C() -> C: + return C() x = 1 try: reveal_type(x) # revealed: Literal[1] - x = could_raise_returns_str() - reveal_type(x) # revealed: str + x = could_raise_returns_A() + reveal_type(x) # revealed: A except TypeError: - reveal_type(x) # revealed: Literal[1] | str - x = could_raise_returns_bytes() - reveal_type(x) # revealed: bytes - x = could_raise_returns_bool() - reveal_type(x) # revealed: bool + reveal_type(x) # revealed: Literal[1] | A + x = could_raise_returns_B() + reveal_type(x) # revealed: B + x = could_raise_returns_C() + reveal_type(x) # revealed: C finally: - # TODO: should be `Literal[1] | str | bytes | bool` - reveal_type(x) # revealed: str | bool + # TODO: should be `Literal[1] | A | B | C` + reveal_type(x) # revealed: A | C x = 2 reveal_type(x) # revealed: Literal[2] @@ -282,53 +286,56 @@ x = 1 try: reveal_type(x) # revealed: Literal[1] - x = could_raise_returns_str() - reveal_type(x) # revealed: str + x = could_raise_returns_A() + reveal_type(x) # revealed: A except TypeError: - reveal_type(x) # revealed: Literal[1] | str - x = could_raise_returns_bytes() - reveal_type(x) # revealed: bytes - x = could_raise_returns_bool() - reveal_type(x) # revealed: bool + reveal_type(x) # revealed: Literal[1] | A + x = could_raise_returns_B() + reveal_type(x) # revealed: B + x = could_raise_returns_C() + reveal_type(x) # revealed: C finally: - # TODO: should be `Literal[1] | str | bytes | bool` - reveal_type(x) # revealed: str | bool + # TODO: should be `Literal[1] | A | B | C` + 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: ```py -def could_raise_returns_memoryview() -> memoryview: - return memoryview(b"") +class D: ... +class E: ... -def could_raise_returns_bytearray() -> bytearray: - return bytearray() +def could_raise_returns_D() -> D: + return D() + +def could_raise_returns_E() -> E: + return E() x = 1 try: reveal_type(x) # revealed: Literal[1] - x = could_raise_returns_str() - reveal_type(x) # revealed: str + x = could_raise_returns_A() + reveal_type(x) # revealed: A except TypeError: - reveal_type(x) # revealed: Literal[1] | str - x = could_raise_returns_bytes() - reveal_type(x) # revealed: bytes - x = could_raise_returns_bool() - reveal_type(x) # revealed: bool + reveal_type(x) # revealed: Literal[1] | A + x = could_raise_returns_B() + reveal_type(x) # revealed: B + x = could_raise_returns_C() + reveal_type(x) # revealed: C except ValueError: - reveal_type(x) # revealed: Literal[1] | str - x = could_raise_returns_memoryview() - reveal_type(x) # revealed: memoryview - x = could_raise_returns_bytearray() - reveal_type(x) # revealed: bytearray + reveal_type(x) # revealed: Literal[1] | A + x = could_raise_returns_D() + reveal_type(x) # revealed: D + x = could_raise_returns_E() + reveal_type(x) # revealed: E finally: - # TODO: should be `Literal[1] | str | bytes | bool | memoryview | bytearray` - reveal_type(x) # revealed: str | bool | bytearray + # TODO: should be `Literal[1] | A | B | C | D | E` + 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 @@ -338,84 +345,93 @@ control flow could have jumped to the `finally` suite from partway through the ` an exception raised *there*. ```py -def could_raise_returns_str() -> str: - return "foo" +class A: ... +class B: ... +class C: ... +class D: ... +class E: ... -def could_raise_returns_bytes() -> bytes: - return b"foo" +def could_raise_returns_A() -> A: + return A() -def could_raise_returns_bool() -> bool: - return True +def could_raise_returns_B() -> B: + return B() -def could_raise_returns_memoryview() -> memoryview: - return memoryview(b"") +def could_raise_returns_C() -> C: + return C() -def could_raise_returns_bytearray() -> bytearray: - return bytearray() +def could_raise_returns_D() -> D: + return D() + +def could_raise_returns_E() -> E: + return E() x = 1 try: reveal_type(x) # revealed: Literal[1] - x = could_raise_returns_str() - reveal_type(x) # revealed: str + x = could_raise_returns_A() + reveal_type(x) # revealed: A except TypeError: - reveal_type(x) # revealed: Literal[1] | str - x = could_raise_returns_bytes() - reveal_type(x) # revealed: bytes - x = could_raise_returns_bool() - reveal_type(x) # revealed: bool + reveal_type(x) # revealed: Literal[1] | A + x = could_raise_returns_B() + reveal_type(x) # revealed: B + x = could_raise_returns_C() + reveal_type(x) # revealed: C else: - reveal_type(x) # revealed: str - x = could_raise_returns_memoryview() - reveal_type(x) # revealed: memoryview - x = could_raise_returns_bytearray() - reveal_type(x) # revealed: bytearray + reveal_type(x) # revealed: A + x = could_raise_returns_D() + reveal_type(x) # revealed: D + x = could_raise_returns_E() + reveal_type(x) # revealed: E finally: - # TODO: should be `Literal[1] | str | bytes | bool | memoryview | bytearray` - reveal_type(x) # revealed: bool | bytearray + # TODO: should be `Literal[1] | A | B | C | D | E` + 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: ```py -def could_raise_returns_range() -> range: - return range(42) +class F: ... +class G: ... -def could_raise_returns_slice() -> slice: - return slice(None) +def could_raise_returns_F() -> F: + return F() + +def could_raise_returns_G() -> G: + return G() x = 1 try: reveal_type(x) # revealed: Literal[1] - x = could_raise_returns_str() - reveal_type(x) # revealed: str + x = could_raise_returns_A() + reveal_type(x) # revealed: A except TypeError: - reveal_type(x) # revealed: Literal[1] | str - x = could_raise_returns_bytes() - reveal_type(x) # revealed: bytes - x = could_raise_returns_bool() - reveal_type(x) # revealed: bool + reveal_type(x) # revealed: Literal[1] | A + x = could_raise_returns_B() + reveal_type(x) # revealed: B + x = could_raise_returns_C() + reveal_type(x) # revealed: C except ValueError: - reveal_type(x) # revealed: Literal[1] | str - x = could_raise_returns_memoryview() - reveal_type(x) # revealed: memoryview - x = could_raise_returns_bytearray() - reveal_type(x) # revealed: bytearray + reveal_type(x) # revealed: Literal[1] | A + x = could_raise_returns_D() + reveal_type(x) # revealed: D + x = could_raise_returns_E() + reveal_type(x) # revealed: E else: - reveal_type(x) # revealed: str - x = could_raise_returns_range() - reveal_type(x) # revealed: range - x = could_raise_returns_slice() - reveal_type(x) # revealed: slice + reveal_type(x) # revealed: A + x = could_raise_returns_F() + reveal_type(x) # revealed: F + x = could_raise_returns_G() + reveal_type(x) # revealed: G finally: - # TODO: should be `Literal[1] | str | bytes | bool | memoryview | bytearray | range | slice` - reveal_type(x) # revealed: bool | bytearray | slice + # TODO: should be `Literal[1] | A | B | C | D | E | F | G` + 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 @@ -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. ```py -def could_raise_returns_str() -> str: - return "foo" +class A: ... +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: - return b"foo" +def could_raise_returns_A() -> A: + return A() -def could_raise_returns_bool() -> bool: - return True +def could_raise_returns_B() -> B: + return B() -def could_raise_returns_memoryview() -> memoryview: - return memoryview(b"") +def could_raise_returns_C() -> C: + return C() -def could_raise_returns_property() -> property: - return property() +def could_raise_returns_D() -> D: + return D() -def could_raise_returns_range() -> range: - return range(42) +def could_raise_returns_E() -> E: + return E() -def could_raise_returns_slice() -> slice: - return slice(None) +def could_raise_returns_F() -> F: + return F() -def could_raise_returns_super() -> super: - return super() +def could_raise_returns_G() -> G: + return G() -def could_raise_returns_bytearray() -> bytearray: - return bytearray() +def could_raise_returns_H() -> H: + return H() -class Foo: ... -class Bar: ... +def could_raise_returns_I() -> I: + return I() -def could_raise_returns_Foo() -> Foo: - return Foo() +def could_raise_returns_J() -> J: + return J() -def could_raise_returns_Bar() -> Bar: - return Bar() +def could_raise_returns_K() -> K: + return K() x = 1 try: try: reveal_type(x) # revealed: Literal[1] - x = could_raise_returns_str() - reveal_type(x) # revealed: str + x = could_raise_returns_A() + reveal_type(x) # revealed: A except TypeError: - reveal_type(x) # revealed: Literal[1] | str - x = could_raise_returns_bytes() - reveal_type(x) # revealed: bytes - x = could_raise_returns_bool() - reveal_type(x) # revealed: bool + reveal_type(x) # revealed: Literal[1] | A + x = could_raise_returns_B() + reveal_type(x) # revealed: B + x = could_raise_returns_C() + reveal_type(x) # revealed: C except ValueError: - reveal_type(x) # revealed: Literal[1] | str - x = could_raise_returns_memoryview() - reveal_type(x) # revealed: memoryview - x = could_raise_returns_property() - reveal_type(x) # revealed: property + reveal_type(x) # revealed: Literal[1] | A + x = could_raise_returns_D() + reveal_type(x) # revealed: D + x = could_raise_returns_E() + reveal_type(x) # revealed: E else: - reveal_type(x) # revealed: str - x = could_raise_returns_range() - reveal_type(x) # revealed: range - x = could_raise_returns_slice() - reveal_type(x) # revealed: slice + reveal_type(x) # revealed: A + x = could_raise_returns_F() + reveal_type(x) # revealed: F + x = could_raise_returns_G() + reveal_type(x) # revealed: G finally: - # TODO: should be `Literal[1] | str | bytes | bool | memoryview | property | range | slice` - reveal_type(x) # revealed: bool | property | slice + # TODO: should be `Literal[1] | A | B | C | D | E | F | G` + reveal_type(x) # revealed: C | E | G x = 2 reveal_type(x) # revealed: Literal[2] reveal_type(x) # revealed: Literal[2] except: - reveal_type(x) # revealed: Literal[1, 2] | str | bytes | bool | memoryview | property | range | slice - x = could_raise_returns_super() - reveal_type(x) # revealed: super - x = could_raise_returns_bytearray() - reveal_type(x) # revealed: bytearray + reveal_type(x) # revealed: Literal[1, 2] | A | B | C | D | E | F | G + x = could_raise_returns_H() + reveal_type(x) # revealed: H + x = could_raise_returns_I() + reveal_type(x) # revealed: I else: reveal_type(x) # revealed: Literal[2] - x = could_raise_returns_Foo() - reveal_type(x) # revealed: Foo - x = could_raise_returns_Bar() - reveal_type(x) # revealed: Bar + x = could_raise_returns_J() + reveal_type(x) # revealed: J + x = could_raise_returns_K() + reveal_type(x) # revealed: K finally: - # TODO: should be `Literal[1, 2] | str | bytes | bool | memoryview | property | range | slice | super | bytearray | Foo | Bar` - reveal_type(x) # revealed: bytearray | Bar + # TODO: should be `Literal[1, 2] | A | B | C | D | E | F | G | H | I | J | K` + reveal_type(x) # revealed: I | K # Either one `except` branch or the `else` # 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 @@ -523,50 +548,56 @@ Shadowing a variable in an inner scope has no effect on type inference of the va in the outer scope: ```py -def could_raise_returns_str() -> str: - return "foo" +class A: ... +class B: ... +class C: ... +class D: ... +class E: ... -def could_raise_returns_bytes() -> bytes: - return b"foo" +def could_raise_returns_A() -> A: + return A() -def could_raise_returns_range() -> range: - return range(42) +def could_raise_returns_B() -> B: + return B() -def could_raise_returns_bytearray() -> bytearray: - return bytearray() +def could_raise_returns_C() -> C: + return C() -def could_raise_returns_memoryview() -> memoryview: - return memoryview(b"") +def could_raise_returns_D() -> D: + return D() + +def could_raise_returns_E() -> E: + return E() x = 1 try: - def foo(param=could_raise_returns_str()): - x = could_raise_returns_str() + def foo(param=could_raise_returns_A()): + x = could_raise_returns_A() try: - reveal_type(x) # revealed: str - x = could_raise_returns_bytes() - reveal_type(x) # revealed: bytes + reveal_type(x) # revealed: A + x = could_raise_returns_B() + reveal_type(x) # revealed: B except: - reveal_type(x) # revealed: str | bytes - x = could_raise_returns_bytearray() - reveal_type(x) # revealed: bytearray - x = could_raise_returns_memoryview() - reveal_type(x) # revealed: memoryview + reveal_type(x) # revealed: A | B + x = could_raise_returns_C() + reveal_type(x) # revealed: C + x = could_raise_returns_D() + reveal_type(x) # revealed: D finally: - # TODO: should be `str | bytes | bytearray | memoryview` - reveal_type(x) # revealed: bytes | memoryview - reveal_type(x) # revealed: bytes | memoryview + # TODO: should be `A | B | C | D` + reveal_type(x) # revealed: B | D + reveal_type(x) # revealed: B | D x = foo reveal_type(x) # revealed: Literal[foo] except: reveal_type(x) # revealed: Literal[1] | Literal[foo] class Bar: - x = could_raise_returns_range() - reveal_type(x) # revealed: range + x = could_raise_returns_E() + reveal_type(x) # revealed: E x = Bar reveal_type(x) # revealed: Literal[Bar] diff --git a/crates/red_knot_python_semantic/resources/mdtest/loops/for.md b/crates/red_knot_python_semantic/resources/mdtest/loops/for.md index 06fcd44be5..b2ef39956f 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/loops/for.md +++ b/crates/red_knot_python_semantic/resources/mdtest/loops/for.md @@ -183,25 +183,32 @@ for x in Test(): ## Union type as iterable and union type as iterator ```py -class TestIter: - def __next__(self) -> int | Exception: - return 42 +class Result1A: ... +class Result1B: ... +class Result2A: ... +class Result2B: ... +class Result3: ... +class Result4: ... + +class TestIter1: + def __next__(self) -> Result1A | Result1B: + return Result1B() class TestIter2: - def __next__(self) -> str | tuple[int, int]: - return "42" + def __next__(self) -> Result2A | Result2B: + return Result2B() class TestIter3: - def __next__(self) -> bytes: - return b"42" + def __next__(self) -> Result3: + return Result3() class TestIter4: - def __next__(self) -> memoryview: - return memoryview(b"42") + def __next__(self) -> Result4: + return Result4() class Test: - def __iter__(self) -> TestIter | TestIter2: - return TestIter() + def __iter__(self) -> TestIter1 | TestIter2: + return TestIter1() class Test2: def __iter__(self) -> TestIter3 | TestIter4: @@ -209,7 +216,7 @@ class Test2: def _(flag: bool): 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