diff --git a/crates/red_knot_python_semantic/resources/mdtest/annotations/any.md b/crates/red_knot_python_semantic/resources/mdtest/annotations/any.md index a1db55272c3bb..17e5a99b2aead 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/annotations/any.md +++ b/crates/red_knot_python_semantic/resources/mdtest/annotations/any.md @@ -34,8 +34,7 @@ If you define your own class named `Any`, using that in a type expression refers isn't a spelling of the Any type. ```py -class Any: - pass +class Any: ... x: Any @@ -59,8 +58,7 @@ assignable to `int`. ```py from typing import Any -class Subclass(Any): - pass +class Subclass(Any): ... reveal_type(Subclass.__mro__) # revealed: tuple[Literal[Subclass], Any, Literal[object]] @@ -68,8 +66,6 @@ x: Subclass = 1 # error: [invalid-assignment] # TODO: no diagnostic y: int = Subclass() # error: [invalid-assignment] -def f() -> Subclass: - pass - -reveal_type(f()) # revealed: Subclass +def _(s: Subclass): + reveal_type(s) # revealed: Subclass ``` diff --git a/crates/red_knot_python_semantic/resources/mdtest/annotations/literal_string.md b/crates/red_knot_python_semantic/resources/mdtest/annotations/literal_string.md index 28bc3898f5c05..35cf90a0cb3c4 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/annotations/literal_string.md +++ b/crates/red_knot_python_semantic/resources/mdtest/annotations/literal_string.md @@ -89,28 +89,26 @@ vice versa. ```py from typing_extensions import Literal, LiteralString -def coinflip() -> bool: - return True +def _(flag: bool): + foo_1: Literal["foo"] = "foo" + bar_1: LiteralString = foo_1 # fine -foo_1: Literal["foo"] = "foo" -bar_1: LiteralString = foo_1 # fine + foo_2 = "foo" if flag else "bar" + reveal_type(foo_2) # revealed: Literal["foo", "bar"] + bar_2: LiteralString = foo_2 # fine -foo_2 = "foo" if coinflip() else "bar" -reveal_type(foo_2) # revealed: Literal["foo", "bar"] -bar_2: LiteralString = foo_2 # fine + foo_3: LiteralString = "foo" * 1_000_000_000 + bar_3: str = foo_2 # fine -foo_3: LiteralString = "foo" * 1_000_000_000 -bar_3: str = foo_2 # fine + baz_1: str = str() + qux_1: LiteralString = baz_1 # error: [invalid-assignment] -baz_1: str = str() -qux_1: LiteralString = baz_1 # error: [invalid-assignment] + baz_2: LiteralString = "baz" * 1_000_000_000 + qux_2: Literal["qux"] = baz_2 # error: [invalid-assignment] -baz_2: LiteralString = "baz" * 1_000_000_000 -qux_2: Literal["qux"] = baz_2 # error: [invalid-assignment] - -baz_3 = "foo" if coinflip() else 1 -reveal_type(baz_3) # revealed: Literal["foo"] | Literal[1] -qux_3: LiteralString = baz_3 # error: [invalid-assignment] + baz_3 = "foo" if flag else 1 + reveal_type(baz_3) # revealed: Literal["foo"] | Literal[1] + qux_3: LiteralString = baz_3 # error: [invalid-assignment] ``` ### Narrowing diff --git a/crates/red_knot_python_semantic/resources/mdtest/annotations/string.md b/crates/red_knot_python_semantic/resources/mdtest/annotations/string.md index 140a9ad0033c7..b72b542529343 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/annotations/string.md +++ b/crates/red_knot_python_semantic/resources/mdtest/annotations/string.md @@ -3,75 +3,56 @@ ## Simple ```py -def f() -> "int": - return 1 - -reveal_type(f()) # revealed: int +def f(v: "int"): + reveal_type(v) # revealed: int ``` ## Nested ```py -def f() -> "'int'": - return 1 - -reveal_type(f()) # revealed: int +def f(v: "'int'"): + reveal_type(v) # revealed: int ``` ## Type expression ```py -def f1() -> "int | str": - return 1 - -def f2() -> "tuple[int, str]": - return 1 - -reveal_type(f1()) # revealed: int | str -reveal_type(f2()) # revealed: tuple[int, str] +def f1(v: "int | str", w: "tuple[int, str]"): + reveal_type(v) # revealed: int | str + reveal_type(w) # revealed: tuple[int, str] ``` ## Partial ```py -def f() -> tuple[int, "str"]: - return 1 - -reveal_type(f()) # revealed: tuple[int, str] +def f(v: tuple[int, "str"]): + reveal_type(v) # revealed: tuple[int, str] ``` ## Deferred ```py -def f() -> "Foo": - return Foo() - -class Foo: - pass +def f(v: "Foo"): + reveal_type(v) # revealed: Foo -reveal_type(f()) # revealed: Foo +class Foo: ... ``` ## Deferred (undefined) ```py # error: [unresolved-reference] -def f() -> "Foo": - pass - -reveal_type(f()) # revealed: Unknown +def f(v: "Foo"): + reveal_type(v) # revealed: Unknown ``` ## Partial deferred ```py -def f() -> int | "Foo": - return 1 - -class Foo: - pass +def f(v: int | "Foo"): + reveal_type(v) # revealed: int | Foo -reveal_type(f()) # revealed: int | Foo +class Foo: ... ``` ## `typing.Literal` @@ -79,65 +60,43 @@ reveal_type(f()) # revealed: int | Foo ```py from typing import Literal -def f1() -> Literal["Foo", "Bar"]: - return "Foo" - -def f2() -> 'Literal["Foo", "Bar"]': - return "Foo" +def f1(v: Literal["Foo", "Bar"], w: 'Literal["Foo", "Bar"]'): + reveal_type(v) # revealed: Literal["Foo", "Bar"] + reveal_type(w) # revealed: Literal["Foo", "Bar"] -class Foo: - pass - -reveal_type(f1()) # revealed: Literal["Foo", "Bar"] -reveal_type(f2()) # revealed: Literal["Foo", "Bar"] +class Foo: ... ``` ## Various string kinds ```py -# error: [annotation-raw-string] "Type expressions cannot use raw string literal" -def f1() -> r"int": - return 1 - -# error: [annotation-f-string] "Type expressions cannot use f-strings" -def f2() -> f"int": - return 1 - -# error: [annotation-byte-string] "Type expressions cannot use bytes literal" -def f3() -> b"int": - return 1 - -def f4() -> "int": - return 1 - -# error: [annotation-implicit-concat] "Type expressions cannot span multiple string literals" -def f5() -> "in" "t": - return 1 - -# error: [annotation-escape-character] "Type expressions cannot contain escape characters" -def f6() -> "\N{LATIN SMALL LETTER I}nt": - return 1 - -# error: [annotation-escape-character] "Type expressions cannot contain escape characters" -def f7() -> "\x69nt": - return 1 - -def f8() -> """int""": - return 1 - -# error: [annotation-byte-string] "Type expressions cannot use bytes literal" -def f9() -> "b'int'": - return 1 - -reveal_type(f1()) # revealed: Unknown -reveal_type(f2()) # revealed: Unknown -reveal_type(f3()) # revealed: Unknown -reveal_type(f4()) # revealed: int -reveal_type(f5()) # revealed: Unknown -reveal_type(f6()) # revealed: Unknown -reveal_type(f7()) # revealed: Unknown -reveal_type(f8()) # revealed: int -reveal_type(f9()) # revealed: Unknown +def f1( + # error: [annotation-raw-string] "Type expressions cannot use raw string literal" + a: r"int", + # error: [annotation-f-string] "Type expressions cannot use f-strings" + b: f"int", + # error: [annotation-byte-string] "Type expressions cannot use bytes literal" + c: b"int", + d: "int", + # error: [annotation-implicit-concat] "Type expressions cannot span multiple string literals" + e: "in" "t", + # error: [annotation-escape-character] "Type expressions cannot contain escape characters" + f: "\N{LATIN SMALL LETTER I}nt", + # error: [annotation-escape-character] "Type expressions cannot contain escape characters" + g: "\x69nt", + h: """int""", + # error: [annotation-byte-string] "Type expressions cannot use bytes literal" + i: "b'int'", +): + reveal_type(a) # revealed: Unknown + reveal_type(b) # revealed: Unknown + reveal_type(c) # revealed: Unknown + reveal_type(d) # revealed: int + reveal_type(e) # revealed: Unknown + reveal_type(f) # revealed: Unknown + reveal_type(g) # revealed: Unknown + reveal_type(h) # revealed: int + reveal_type(i) # revealed: Unknown ``` ## Various string kinds in `typing.Literal` @@ -145,10 +104,8 @@ reveal_type(f9()) # revealed: Unknown ```py from typing import Literal -def f() -> Literal["a", r"b", b"c", "d" "e", "\N{LATIN SMALL LETTER F}", "\x67", """h"""]: - return "normal" - -reveal_type(f()) # revealed: Literal["a", "b", "de", "f", "g", "h"] | Literal[b"c"] +def f(v: Literal["a", r"b", b"c", "d" "e", "\N{LATIN SMALL LETTER F}", "\x67", """h"""]): + reveal_type(v) # revealed: Literal["a", "b", "de", "f", "g", "h"] | Literal[b"c"] ``` ## Class variables @@ -175,8 +132,7 @@ c: "Foo" # error: [invalid-assignment] "Object of type `Literal[1]` is not assignable to `Foo`" d: "Foo" = 1 -class Foo: - pass +class Foo: ... c = Foo() diff --git a/crates/red_knot_python_semantic/resources/mdtest/assignment/annotations.md b/crates/red_knot_python_semantic/resources/mdtest/assignment/annotations.md index c83628b83ec2c..c3977ed46b6c4 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/assignment/annotations.md +++ b/crates/red_knot_python_semantic/resources/mdtest/assignment/annotations.md @@ -78,20 +78,10 @@ c: tuple[str | int, str] = ([], "foo") ## PEP-604 annotations are supported ```py -def foo() -> str | int | None: - return None - -reveal_type(foo()) # revealed: str | int | None - -def bar() -> str | str | None: - return None - -reveal_type(bar()) # revealed: str | None - -def baz() -> str | str: - return "Hello, world!" - -reveal_type(baz()) # revealed: str +def foo(v: str | int | None, w: str | str | None, x: str | str): + reveal_type(v) # revealed: str | int | None + reveal_type(w) # revealed: str | None + reveal_type(x) # revealed: str ``` ## Attribute expressions in type annotations are understood @@ -118,8 +108,7 @@ from __future__ import annotations x: Foo -class Foo: - pass +class Foo: ... x = Foo() reveal_type(x) # revealed: Foo @@ -130,8 +119,7 @@ reveal_type(x) # revealed: Foo ```pyi path=main.pyi x: Foo -class Foo: - pass +class Foo: ... x = Foo() reveal_type(x) # revealed: Foo diff --git a/crates/red_knot_python_semantic/resources/mdtest/assignment/augmented.md b/crates/red_knot_python_semantic/resources/mdtest/assignment/augmented.md index 1e28506e1f5d3..cff18e7fb29c8 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/assignment/augmented.md +++ b/crates/red_knot_python_semantic/resources/mdtest/assignment/augmented.md @@ -49,134 +49,116 @@ reveal_type(x) # revealed: int ## Method union ```py -def bool_instance() -> bool: - return True +def _(flag: bool): + class Foo: + if flag: + def __iadd__(self, other: int) -> str: + return "Hello, world!" + else: + def __iadd__(self, other: int) -> int: + return 42 -flag = bool_instance() - -class Foo: - if bool_instance(): - def __iadd__(self, other: int) -> str: - return "Hello, world!" - else: - def __iadd__(self, other: int) -> int: - return 42 - -f = Foo() -f += 12 + f = Foo() + f += 12 -reveal_type(f) # revealed: str | int + reveal_type(f) # revealed: str | int ``` ## Partially bound `__iadd__` ```py -def bool_instance() -> bool: - return True +def _(flag: bool): + class Foo: + if flag: + def __iadd__(self, other: str) -> int: + return 42 -class Foo: - if bool_instance(): - def __iadd__(self, other: str) -> int: - return 42 - -f = Foo() + f = Foo() -# TODO: We should emit an `unsupported-operator` error here, possibly with the information -# that `Foo.__iadd__` may be unbound as additional context. -f += "Hello, world!" + # TODO: We should emit an `unsupported-operator` error here, possibly with the information + # that `Foo.__iadd__` may be unbound as additional context. + f += "Hello, world!" -reveal_type(f) # revealed: int | Unknown + reveal_type(f) # revealed: int | Unknown ``` ## Partially bound with `__add__` ```py -def bool_instance() -> bool: - return True - -class Foo: - def __add__(self, other: str) -> str: - return "Hello, world!" - if bool_instance(): - def __iadd__(self, other: str) -> int: - return 42 +def _(flag: bool): + class Foo: + def __add__(self, other: str) -> str: + return "Hello, world!" + if flag: + def __iadd__(self, other: str) -> int: + return 42 -f = Foo() -f += "Hello, world!" + f = Foo() + f += "Hello, world!" -reveal_type(f) # revealed: int | str + reveal_type(f) # revealed: int | str ``` ## Partially bound target union ```py -def bool_instance() -> bool: - return True - -class Foo: - def __add__(self, other: int) -> str: - return "Hello, world!" - if bool_instance(): - def __iadd__(self, other: int) -> int: - return 42 +def _(flag1: bool, flag2: bool): + class Foo: + def __add__(self, other: int) -> str: + return "Hello, world!" + if flag1: + def __iadd__(self, other: int) -> int: + return 42 -if bool_instance(): - f = Foo() -else: - f = 42.0 -f += 12 + if flag2: + f = Foo() + else: + f = 42.0 + f += 12 -reveal_type(f) # revealed: int | str | float + reveal_type(f) # revealed: int | str | float ``` ## Target union ```py -def bool_instance() -> bool: - return True - -flag = bool_instance() - -class Foo: - def __iadd__(self, other: int) -> str: - return "Hello, world!" +def _(flag: bool): + class Foo: + def __iadd__(self, other: int) -> str: + return "Hello, world!" -if flag: - f = Foo() -else: - f = 42.0 -f += 12 + if flag: + f = Foo() + else: + f = 42.0 + f += 12 -reveal_type(f) # revealed: str | float + reveal_type(f) # revealed: str | float ``` ## Partially bound target union with `__add__` ```py -def bool_instance() -> bool: - return True - -flag = bool_instance() - -class Foo: - def __add__(self, other: int) -> str: - return "Hello, world!" - if bool_instance(): - def __iadd__(self, other: int) -> int: - return 42 +def f(flag: bool, flag2: bool): + class Foo: + def __add__(self, other: int) -> str: + return "Hello, world!" + if flag: + def __iadd__(self, other: int) -> int: + return 42 -class Bar: - def __add__(self, other: int) -> bytes: - return b"Hello, world!" + class Bar: + def __add__(self, other: int) -> bytes: + return b"Hello, world!" - def __iadd__(self, other: int) -> float: - return 42.0 + def __iadd__(self, other: int) -> float: + return 42.0 -if flag: - f = Foo() -else: - f = Bar() -f += 12 + if flag2: + f = Foo() + else: + f = Bar() + f += 12 -reveal_type(f) # revealed: int | str | float + reveal_type(f) # revealed: int | str | float ``` diff --git a/crates/red_knot_python_semantic/resources/mdtest/attributes.md b/crates/red_knot_python_semantic/resources/mdtest/attributes.md index d4b6184f72d3b..df7cbaabcaa8c 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/attributes.md +++ b/crates/red_knot_python_semantic/resources/mdtest/attributes.md @@ -3,27 +3,23 @@ ## Union of attributes ```py -def bool_instance() -> bool: - return True - -flag = bool_instance() - -if flag: - class C1: - x = 1 - -else: - class C1: - x = 2 - -class C2: +def _(flag: bool): if flag: - x = 3 + class C1: + x = 1 + else: - x = 4 + class C1: + x = 2 -reveal_type(C1.x) # revealed: Literal[1, 2] -reveal_type(C2.x) # revealed: Literal[3, 4] + class C2: + if flag: + x = 3 + else: + x = 4 + + reveal_type(C1.x) # revealed: Literal[1, 2] + reveal_type(C2.x) # revealed: Literal[3, 4] ``` ## Inherited attributes @@ -68,24 +64,19 @@ reveal_type(A.X) # revealed: Literal[42] In this example, the `x` attribute is not defined in the `C2` element of the union: ```py -def bool_instance() -> bool: - return True - -class C1: - x = 1 - -class C2: ... +def _(flag1: bool, flag2: bool): + class C1: + x = 1 -class C3: - x = 3 + class C2: ... -flag1 = bool_instance() -flag2 = bool_instance() + class C3: + x = 3 -C = C1 if flag1 else C2 if flag2 else C3 + C = C1 if flag1 else C2 if flag2 else C3 -# error: [possibly-unbound-attribute] "Attribute `x` on type `Literal[C1, C2, C3]` is possibly unbound" -reveal_type(C.x) # revealed: Literal[1, 3] + # error: [possibly-unbound-attribute] "Attribute `x` on type `Literal[C1, C2, C3]` is possibly unbound" + reveal_type(C.x) # revealed: Literal[1, 3] ``` ### Possibly-unbound within a class @@ -94,26 +85,21 @@ We raise the same diagnostic if the attribute is possibly-unbound in at least on union: ```py -def bool_instance() -> bool: - return True - -class C1: - x = 1 - -class C2: - if bool_instance(): - x = 2 +def _(flag: bool, flag1: bool, flag2: bool): + class C1: + x = 1 -class C3: - x = 3 + class C2: + if flag: + x = 2 -flag1 = bool_instance() -flag2 = bool_instance() + class C3: + x = 3 -C = C1 if flag1 else C2 if flag2 else C3 + C = C1 if flag1 else C2 if flag2 else C3 -# error: [possibly-unbound-attribute] "Attribute `x` on type `Literal[C1, C2, C3]` is possibly unbound" -reveal_type(C.x) # revealed: Literal[1, 2, 3] + # error: [possibly-unbound-attribute] "Attribute `x` on type `Literal[C1, C2, C3]` is possibly unbound" + reveal_type(C.x) # revealed: Literal[1, 2, 3] ``` ## Unions with all paths unbound @@ -121,16 +107,11 @@ reveal_type(C.x) # revealed: Literal[1, 2, 3] If the symbol is unbound in all elements of the union, we detect that: ```py -def bool_instance() -> bool: - return True - -class C1: ... -class C2: ... - -flag = bool_instance() - -C = C1 if flag else C2 +def _(flag: bool): + class C1: ... + class C2: ... + C = C1 if flag else C2 -# error: [unresolved-attribute] "Type `Literal[C1, C2]` has no attribute `x`" -reveal_type(C.x) # revealed: Unknown + # error: [unresolved-attribute] "Type `Literal[C1, C2]` has no attribute `x`" + reveal_type(C.x) # revealed: Unknown ``` diff --git a/crates/red_knot_python_semantic/resources/mdtest/binary/instances.md b/crates/red_knot_python_semantic/resources/mdtest/binary/instances.md index 15029fead98eb..0f614802f3584 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/binary/instances.md +++ b/crates/red_knot_python_semantic/resources/mdtest/binary/instances.md @@ -281,20 +281,12 @@ reveal_type(42 + 4.2) # revealed: int # TODO should be complex, need to check arg type and fall back to `rhs.__radd__` reveal_type(3 + 3j) # revealed: int -def returns_int() -> int: - return 42 +def _(x: bool, y: int): + reveal_type(x + y) # revealed: int + reveal_type(4.2 + x) # revealed: float -def returns_bool() -> bool: - return True - -x = returns_bool() -y = returns_int() - -reveal_type(x + y) # revealed: int -reveal_type(4.2 + x) # revealed: float - -# TODO should be float, need to check arg type and fall back to `rhs.__radd__` -reveal_type(y + 4.12) # revealed: int + # TODO should be float, need to check arg type and fall back to `rhs.__radd__` + reveal_type(y + 4.12) # revealed: int ``` ## With literal types diff --git a/crates/red_knot_python_semantic/resources/mdtest/boolean/short_circuit.md b/crates/red_knot_python_semantic/resources/mdtest/boolean/short_circuit.md index fc475af2641f8..9ee078606d5cd 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/boolean/short_circuit.md +++ b/crates/red_knot_python_semantic/resources/mdtest/boolean/short_circuit.md @@ -7,29 +7,25 @@ Similarly, in `and` expressions, if the left-hand side is falsy, the right-hand evaluated. ```py -def bool_instance() -> bool: - return True - -if bool_instance() or (x := 1): - # error: [possibly-unresolved-reference] - reveal_type(x) # revealed: Literal[1] - -if bool_instance() and (x := 1): - # error: [possibly-unresolved-reference] - reveal_type(x) # revealed: Literal[1] +def _(flag: bool): + if flag or (x := 1): + # error: [possibly-unresolved-reference] + reveal_type(x) # revealed: Literal[1] + + if flag and (x := 1): + # error: [possibly-unresolved-reference] + reveal_type(x) # revealed: Literal[1] ``` ## First expression is always evaluated ```py -def bool_instance() -> bool: - return True +def _(flag: bool): + if (x := 1) or flag: + reveal_type(x) # revealed: Literal[1] -if (x := 1) or bool_instance(): - reveal_type(x) # revealed: Literal[1] - -if (x := 1) and bool_instance(): - reveal_type(x) # revealed: Literal[1] + if (x := 1) and flag: + reveal_type(x) # revealed: Literal[1] ``` ## Statically known truthiness @@ -49,30 +45,26 @@ if True and (x := 1): ## Later expressions can always use variables from earlier expressions ```py -def bool_instance() -> bool: - return True - -bool_instance() or (x := 1) or reveal_type(x) # revealed: Literal[1] +def _(flag: bool): + flag or (x := 1) or reveal_type(x) # revealed: Literal[1] -# error: [unresolved-reference] -bool_instance() or reveal_type(y) or (y := 1) # revealed: Unknown + # error: [unresolved-reference] + flag or reveal_type(y) or (y := 1) # revealed: Unknown ``` ## Nested expressions ```py -def bool_instance() -> bool: - return True - -if bool_instance() or ((x := 1) and bool_instance()): - # error: [possibly-unresolved-reference] - reveal_type(x) # revealed: Literal[1] +def _(flag1: bool, flag2: bool): + if flag1 or ((x := 1) and flag2): + # error: [possibly-unresolved-reference] + reveal_type(x) # revealed: Literal[1] -if ((y := 1) and bool_instance()) or bool_instance(): - reveal_type(y) # revealed: Literal[1] + if ((y := 1) and flag1) or flag2: + reveal_type(y) # revealed: Literal[1] -# error: [possibly-unresolved-reference] -if (bool_instance() and (z := 1)) or reveal_type(z): # revealed: Literal[1] # error: [possibly-unresolved-reference] - reveal_type(z) # revealed: Literal[1] + if (flag1 and (z := 1)) or reveal_type(z): # revealed: Literal[1] + # error: [possibly-unresolved-reference] + reveal_type(z) # revealed: Literal[1] ``` diff --git a/crates/red_knot_python_semantic/resources/mdtest/call/callable_instance.md b/crates/red_knot_python_semantic/resources/mdtest/call/callable_instance.md index 95e82fc5720b1..746aee725f547 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/call/callable_instance.md +++ b/crates/red_knot_python_semantic/resources/mdtest/call/callable_instance.md @@ -22,29 +22,27 @@ reveal_type(b) # revealed: Unknown ## Possibly unbound `__call__` method ```py -def flag() -> bool: ... - -class PossiblyNotCallable: - if flag(): - def __call__(self) -> int: ... - -a = PossiblyNotCallable() -result = a() # error: "Object of type `PossiblyNotCallable` is not callable (possibly unbound `__call__` method)" -reveal_type(result) # revealed: int +def _(flag: bool): + class PossiblyNotCallable: + if flag: + def __call__(self) -> int: ... + + a = PossiblyNotCallable() + result = a() # error: "Object of type `PossiblyNotCallable` is not callable (possibly unbound `__call__` method)" + reveal_type(result) # revealed: int ``` ## Possibly unbound callable ```py -def flag() -> bool: ... - -if flag(): - class PossiblyUnbound: - def __call__(self) -> int: ... - -# error: [possibly-unresolved-reference] -a = PossiblyUnbound() -reveal_type(a()) # revealed: int +def _(flag: bool): + if flag: + class PossiblyUnbound: + def __call__(self) -> int: ... + + # error: [possibly-unresolved-reference] + a = PossiblyUnbound() + reveal_type(a()) # revealed: int ``` ## Non-callable `__call__` @@ -61,15 +59,14 @@ reveal_type(a()) # revealed: Unknown ## Possibly non-callable `__call__` ```py -def flag() -> bool: ... - -class NonCallable: - if flag(): - __call__ = 1 - else: - def __call__(self) -> int: ... - -a = NonCallable() -# error: "Object of type `Literal[1] | Literal[__call__]` is not callable (due to union element `Literal[1]`)" -reveal_type(a()) # revealed: Unknown | int +def _(flag: bool): + class NonCallable: + if flag: + __call__ = 1 + else: + def __call__(self) -> int: ... + + a = NonCallable() + # error: "Object of type `Literal[1] | Literal[__call__]` is not callable (due to union element `Literal[1]`)" + reveal_type(a()) # revealed: Unknown | int ``` diff --git a/crates/red_knot_python_semantic/resources/mdtest/call/function.md b/crates/red_knot_python_semantic/resources/mdtest/call/function.md index b67b14d558585..af7c1e2582cae 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/call/function.md +++ b/crates/red_knot_python_semantic/resources/mdtest/call/function.md @@ -57,12 +57,10 @@ x = nonsense() # error: "Object of type `Literal[123]` is not callable" ## Potentially unbound function ```py -def flag() -> bool: ... - -if flag(): - def foo() -> int: - return 42 - -# error: [possibly-unresolved-reference] -reveal_type(foo()) # revealed: int +def _(flag: bool): + if flag: + def foo() -> int: + return 42 + # error: [possibly-unresolved-reference] + reveal_type(foo()) # revealed: int ``` diff --git a/crates/red_knot_python_semantic/resources/mdtest/call/union.md b/crates/red_knot_python_semantic/resources/mdtest/call/union.md index 911f21947ef3c..55483854888af 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/call/union.md +++ b/crates/red_knot_python_semantic/resources/mdtest/call/union.md @@ -3,22 +3,14 @@ ## Union of return types ```py -def bool_instance() -> bool: - return True - -flag = bool_instance() - -if flag: - - def f() -> int: - return 1 - -else: - - def f() -> str: - return "foo" - -reveal_type(f()) # revealed: int | str +def _(flag: bool): + if flag: + def f() -> int: + return 1 + else: + def f() -> str: + return "foo" + reveal_type(f()) # revealed: int | str ``` ## Calling with an unknown union @@ -26,13 +18,10 @@ reveal_type(f()) # revealed: int | str ```py from nonexistent import f # error: [unresolved-import] "Cannot resolve import `nonexistent`" -def bool_instance() -> bool: +def coinflip() -> bool: return True -flag = bool_instance() - -if flag: - +if coinflip(): def f() -> int: return 1 @@ -44,20 +33,14 @@ reveal_type(f()) # revealed: Unknown | int Calling a union with a non-callable element should emit a diagnostic. ```py -def bool_instance() -> bool: - return True - -flag = bool_instance() - -if flag: - f = 1 -else: - - def f() -> int: - return 1 - -x = f() # error: "Object of type `Literal[1] | Literal[f]` is not callable (due to union element `Literal[1]`)" -reveal_type(x) # revealed: Unknown | int +def _(flag: bool): + if flag: + f = 1 + else: + def f() -> int: + return 1 + x = f() # error: "Object of type `Literal[1] | Literal[f]` is not callable (due to union element `Literal[1]`)" + reveal_type(x) # revealed: Unknown | int ``` ## Multiple non-callable elements in a union @@ -65,23 +48,17 @@ reveal_type(x) # revealed: Unknown | int Calling a union with multiple non-callable elements should mention all of them in the diagnostic. ```py -def bool_instance() -> bool: - return True - -flag, flag2 = bool_instance(), bool_instance() - -if flag: - f = 1 -elif flag2: - f = "foo" -else: - - def f() -> int: - return 1 - -# error: "Object of type `Literal[1] | Literal["foo"] | Literal[f]` is not callable (due to union elements Literal[1], Literal["foo"])" -# revealed: Unknown | int -reveal_type(f()) +def _(flag: bool, flag2: bool): + if flag: + f = 1 + elif flag2: + f = "foo" + else: + def f() -> int: + return 1 + # error: "Object of type `Literal[1] | Literal["foo"] | Literal[f]` is not callable (due to union elements Literal[1], Literal["foo"])" + # revealed: Unknown | int + reveal_type(f()) ``` ## All non-callable union elements @@ -89,16 +66,12 @@ reveal_type(f()) Calling a union with no callable elements can emit a simpler diagnostic. ```py -def bool_instance() -> bool: - return True - -flag = bool_instance() - -if flag: - f = 1 -else: - f = "foo" - -x = f() # error: "Object of type `Literal[1] | Literal["foo"]` is not callable" -reveal_type(x) # revealed: Unknown +def _(flag: bool): + if flag: + f = 1 + else: + f = "foo" + + x = f() # error: "Object of type `Literal[1] | Literal["foo"]` is not callable" + reveal_type(x) # revealed: Unknown ``` diff --git a/crates/red_knot_python_semantic/resources/mdtest/comparison/identity.md b/crates/red_knot_python_semantic/resources/mdtest/comparison/identity.md index bf162efa0a9e0..a636307a1dbfa 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/comparison/identity.md +++ b/crates/red_knot_python_semantic/resources/mdtest/comparison/identity.md @@ -3,38 +3,31 @@ ```py class A: ... -def get_a() -> A: ... -def get_object() -> object: ... +def _(a1: A, a2: A, o: object): + n1 = None + n2 = None -a1 = get_a() -a2 = get_a() + reveal_type(a1 is a1) # revealed: bool + reveal_type(a1 is a2) # revealed: bool -n1 = None -n2 = None + reveal_type(n1 is n1) # revealed: Literal[True] + reveal_type(n1 is n2) # revealed: Literal[True] -o = get_object() + reveal_type(a1 is n1) # revealed: Literal[False] + reveal_type(n1 is a1) # revealed: Literal[False] -reveal_type(a1 is a1) # revealed: bool -reveal_type(a1 is a2) # revealed: bool + reveal_type(a1 is o) # revealed: bool + reveal_type(n1 is o) # revealed: bool -reveal_type(n1 is n1) # revealed: Literal[True] -reveal_type(n1 is n2) # revealed: Literal[True] + reveal_type(a1 is not a1) # revealed: bool + reveal_type(a1 is not a2) # revealed: bool -reveal_type(a1 is n1) # revealed: Literal[False] -reveal_type(n1 is a1) # revealed: Literal[False] + reveal_type(n1 is not n1) # revealed: Literal[False] + reveal_type(n1 is not n2) # revealed: Literal[False] -reveal_type(a1 is o) # revealed: bool -reveal_type(n1 is o) # revealed: bool + reveal_type(a1 is not n1) # revealed: Literal[True] + reveal_type(n1 is not a1) # revealed: Literal[True] -reveal_type(a1 is not a1) # revealed: bool -reveal_type(a1 is not a2) # revealed: bool - -reveal_type(n1 is not n1) # revealed: Literal[False] -reveal_type(n1 is not n2) # revealed: Literal[False] - -reveal_type(a1 is not n1) # revealed: Literal[True] -reveal_type(n1 is not a1) # revealed: Literal[True] - -reveal_type(a1 is not o) # revealed: bool -reveal_type(n1 is not o) # revealed: bool + reveal_type(a1 is not o) # revealed: bool + reveal_type(n1 is not o) # revealed: bool ``` 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 cffec0bf724a0..c4bad9bbb30a9 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 @@ -312,17 +312,9 @@ reveal_type(1 <= 2j) # revealed: bool reveal_type(1 > 2j) # revealed: bool reveal_type(1 >= 2j) # revealed: bool -def bool_instance() -> bool: - return True - -def int_instance() -> int: - return 42 - -x = bool_instance() -y = int_instance() - -reveal_type(x < y) # revealed: bool -reveal_type(y < x) # revealed: bool -reveal_type(4.2 < x) # revealed: bool -reveal_type(x < 4.2) # revealed: bool +def f(x: bool, y: int): + reveal_type(x < y) # revealed: bool + reveal_type(y < x) # revealed: bool + reveal_type(4.2 < x) # revealed: bool + reveal_type(x < 4.2) # revealed: bool ``` diff --git a/crates/red_knot_python_semantic/resources/mdtest/comparison/integers.md b/crates/red_knot_python_semantic/resources/mdtest/comparison/integers.md index a2092af10983b..a59e1510bf91d 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/comparison/integers.md +++ b/crates/red_knot_python_semantic/resources/mdtest/comparison/integers.md @@ -20,10 +20,8 @@ reveal_type(1 <= "" and 0 < 1) # revealed: bool ```py # TODO: implement lookup of `__eq__` on typeshed `int` stub. -def int_instance() -> int: - return 42 - -reveal_type(1 == int_instance()) # revealed: bool -reveal_type(9 < int_instance()) # revealed: bool -reveal_type(int_instance() < int_instance()) # revealed: bool +def _(a: int, b: int): + reveal_type(1 == a) # revealed: bool + reveal_type(9 < a) # revealed: bool + reveal_type(a < b) # revealed: bool ``` diff --git a/crates/red_knot_python_semantic/resources/mdtest/comparison/intersections.md b/crates/red_knot_python_semantic/resources/mdtest/comparison/intersections.md index 39efa500fa1fe..c8c3ffa977d12 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/comparison/intersections.md +++ b/crates/red_knot_python_semantic/resources/mdtest/comparison/intersections.md @@ -14,21 +14,19 @@ class Child1(Base): class Child2(Base): ... -def get_base() -> Base: ... +def _(x: Base): + c1 = Child1() -x = get_base() -c1 = Child1() + # Create an intersection type through narrowing: + if isinstance(x, Child1): + if isinstance(x, Child2): + reveal_type(x) # revealed: Child1 & Child2 -# Create an intersection type through narrowing: -if isinstance(x, Child1): - if isinstance(x, Child2): - reveal_type(x) # revealed: Child1 & Child2 + reveal_type(x == 1) # revealed: Literal[True] - reveal_type(x == 1) # revealed: Literal[True] - - # Other comparison operators fall back to the base type: - reveal_type(x > 1) # revealed: bool - reveal_type(x is c1) # revealed: bool + # Other comparison operators fall back to the base type: + reveal_type(x > 1) # revealed: bool + reveal_type(x is c1) # revealed: bool ``` ## Negative contributions @@ -73,18 +71,15 @@ if x != "abc": #### Integers ```py -def get_int() -> int: ... - -x = get_int() - -if x != 1: - reveal_type(x) # revealed: int & ~Literal[1] +def _(x: int): + if x != 1: + reveal_type(x) # revealed: int & ~Literal[1] - reveal_type(x != 1) # revealed: Literal[True] - reveal_type(x != 2) # revealed: bool + reveal_type(x != 1) # revealed: Literal[True] + reveal_type(x != 2) # revealed: bool - reveal_type(x == 1) # revealed: Literal[False] - reveal_type(x == 2) # revealed: bool + reveal_type(x == 1) # revealed: Literal[False] + reveal_type(x == 2) # revealed: bool ``` ### Identity comparisons @@ -92,18 +87,15 @@ if x != 1: ```py class A: ... -def get_object() -> object: ... - -o = object() - -a = A() -n = None +def _(o: object): + a = A() + n = None -if o is not None: - reveal_type(o) # revealed: object & ~None + if o is not None: + reveal_type(o) # revealed: object & ~None - reveal_type(o is n) # revealed: Literal[False] - reveal_type(o is not n) # revealed: Literal[True] + reveal_type(o is n) # revealed: Literal[False] + reveal_type(o is not n) # revealed: Literal[True] ``` ## Diagnostics @@ -119,16 +111,13 @@ class Container: class NonContainer: ... -def get_object() -> object: ... +def _(x: object): + if isinstance(x, Container): + if isinstance(x, NonContainer): + reveal_type(x) # revealed: Container & NonContainer -x = get_object() - -if isinstance(x, Container): - if isinstance(x, NonContainer): - reveal_type(x) # revealed: Container & NonContainer - - # error: [unsupported-operator] "Operator `in` is not supported for types `int` and `NonContainer`" - reveal_type(2 in x) # revealed: bool + # error: [unsupported-operator] "Operator `in` is not supported for types `int` and `NonContainer`" + reveal_type(2 in x) # revealed: bool ``` ### Unsupported operators for negative contributions @@ -142,14 +131,11 @@ class Container: class NonContainer: ... -def get_object() -> object: ... - -x = get_object() - -if isinstance(x, Container): - if not isinstance(x, NonContainer): - reveal_type(x) # revealed: Container & ~NonContainer +def _(x: object): + if isinstance(x, Container): + if not isinstance(x, NonContainer): + reveal_type(x) # revealed: Container & ~NonContainer - # No error here! - reveal_type(2 in x) # revealed: bool + # No error here! + reveal_type(2 in x) # revealed: bool ``` diff --git a/crates/red_knot_python_semantic/resources/mdtest/comparison/strings.md b/crates/red_knot_python_semantic/resources/mdtest/comparison/strings.md index 04cfc6771ed21..80015b6a257dc 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/comparison/strings.md +++ b/crates/red_knot_python_semantic/resources/mdtest/comparison/strings.md @@ -3,18 +3,17 @@ ## String literals ```py -def str_instance() -> str: ... +def _(x: str): + reveal_type("abc" == "abc") # revealed: Literal[True] + reveal_type("ab_cd" <= "ab_ce") # revealed: Literal[True] + reveal_type("abc" in "ab cd") # revealed: Literal[False] + reveal_type("" not in "hello") # revealed: Literal[False] + reveal_type("--" is "--") # revealed: bool + reveal_type("A" is "B") # revealed: Literal[False] + reveal_type("--" is not "--") # revealed: bool + reveal_type("A" is not "B") # revealed: Literal[True] + reveal_type(x < "...") # revealed: bool -reveal_type("abc" == "abc") # revealed: Literal[True] -reveal_type("ab_cd" <= "ab_ce") # revealed: Literal[True] -reveal_type("abc" in "ab cd") # revealed: Literal[False] -reveal_type("" not in "hello") # revealed: Literal[False] -reveal_type("--" is "--") # revealed: bool -reveal_type("A" is "B") # revealed: Literal[False] -reveal_type("--" is not "--") # revealed: bool -reveal_type("A" is not "B") # revealed: Literal[True] -reveal_type(str_instance() < "...") # revealed: bool - -# ensure we're not comparing the interned salsa symbols, which compare by order of declaration. -reveal_type("ab" < "ab_cd") # revealed: Literal[True] + # ensure we're not comparing the interned salsa symbols, which compare by order of declaration. + reveal_type("ab" < "ab_cd") # revealed: Literal[True] ``` 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 f21ea7257e6e7..8fe7f29541a9a 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/comparison/tuples.md +++ b/crates/red_knot_python_semantic/resources/mdtest/comparison/tuples.md @@ -58,28 +58,23 @@ reveal_type(c >= d) # revealed: Literal[True] #### Results with Ambiguity ```py -def bool_instance() -> bool: - return True - -def int_instance() -> int: - return 42 - -a = (bool_instance(),) -b = (int_instance(),) - -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: bool -reveal_type(a >= a) # revealed: bool - -reveal_type(a == b) # revealed: bool -reveal_type(a != b) # revealed: bool -reveal_type(a < b) # revealed: bool -reveal_type(a <= b) # revealed: bool -reveal_type(a > b) # revealed: bool -reveal_type(a >= b) # revealed: bool +def _(x: bool, y: int): + a = (x,) + b = (y,) + + 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: bool + reveal_type(a >= a) # revealed: bool + + reveal_type(a == b) # revealed: bool + reveal_type(a != b) # revealed: bool + reveal_type(a < b) # revealed: bool + reveal_type(a <= b) # revealed: bool + reveal_type(a > b) # revealed: bool + reveal_type(a >= b) # revealed: bool ``` #### Comparison Unsupported @@ -197,7 +192,7 @@ reveal_type((A(), B()) < (A(), B())) # revealed: float | set | Literal[False] #### Special Handling of Eq and NotEq in Lexicographic Comparisons -> Example: `(int_instance(), "foo") == (int_instance(), "bar")` +> Example: `(, "foo") == (, "bar")` `Eq` and `NotEq` have unique behavior compared to other operators in lexicographic comparisons. Specifically, for `Eq`, if any non-equal pair exists within the tuples being compared, we can @@ -208,42 +203,38 @@ In contrast, with operators like `<` and `>`, the comparison must consider each sequentially, and the final outcome might remain ambiguous until all pairs are compared. ```py -def str_instance() -> str: - return "hello" - -def int_instance() -> int: - return 42 - -reveal_type("foo" == "bar") # revealed: Literal[False] -reveal_type(("foo",) == ("bar",)) # revealed: Literal[False] -reveal_type((4, "foo") == (4, "bar")) # revealed: Literal[False] -reveal_type((int_instance(), "foo") == (int_instance(), "bar")) # revealed: Literal[False] - -a = (str_instance(), int_instance(), "foo") - -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: bool -reveal_type(a >= a) # revealed: bool - -b = (str_instance(), int_instance(), "bar") - -reveal_type(a == b) # revealed: Literal[False] -reveal_type(a != b) # revealed: Literal[True] -reveal_type(a < b) # revealed: bool -reveal_type(a <= b) # revealed: bool -reveal_type(a > b) # revealed: bool -reveal_type(a >= b) # revealed: bool - -c = (str_instance(), int_instance(), "foo", "different_length") -reveal_type(a == c) # revealed: Literal[False] -reveal_type(a != c) # revealed: Literal[True] -reveal_type(a < c) # revealed: bool -reveal_type(a <= c) # revealed: bool -reveal_type(a > c) # revealed: bool -reveal_type(a >= c) # revealed: bool +def _(x: str, y: int): + reveal_type("foo" == "bar") # revealed: Literal[False] + reveal_type(("foo",) == ("bar",)) # revealed: Literal[False] + reveal_type((4, "foo") == (4, "bar")) # revealed: Literal[False] + reveal_type((y, "foo") == (y, "bar")) # revealed: Literal[False] + + a = (x, y, "foo") + + 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: bool + reveal_type(a >= a) # revealed: bool + + b = (x, y, "bar") + + reveal_type(a == b) # revealed: Literal[False] + reveal_type(a != b) # revealed: Literal[True] + reveal_type(a < b) # revealed: bool + reveal_type(a <= b) # revealed: bool + reveal_type(a > b) # revealed: bool + reveal_type(a >= b) # revealed: bool + + c = (x, y, "foo", "different_length") + + reveal_type(a == c) # revealed: Literal[False] + reveal_type(a != c) # revealed: Literal[True] + reveal_type(a < c) # revealed: bool + reveal_type(a <= c) # revealed: bool + reveal_type(a > c) # revealed: bool + reveal_type(a >= c) # revealed: bool ``` #### Error Propagation @@ -252,42 +243,36 @@ Errors occurring within a tuple comparison should propagate outward. However, if comparison can clearly conclude before encountering an error, the error should not be raised. ```py -def int_instance() -> int: - return 42 - -def str_instance() -> str: - return "hello" - -class A: ... - -# error: [unsupported-operator] "Operator `<` is not supported for types `A` and `A`" -A() < A() -# error: [unsupported-operator] "Operator `<=` is not supported for types `A` and `A`" -A() <= A() -# error: [unsupported-operator] "Operator `>` is not supported for types `A` and `A`" -A() > A() -# error: [unsupported-operator] "Operator `>=` is not supported for types `A` and `A`" -A() >= A() - -a = (0, int_instance(), A()) - -# error: [unsupported-operator] "Operator `<` is not supported for types `A` and `A`, in comparing `tuple[Literal[0], int, A]` with `tuple[Literal[0], int, A]`" -reveal_type(a < a) # revealed: Unknown -# error: [unsupported-operator] "Operator `<=` is not supported for types `A` and `A`, in comparing `tuple[Literal[0], int, A]` with `tuple[Literal[0], int, A]`" -reveal_type(a <= a) # revealed: Unknown -# error: [unsupported-operator] "Operator `>` is not supported for types `A` and `A`, in comparing `tuple[Literal[0], int, A]` with `tuple[Literal[0], int, A]`" -reveal_type(a > a) # revealed: Unknown -# error: [unsupported-operator] "Operator `>=` is not supported for types `A` and `A`, in comparing `tuple[Literal[0], int, A]` with `tuple[Literal[0], int, A]`" -reveal_type(a >= a) # revealed: Unknown - -# Comparison between `a` and `b` should only involve the first elements, `Literal[0]` and `Literal[99999]`, -# and should terminate immediately. -b = (99999, int_instance(), A()) - -reveal_type(a < b) # revealed: Literal[True] -reveal_type(a <= b) # revealed: Literal[True] -reveal_type(a > b) # revealed: Literal[False] -reveal_type(a >= b) # revealed: Literal[False] +def _(n: int, s: str): + class A: ... + # error: [unsupported-operator] "Operator `<` is not supported for types `A` and `A`" + A() < A() + # error: [unsupported-operator] "Operator `<=` is not supported for types `A` and `A`" + A() <= A() + # error: [unsupported-operator] "Operator `>` is not supported for types `A` and `A`" + A() > A() + # error: [unsupported-operator] "Operator `>=` is not supported for types `A` and `A`" + A() >= A() + + a = (0, n, A()) + + # error: [unsupported-operator] "Operator `<` is not supported for types `A` and `A`, in comparing `tuple[Literal[0], int, A]` with `tuple[Literal[0], int, A]`" + reveal_type(a < a) # revealed: Unknown + # error: [unsupported-operator] "Operator `<=` is not supported for types `A` and `A`, in comparing `tuple[Literal[0], int, A]` with `tuple[Literal[0], int, A]`" + reveal_type(a <= a) # revealed: Unknown + # error: [unsupported-operator] "Operator `>` is not supported for types `A` and `A`, in comparing `tuple[Literal[0], int, A]` with `tuple[Literal[0], int, A]`" + reveal_type(a > a) # revealed: Unknown + # error: [unsupported-operator] "Operator `>=` is not supported for types `A` and `A`, in comparing `tuple[Literal[0], int, A]` with `tuple[Literal[0], int, A]`" + reveal_type(a >= a) # revealed: Unknown + + # Comparison between `a` and `b` should only involve the first elements, `Literal[0]` and `Literal[99999]`, + # and should terminate immediately. + b = (99999, n, A()) + + reveal_type(a < b) # revealed: Literal[True] + reveal_type(a <= b) # revealed: Literal[True] + reveal_type(a > b) # revealed: Literal[False] + reveal_type(a >= b) # revealed: Literal[False] ``` ### Membership Test Comparisons @@ -295,22 +280,20 @@ reveal_type(a >= b) # revealed: Literal[False] "Membership Test Comparisons" refers to the operators `in` and `not in`. ```py -def int_instance() -> int: - return 42 - -a = (1, 2) -b = ((3, 4), (1, 2)) -c = ((1, 2, 3), (4, 5, 6)) -d = ((int_instance(), int_instance()), (int_instance(), int_instance())) +def _(n: int): + a = (1, 2) + b = ((3, 4), (1, 2)) + c = ((1, 2, 3), (4, 5, 6)) + d = ((n, n), (n, n)) -reveal_type(a in b) # revealed: Literal[True] -reveal_type(a not in b) # revealed: Literal[False] + reveal_type(a in b) # revealed: Literal[True] + reveal_type(a not in b) # revealed: Literal[False] -reveal_type(a in c) # revealed: Literal[False] -reveal_type(a not in c) # revealed: Literal[True] + reveal_type(a in c) # revealed: Literal[False] + reveal_type(a not in c) # revealed: Literal[True] -reveal_type(a in d) # revealed: bool -reveal_type(a not in d) # revealed: bool + reveal_type(a in d) # revealed: bool + reveal_type(a not in d) # revealed: bool ``` ### Identity Comparisons diff --git a/crates/red_knot_python_semantic/resources/mdtest/comparison/unions.md b/crates/red_knot_python_semantic/resources/mdtest/comparison/unions.md index b6b6c738517db..56924ecc7fad0 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/comparison/unions.md +++ b/crates/red_knot_python_semantic/resources/mdtest/comparison/unions.md @@ -5,49 +5,46 @@ Comparisons on union types need to consider all possible cases: ```py -def bool_instance() -> bool: - return True +def _(flag: bool): + one_or_two = 1 if flag else 2 -flag = bool_instance() -one_or_two = 1 if flag else 2 + reveal_type(one_or_two <= 2) # revealed: Literal[True] + reveal_type(one_or_two <= 1) # revealed: bool + reveal_type(one_or_two <= 0) # revealed: Literal[False] -reveal_type(one_or_two <= 2) # revealed: Literal[True] -reveal_type(one_or_two <= 1) # revealed: bool -reveal_type(one_or_two <= 0) # revealed: Literal[False] + reveal_type(2 >= one_or_two) # revealed: Literal[True] + reveal_type(1 >= one_or_two) # revealed: bool + reveal_type(0 >= one_or_two) # revealed: Literal[False] -reveal_type(2 >= one_or_two) # revealed: Literal[True] -reveal_type(1 >= one_or_two) # revealed: bool -reveal_type(0 >= one_or_two) # revealed: Literal[False] + reveal_type(one_or_two < 1) # revealed: Literal[False] + reveal_type(one_or_two < 2) # revealed: bool + reveal_type(one_or_two < 3) # revealed: Literal[True] -reveal_type(one_or_two < 1) # revealed: Literal[False] -reveal_type(one_or_two < 2) # revealed: bool -reveal_type(one_or_two < 3) # revealed: Literal[True] + reveal_type(one_or_two > 0) # revealed: Literal[True] + reveal_type(one_or_two > 1) # revealed: bool + reveal_type(one_or_two > 2) # revealed: Literal[False] -reveal_type(one_or_two > 0) # revealed: Literal[True] -reveal_type(one_or_two > 1) # revealed: bool -reveal_type(one_or_two > 2) # revealed: Literal[False] + reveal_type(one_or_two == 3) # revealed: Literal[False] + reveal_type(one_or_two == 1) # revealed: bool -reveal_type(one_or_two == 3) # revealed: Literal[False] -reveal_type(one_or_two == 1) # revealed: bool + reveal_type(one_or_two != 3) # revealed: Literal[True] + reveal_type(one_or_two != 1) # revealed: bool -reveal_type(one_or_two != 3) # revealed: Literal[True] -reveal_type(one_or_two != 1) # revealed: bool + a_or_ab = "a" if flag else "ab" -a_or_ab = "a" if flag else "ab" + reveal_type(a_or_ab in "ab") # revealed: Literal[True] + reveal_type("a" in a_or_ab) # revealed: Literal[True] -reveal_type(a_or_ab in "ab") # revealed: Literal[True] -reveal_type("a" in a_or_ab) # revealed: Literal[True] + reveal_type("c" not in a_or_ab) # revealed: Literal[True] + reveal_type("a" not in a_or_ab) # revealed: Literal[False] -reveal_type("c" not in a_or_ab) # revealed: Literal[True] -reveal_type("a" not in a_or_ab) # revealed: Literal[False] + reveal_type("b" in a_or_ab) # revealed: bool + reveal_type("b" not in a_or_ab) # revealed: bool -reveal_type("b" in a_or_ab) # revealed: bool -reveal_type("b" not in a_or_ab) # revealed: bool + one_or_none = 1 if flag else None -one_or_none = 1 if flag else None - -reveal_type(one_or_none is None) # revealed: bool -reveal_type(one_or_none is not None) # revealed: bool + reveal_type(one_or_none is None) # revealed: bool + reveal_type(one_or_none is not None) # revealed: bool ``` ## Union on both sides of the comparison @@ -56,18 +53,15 @@ With unions on both sides, we need to consider the full cross product of options resulting (union) type: ```py -def bool_instance() -> bool: - return True - -flag_s, flag_l = bool_instance(), bool_instance() -small = 1 if flag_s else 2 -large = 2 if flag_l else 3 +def _(flag_s: bool, flag_l: bool): + small = 1 if flag_s else 2 + large = 2 if flag_l else 3 -reveal_type(small <= large) # revealed: Literal[True] -reveal_type(small >= large) # revealed: bool + reveal_type(small <= large) # revealed: Literal[True] + reveal_type(small >= large) # revealed: bool -reveal_type(small < large) # revealed: bool -reveal_type(small > large) # revealed: Literal[False] + reveal_type(small < large) # revealed: bool + reveal_type(small > large) # revealed: Literal[False] ``` ## Unsupported operations @@ -77,12 +71,9 @@ back to `bool` for the result type instead of trying to infer something more pre (supported) variants: ```py -def bool_instance() -> bool: - return True - -flag = bool_instance() -x = [1, 2] if flag else 1 +def _(flag: bool): + x = [1, 2] if flag else 1 -result = 1 in x # error: "Operator `in` is not supported" -reveal_type(result) # revealed: bool + result = 1 in x # error: "Operator `in` is not supported" + reveal_type(result) # revealed: bool ``` diff --git a/crates/red_knot_python_semantic/resources/mdtest/comparison/unsupported.md b/crates/red_knot_python_semantic/resources/mdtest/comparison/unsupported.md index 472cac5073193..5dc769ac48020 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/comparison/unsupported.md +++ b/crates/red_knot_python_semantic/resources/mdtest/comparison/unsupported.md @@ -1,42 +1,38 @@ # Comparison: Unsupported operators ```py -def bool_instance() -> bool: - return True - -class A: ... - -a = 1 in 7 # error: "Operator `in` is not supported for types `Literal[1]` and `Literal[7]`" -reveal_type(a) # revealed: bool - -b = 0 not in 10 # error: "Operator `not in` is not supported for types `Literal[0]` and `Literal[10]`" -reveal_type(b) # revealed: bool - -# TODO: should error, once operand type check is implemented -# ("Operator `<` is not supported for types `object` and `int`") -c = object() < 5 -# TODO: should be Unknown, once operand type check is implemented -reveal_type(c) # revealed: bool - -# TODO: should error, once operand type check is implemented -# ("Operator `<` is not supported for types `int` and `object`") -d = 5 < object() -# TODO: should be Unknown, once operand type check is implemented -reveal_type(d) # revealed: bool - -flag = bool_instance() -int_literal_or_str_literal = 1 if flag else "foo" -# error: "Operator `in` is not supported for types `Literal[42]` and `Literal[1]`, in comparing `Literal[42]` with `Literal[1] | Literal["foo"]`" -e = 42 in int_literal_or_str_literal -reveal_type(e) # revealed: bool - -# TODO: should error, need to check if __lt__ signature is valid for right operand -# error may be "Operator `<` is not supported for types `int` and `str`, in comparing `tuple[Literal[1], Literal[2]]` with `tuple[Literal[1], Literal["hello"]]` -f = (1, 2) < (1, "hello") -# TODO: should be Unknown, once operand type check is implemented -reveal_type(f) # revealed: bool - -# error: [unsupported-operator] "Operator `<` is not supported for types `A` and `A`, in comparing `tuple[bool, A]` with `tuple[bool, A]`" -g = (bool_instance(), A()) < (bool_instance(), A()) -reveal_type(g) # revealed: Unknown +def _(flag: bool, flag1: bool, flag2: bool): + class A: ... + a = 1 in 7 # error: "Operator `in` is not supported for types `Literal[1]` and `Literal[7]`" + reveal_type(a) # revealed: bool + + b = 0 not in 10 # error: "Operator `not in` is not supported for types `Literal[0]` and `Literal[10]`" + reveal_type(b) # revealed: bool + + # TODO: should error, once operand type check is implemented + # ("Operator `<` is not supported for types `object` and `int`") + c = object() < 5 + # TODO: should be Unknown, once operand type check is implemented + reveal_type(c) # revealed: bool + + # TODO: should error, once operand type check is implemented + # ("Operator `<` is not supported for types `int` and `object`") + d = 5 < object() + # TODO: should be Unknown, once operand type check is implemented + reveal_type(d) # revealed: bool + + int_literal_or_str_literal = 1 if flag else "foo" + # error: "Operator `in` is not supported for types `Literal[42]` and `Literal[1]`, in comparing `Literal[42]` with `Literal[1] | Literal["foo"]`" + e = 42 in int_literal_or_str_literal + reveal_type(e) # revealed: bool + + # TODO: should error, need to check if __lt__ signature is valid for right operand + # error may be "Operator `<` is not supported for types `int` and `str`, in comparing `tuple[Literal[1], Literal[2]]` with `tuple[Literal[1], Literal["hello"]]` + f = (1, 2) < (1, "hello") + # TODO: should be Unknown, once operand type check is implemented + reveal_type(f) # revealed: bool + + # error: [unsupported-operator] "Operator `<` is not supported for types `A` and `A`, in comparing `tuple[bool, A]` with `tuple[bool, A]`" + g = (flag1, A()) < (flag2, A()) + reveal_type(g) # revealed: Unknown ``` diff --git a/crates/red_knot_python_semantic/resources/mdtest/conditional/if_expression.md b/crates/red_knot_python_semantic/resources/mdtest/conditional/if_expression.md index f162a3a39d704..d9ef65b8f325b 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/conditional/if_expression.md +++ b/crates/red_knot_python_semantic/resources/mdtest/conditional/if_expression.md @@ -3,47 +3,35 @@ ## Simple if-expression ```py -def bool_instance() -> bool: - return True - -flag = bool_instance() -x = 1 if flag else 2 -reveal_type(x) # revealed: Literal[1, 2] +def _(flag: bool): + x = 1 if flag else 2 + reveal_type(x) # revealed: Literal[1, 2] ``` ## If-expression with walrus operator ```py -def bool_instance() -> bool: - return True - -flag = bool_instance() -y = 0 -z = 0 -x = (y := 1) if flag else (z := 2) -reveal_type(x) # revealed: Literal[1, 2] -reveal_type(y) # revealed: Literal[0, 1] -reveal_type(z) # revealed: Literal[0, 2] +def _(flag: bool): + y = 0 + z = 0 + x = (y := 1) if flag else (z := 2) + reveal_type(x) # revealed: Literal[1, 2] + reveal_type(y) # revealed: Literal[0, 1] + reveal_type(z) # revealed: Literal[0, 2] ``` ## Nested if-expression ```py -def bool_instance() -> bool: - return True - -flag, flag2 = bool_instance(), bool_instance() -x = 1 if flag else 2 if flag2 else 3 -reveal_type(x) # revealed: Literal[1, 2, 3] +def _(flag: bool, flag2: bool): + x = 1 if flag else 2 if flag2 else 3 + reveal_type(x) # revealed: Literal[1, 2, 3] ``` ## None ```py -def bool_instance() -> bool: - return True - -flag = bool_instance() -x = 1 if flag else None -reveal_type(x) # revealed: Literal[1] | None +def _(flag: bool): + x = 1 if flag else None + reveal_type(x) # revealed: Literal[1] | None ``` diff --git a/crates/red_knot_python_semantic/resources/mdtest/conditional/if_statement.md b/crates/red_knot_python_semantic/resources/mdtest/conditional/if_statement.md index 1e3ec4ea50e53..b436a739a1141 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/conditional/if_statement.md +++ b/crates/red_knot_python_semantic/resources/mdtest/conditional/if_statement.md @@ -3,128 +3,115 @@ ## Simple if ```py -def bool_instance() -> bool: - return True - -flag = bool_instance() -y = 1 -y = 2 +def _(flag: bool): + y = 1 + y = 2 -if flag: - y = 3 + if flag: + y = 3 -reveal_type(y) # revealed: Literal[2, 3] + reveal_type(y) # revealed: Literal[2, 3] ``` ## Simple if-elif-else ```py -def bool_instance() -> bool: - return True - -flag, flag2 = bool_instance(), bool_instance() -y = 1 -y = 2 -if flag: - y = 3 -elif flag2: - y = 4 -else: - r = y - y = 5 - s = y -x = y - -reveal_type(x) # revealed: Literal[3, 4, 5] - -# revealed: Literal[2] -# error: [possibly-unresolved-reference] -reveal_type(r) - -# revealed: Literal[5] -# error: [possibly-unresolved-reference] -reveal_type(s) +def _(flag: bool, flag2: bool): + y = 1 + y = 2 + + if flag: + y = 3 + elif flag2: + y = 4 + else: + r = y + y = 5 + s = y + x = y + + reveal_type(x) # revealed: Literal[3, 4, 5] + + # revealed: Literal[2] + # error: [possibly-unresolved-reference] + reveal_type(r) + + # revealed: Literal[5] + # error: [possibly-unresolved-reference] + reveal_type(s) ``` ## Single symbol across if-elif-else ```py -def bool_instance() -> bool: - return True - -flag, flag2 = bool_instance(), bool_instance() +def _(flag: bool, flag2: bool): + if flag: + y = 1 + elif flag2: + y = 2 + else: + y = 3 -if flag: - y = 1 -elif flag2: - y = 2 -else: - y = 3 -reveal_type(y) # revealed: Literal[1, 2, 3] + reveal_type(y) # revealed: Literal[1, 2, 3] ``` ## if-elif-else without else assignment ```py -def bool_instance() -> bool: - return True +def _(flag: bool, flag2: bool): + y = 0 -flag, flag2 = bool_instance(), bool_instance() -y = 0 -if flag: - y = 1 -elif flag2: - y = 2 -else: - pass -reveal_type(y) # revealed: Literal[0, 1, 2] + if flag: + y = 1 + elif flag2: + y = 2 + else: + pass + + reveal_type(y) # revealed: Literal[0, 1, 2] ``` ## if-elif-else with intervening assignment ```py -def bool_instance() -> bool: - return True +def _(flag: bool, flag2: bool): + y = 0 -flag, flag2 = bool_instance(), bool_instance() -y = 0 -if flag: - y = 1 - z = 3 -elif flag2: - y = 2 -else: - pass -reveal_type(y) # revealed: Literal[0, 1, 2] + if flag: + y = 1 + z = 3 + elif flag2: + y = 2 + else: + pass + + reveal_type(y) # revealed: Literal[0, 1, 2] ``` ## Nested if statement ```py -def bool_instance() -> bool: - return True +def _(flag: bool, flag2: bool): + y = 0 -flag, flag2 = bool_instance(), bool_instance() -y = 0 -if flag: - if flag2: - y = 1 -reveal_type(y) # revealed: Literal[0, 1] + if flag: + if flag2: + y = 1 + + reveal_type(y) # revealed: Literal[0, 1] ``` ## if-elif without else ```py -def bool_instance() -> bool: - return True - -flag, flag2 = bool_instance(), bool_instance() -y = 1 -y = 2 -if flag: - y = 3 -elif flag2: - y = 4 - -reveal_type(y) # revealed: Literal[2, 3, 4] +def _(flag: bool, flag2: bool): + y = 1 + y = 2 + + if flag: + y = 3 + elif flag2: + y = 4 + + reveal_type(y) # revealed: Literal[2, 3, 4] ``` diff --git a/crates/red_knot_python_semantic/resources/mdtest/conditional/match.md b/crates/red_knot_python_semantic/resources/mdtest/conditional/match.md index a173354b4cf7c..81dd93e738ade 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/conditional/match.md +++ b/crates/red_knot_python_semantic/resources/mdtest/conditional/match.md @@ -31,6 +31,7 @@ reveal_type(y) ```py y = 1 y = 2 + match 0: case 1: y = 3 diff --git a/crates/red_knot_python_semantic/resources/mdtest/declaration/error.md b/crates/red_knot_python_semantic/resources/mdtest/declaration/error.md index 19fbfa20a5459..f6cb268312292 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/declaration/error.md +++ b/crates/red_knot_python_semantic/resources/mdtest/declaration/error.md @@ -10,42 +10,35 @@ x: str # error: [invalid-declaration] "Cannot declare type `str` for inferred t ## Incompatible declarations ```py -def bool_instance() -> bool: - return True - -flag = bool_instance() -if flag: - x: str -else: - x: int -x = 1 # error: [conflicting-declarations] "Conflicting declared types for `x`: str, int" +def _(flag: bool): + if flag: + x: str + else: + x: int + + x = 1 # error: [conflicting-declarations] "Conflicting declared types for `x`: str, int" ``` ## Partial declarations ```py -def bool_instance() -> bool: - return True +def _(flag: bool): + if flag: + x: int -flag = bool_instance() -if flag: - x: int -x = 1 # error: [conflicting-declarations] "Conflicting declared types for `x`: Unknown, int" + x = 1 # error: [conflicting-declarations] "Conflicting declared types for `x`: Unknown, int" ``` ## Incompatible declarations with bad assignment ```py -def bool_instance() -> bool: - return True - -flag = bool_instance() -if flag: - x: str -else: - x: int - -# error: [conflicting-declarations] -# error: [invalid-assignment] -x = b"foo" +def _(flag: bool): + if flag: + x: str + else: + x: int + + # error: [conflicting-declarations] + # error: [invalid-assignment] + x = b"foo" ``` diff --git a/crates/red_knot_python_semantic/resources/mdtest/exception/invalid_syntax.md b/crates/red_knot_python_semantic/resources/mdtest/exception/invalid_syntax.md index fcf1780f1d710..19bb7c43772f1 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/exception/invalid_syntax.md +++ b/crates/red_knot_python_semantic/resources/mdtest/exception/invalid_syntax.md @@ -9,5 +9,4 @@ try: print except as e: # error: [invalid-syntax] reveal_type(e) # revealed: Unknown - ``` diff --git a/crates/red_knot_python_semantic/resources/mdtest/expression/attribute.md b/crates/red_knot_python_semantic/resources/mdtest/expression/attribute.md index 06e3cb9e504ab..66f71e8dad398 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/expression/attribute.md +++ b/crates/red_knot_python_semantic/resources/mdtest/expression/attribute.md @@ -3,26 +3,25 @@ ## Boundness ```py -def flag() -> bool: ... +def _(flag: bool): + class A: + always_bound = 1 -class A: - always_bound = 1 + if flag: + union = 1 + else: + union = "abc" - if flag(): - union = 1 - else: - union = "abc" + if flag: + possibly_unbound = "abc" - if flag(): - possibly_unbound = "abc" + reveal_type(A.always_bound) # revealed: Literal[1] -reveal_type(A.always_bound) # revealed: Literal[1] + reveal_type(A.union) # revealed: Literal[1] | Literal["abc"] -reveal_type(A.union) # revealed: Literal[1] | Literal["abc"] + # error: [possibly-unbound-attribute] "Attribute `possibly_unbound` on type `Literal[A]` is possibly unbound" + reveal_type(A.possibly_unbound) # revealed: Literal["abc"] -# error: [possibly-unbound-attribute] "Attribute `possibly_unbound` on type `Literal[A]` is possibly unbound" -reveal_type(A.possibly_unbound) # revealed: Literal["abc"] - -# error: [unresolved-attribute] "Type `Literal[A]` has no attribute `non_existent`" -reveal_type(A.non_existent) # revealed: Unknown + # error: [unresolved-attribute] "Type `Literal[A]` has no attribute `non_existent`" + reveal_type(A.non_existent) # revealed: Unknown ``` diff --git a/crates/red_knot_python_semantic/resources/mdtest/expression/boolean.md b/crates/red_knot_python_semantic/resources/mdtest/expression/boolean.md index a84017ba709ee..7ce689164248a 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/expression/boolean.md +++ b/crates/red_knot_python_semantic/resources/mdtest/expression/boolean.md @@ -3,54 +3,45 @@ ## OR ```py -def foo() -> str: - pass - -reveal_type(True or False) # revealed: Literal[True] -reveal_type("x" or "y" or "z") # revealed: Literal["x"] -reveal_type("" or "y" or "z") # revealed: Literal["y"] -reveal_type(False or "z") # revealed: Literal["z"] -reveal_type(False or True) # revealed: Literal[True] -reveal_type(False or False) # revealed: Literal[False] -reveal_type(foo() or False) # revealed: str | Literal[False] -reveal_type(foo() or True) # revealed: str | Literal[True] +def _(foo: str): + reveal_type(True or False) # revealed: Literal[True] + reveal_type("x" or "y" or "z") # revealed: Literal["x"] + reveal_type("" or "y" or "z") # revealed: Literal["y"] + reveal_type(False or "z") # revealed: Literal["z"] + reveal_type(False or True) # revealed: Literal[True] + reveal_type(False or False) # revealed: Literal[False] + reveal_type(foo or False) # revealed: str | Literal[False] + reveal_type(foo or True) # revealed: str | Literal[True] ``` ## AND ```py -def foo() -> str: - pass - -reveal_type(True and False) # revealed: Literal[False] -reveal_type(False and True) # revealed: Literal[False] -reveal_type(foo() and False) # revealed: str | Literal[False] -reveal_type(foo() and True) # revealed: str | Literal[True] -reveal_type("x" and "y" and "z") # revealed: Literal["z"] -reveal_type("x" and "y" and "") # revealed: Literal[""] -reveal_type("" and "y") # revealed: Literal[""] +def _(foo: str): + reveal_type(True and False) # revealed: Literal[False] + reveal_type(False and True) # revealed: Literal[False] + reveal_type(foo and False) # revealed: str | Literal[False] + reveal_type(foo and True) # revealed: str | Literal[True] + reveal_type("x" and "y" and "z") # revealed: Literal["z"] + reveal_type("x" and "y" and "") # revealed: Literal[""] + reveal_type("" and "y") # revealed: Literal[""] ``` ## Simple function calls to bool ```py -def returns_bool() -> bool: - return True - -if returns_bool(): - x = True -else: - x = False +def _(flag: bool): + if flag: + x = True + else: + x = False -reveal_type(x) # revealed: bool + reveal_type(x) # revealed: bool ``` ## Complex ```py -def foo() -> str: - pass - reveal_type("x" and "y" or "z") # revealed: Literal["y"] reveal_type("x" or "y" and "z") # revealed: Literal["x"] reveal_type("" and "y" or "z") # revealed: Literal["z"] diff --git a/crates/red_knot_python_semantic/resources/mdtest/expression/if.md b/crates/red_knot_python_semantic/resources/mdtest/expression/if.md index d3f9eef48f124..79faa45426855 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/expression/if.md +++ b/crates/red_knot_python_semantic/resources/mdtest/expression/if.md @@ -3,10 +3,8 @@ ## Union ```py -def bool_instance() -> bool: - return True - -reveal_type(1 if bool_instance() else 2) # revealed: Literal[1, 2] +def _(flag: bool): + reveal_type(1 if flag else 2) # revealed: Literal[1, 2] ``` ## Statically known branches @@ -30,14 +28,12 @@ reveal_type(1 if 0 else 2) # revealed: Literal[2] The test inside an if expression should not affect code outside of the expression. ```py -def bool_instance() -> bool: - return True - -x: Literal[42, "hello"] = 42 if bool_instance() else "hello" +def _(flag: bool): + x: Literal[42, "hello"] = 42 if flag else "hello" -reveal_type(x) # revealed: Literal[42] | Literal["hello"] + reveal_type(x) # revealed: Literal[42] | Literal["hello"] -_ = ... if isinstance(x, str) else ... + _ = ... if isinstance(x, str) else ... -reveal_type(x) # revealed: Literal[42] | Literal["hello"] + reveal_type(x) # revealed: Literal[42] | Literal["hello"] ``` diff --git a/crates/red_knot_python_semantic/resources/mdtest/expression/len.md b/crates/red_knot_python_semantic/resources/mdtest/expression/len.md index 04f6efff5fc32..3418adc77be47 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/expression/len.md +++ b/crates/red_knot_python_semantic/resources/mdtest/expression/len.md @@ -211,8 +211,7 @@ reveal_type(len(SecondRequiredArgument())) # revealed: Literal[1] ### No `__len__` ```py -class NoDunderLen: - pass +class NoDunderLen: ... # TODO: Emit a diagnostic reveal_type(len(NoDunderLen())) # revealed: int diff --git a/crates/red_knot_python_semantic/resources/mdtest/import/conditional.md b/crates/red_knot_python_semantic/resources/mdtest/import/conditional.md index 23056cbf80599..79686f8e74676 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/import/conditional.md +++ b/crates/red_knot_python_semantic/resources/mdtest/import/conditional.md @@ -3,11 +3,10 @@ ## Maybe unbound ```py path=maybe_unbound.py -def bool_instance() -> bool: +def coinflip() -> bool: return True -flag = bool_instance() -if flag: +if coinflip(): y = 3 x = y # error: [possibly-unresolved-reference] @@ -31,13 +30,12 @@ reveal_type(y) # revealed: Literal[3] ## Maybe unbound annotated ```py path=maybe_unbound_annotated.py -def bool_instance() -> bool: +def coinflip() -> bool: return True -flag = bool_instance() - -if flag: +if coinflip(): y: int = 3 + x = y # error: [possibly-unresolved-reference] # revealed: Literal[3] @@ -63,10 +61,10 @@ reveal_type(y) # revealed: int Importing a possibly undeclared name still gives us its declared type: ```py path=maybe_undeclared.py -def bool_instance() -> bool: +def coinflip() -> bool: return True -if bool_instance(): +if coinflip(): x: int ``` @@ -83,14 +81,12 @@ def f(): ... ``` ```py path=b.py -def bool_instance() -> bool: +def coinflip() -> bool: return True -flag = bool_instance() -if flag: +if coinflip(): from c import f else: - def f(): ... ``` @@ -111,11 +107,10 @@ x: int ``` ```py path=b.py -def bool_instance() -> bool: +def coinflip() -> bool: return True -flag = bool_instance() -if flag: +if coinflip(): from c import x else: x = 1 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 09a9bc81873d0..58675475abe72 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/loops/for.md +++ b/crates/red_knot_python_semantic/resources/mdtest/loops/for.md @@ -106,23 +106,19 @@ reveal_type(x) ## With non-callable iterator ```py -def bool_instance() -> bool: - return True - -flag = bool_instance() - -class NotIterable: - if flag: - __iter__ = 1 - else: - __iter__ = None - -for x in NotIterable(): # error: "Object of type `NotIterable` is not iterable" - pass - -# revealed: Unknown -# error: [possibly-unresolved-reference] -reveal_type(x) +def _(flag: bool): + class NotIterable: + if flag: + __iter__ = 1 + else: + __iter__ = None + + for x in NotIterable(): # error: "Object of type `NotIterable` is not iterable" + pass + + # revealed: Unknown + # error: [possibly-unresolved-reference] + reveal_type(x) ``` ## Invalid iterable @@ -160,13 +156,9 @@ class Test2: def __iter__(self) -> TestIter: return TestIter() -def bool_instance() -> bool: - return True - -flag = bool_instance() - -for x in Test() if flag else Test2(): - reveal_type(x) # revealed: int +def _(flag: bool): + for x in Test() if flag else Test2(): + reveal_type(x) # revealed: int ``` ## Union type as iterator @@ -215,13 +207,9 @@ class Test2: def __iter__(self) -> TestIter3 | TestIter4: return TestIter3() -def bool_instance() -> bool: - return True - -flag = bool_instance() - -for x in Test() if flag else Test2(): - reveal_type(x) # revealed: int | Exception | str | tuple[int, int] | bytes | memoryview +def _(flag: bool): + for x in Test() if flag else Test2(): + reveal_type(x) # revealed: int | Exception | str | tuple[int, int] | bytes | memoryview ``` ## Union type as iterable where one union element has no `__iter__` method @@ -235,12 +223,10 @@ class Test: def __iter__(self) -> TestIter: return TestIter() -def coinflip() -> bool: - return True - -# error: [not-iterable] "Object of type `Test | Literal[42]` is not iterable because its `__iter__` method is possibly unbound" -for x in Test() if coinflip() else 42: - reveal_type(x) # revealed: int +def _(flag: bool): + # error: [not-iterable] "Object of type `Test | Literal[42]` is not iterable because its `__iter__` method is possibly unbound" + for x in Test() if flag else 42: + reveal_type(x) # revealed: int ``` ## Union type as iterable where one union element has invalid `__iter__` method @@ -258,12 +244,10 @@ class Test2: def __iter__(self) -> int: return 42 -def coinflip() -> bool: - return True - -# error: "Object of type `Test | Test2` is not iterable" -for x in Test() if coinflip() else Test2(): - reveal_type(x) # revealed: Unknown +def _(flag: bool): + # error: "Object of type `Test | Test2` is not iterable" + for x in Test() if flag else Test2(): + reveal_type(x) # revealed: Unknown ``` ## Union type as iterator where one union element has no `__next__` method diff --git a/crates/red_knot_python_semantic/resources/mdtest/loops/while_loop.md b/crates/red_knot_python_semantic/resources/mdtest/loops/while_loop.md index a03c2cf83fa5c..28c44df393d88 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/loops/while_loop.md +++ b/crates/red_knot_python_semantic/resources/mdtest/loops/while_loop.md @@ -3,54 +3,45 @@ ## Basic While Loop ```py -def bool_instance() -> bool: - return True - -flag = bool_instance() -x = 1 -while flag: - x = 2 +def _(flag: bool): + x = 1 + while flag: + x = 2 -reveal_type(x) # revealed: Literal[1, 2] + reveal_type(x) # revealed: Literal[1, 2] ``` ## While with else (no break) ```py -def bool_instance() -> bool: - return True - -flag = bool_instance() -x = 1 -while flag: - x = 2 -else: - reveal_type(x) # revealed: Literal[1, 2] - x = 3 +def _(flag: bool): + x = 1 + while flag: + x = 2 + else: + reveal_type(x) # revealed: Literal[1, 2] + x = 3 -reveal_type(x) # revealed: Literal[3] + reveal_type(x) # revealed: Literal[3] ``` ## While with Else (may break) ```py -def bool_instance() -> bool: - return True - -flag, flag2 = bool_instance(), bool_instance() -x = 1 -y = 0 -while flag: - x = 2 - if flag2: - y = 4 - break -else: - y = x - x = 3 +def _(flag: bool, flag2: bool): + x = 1 + y = 0 + while flag: + x = 2 + if flag2: + y = 4 + break + else: + y = x + x = 3 -reveal_type(x) # revealed: Literal[2, 3] -reveal_type(y) # revealed: Literal[1, 2, 4] + reveal_type(x) # revealed: Literal[2, 3] + reveal_type(y) # revealed: Literal[1, 2, 4] ``` ## Nested while loops diff --git a/crates/red_knot_python_semantic/resources/mdtest/narrow/bool-call.md b/crates/red_knot_python_semantic/resources/mdtest/narrow/bool-call.md index d7ae47b4fdd07..9bf3007e91fe0 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/narrow/bool-call.md +++ b/crates/red_knot_python_semantic/resources/mdtest/narrow/bool-call.md @@ -1,32 +1,32 @@ ## Narrowing for `bool(..)` checks ```py -def flag() -> bool: ... +def _(flag: bool): -x = 1 if flag() else None + x = 1 if flag else None -# valid invocation, positive -reveal_type(x) # revealed: Literal[1] | None -if bool(x is not None): - reveal_type(x) # revealed: Literal[1] + # valid invocation, positive + reveal_type(x) # revealed: Literal[1] | None + if bool(x is not None): + reveal_type(x) # revealed: Literal[1] -# valid invocation, negative -reveal_type(x) # revealed: Literal[1] | None -if not bool(x is not None): - reveal_type(x) # revealed: None + # valid invocation, negative + reveal_type(x) # revealed: Literal[1] | None + if not bool(x is not None): + reveal_type(x) # revealed: None -# no args/narrowing -reveal_type(x) # revealed: Literal[1] | None -if not bool(): + # no args/narrowing reveal_type(x) # revealed: Literal[1] | None + if not bool(): + reveal_type(x) # revealed: Literal[1] | None -# invalid invocation, too many positional args -reveal_type(x) # revealed: Literal[1] | None -if bool(x is not None, 5): # TODO diagnostic + # invalid invocation, too many positional args reveal_type(x) # revealed: Literal[1] | None + if bool(x is not None, 5): # TODO diagnostic + reveal_type(x) # revealed: Literal[1] | None -# invalid invocation, too many kwargs -reveal_type(x) # revealed: Literal[1] | None -if bool(x is not None, y=5): # TODO diagnostic + # invalid invocation, too many kwargs reveal_type(x) # revealed: Literal[1] | None + if bool(x is not None, y=5): # TODO diagnostic + reveal_type(x) # revealed: Literal[1] | None ``` diff --git a/crates/red_knot_python_semantic/resources/mdtest/narrow/boolean.md b/crates/red_knot_python_semantic/resources/mdtest/narrow/boolean.md index 26b63f0840bd2..48553faf0c795 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/narrow/boolean.md +++ b/crates/red_knot_python_semantic/resources/mdtest/narrow/boolean.md @@ -9,85 +9,67 @@ Similarly, in `and` expressions, the right-hand side is evaluated only if the le ## Narrowing in `or` ```py -def bool_instance() -> bool: - return True +def _(flag: bool): + class A: ... + x: A | None = A() if flag else None -class A: ... - -x: A | None = A() if bool_instance() else None - -isinstance(x, A) or reveal_type(x) # revealed: None -x is None or reveal_type(x) # revealed: A -reveal_type(x) # revealed: A | None + isinstance(x, A) or reveal_type(x) # revealed: None + x is None or reveal_type(x) # revealed: A + reveal_type(x) # revealed: A | None ``` ## Narrowing in `and` ```py -def bool_instance() -> bool: - return True - -class A: ... +def _(flag: bool): + class A: ... + x: A | None = A() if flag else None -x: A | None = A() if bool_instance() else None - -isinstance(x, A) and reveal_type(x) # revealed: A -x is None and reveal_type(x) # revealed: None -reveal_type(x) # revealed: A | None + isinstance(x, A) and reveal_type(x) # revealed: A + x is None and reveal_type(x) # revealed: None + reveal_type(x) # revealed: A | None ``` ## Multiple `and` arms ```py -def bool_instance() -> bool: - return True - -class A: ... - -x: A | None = A() if bool_instance() else None +def _(flag1: bool, flag2: bool, flag3: bool, flag4: bool): + class A: ... + x: A | None = A() if flag1 else None -bool_instance() and isinstance(x, A) and reveal_type(x) # revealed: A -isinstance(x, A) and bool_instance() and reveal_type(x) # revealed: A -reveal_type(x) and isinstance(x, A) and bool_instance() # revealed: A | None + flag2 and isinstance(x, A) and reveal_type(x) # revealed: A + isinstance(x, A) and flag2 and reveal_type(x) # revealed: A + reveal_type(x) and isinstance(x, A) and flag3 # revealed: A | None ``` ## Multiple `or` arms ```py -def bool_instance() -> bool: - return True +def _(flag1: bool, flag2: bool, flag3: bool, flag4: bool): + class A: ... + x: A | None = A() if flag1 else None -class A: ... - -x: A | None = A() if bool_instance() else None - -bool_instance() or isinstance(x, A) or reveal_type(x) # revealed: None -isinstance(x, A) or bool_instance() or reveal_type(x) # revealed: None -reveal_type(x) or isinstance(x, A) or bool_instance() # revealed: A | None + flag2 or isinstance(x, A) or reveal_type(x) # revealed: None + isinstance(x, A) or flag3 or reveal_type(x) # revealed: None + reveal_type(x) or isinstance(x, A) or flag4 # revealed: A | None ``` ## Multiple predicates ```py -def bool_instance() -> bool: - return True - -class A: ... +def _(flag1: bool, flag2: bool): + class A: ... + x: A | None | Literal[1] = A() if flag1 else None if flag2 else 1 -x: A | None | Literal[1] = A() if bool_instance() else None if bool_instance() else 1 - -x is None or isinstance(x, A) or reveal_type(x) # revealed: Literal[1] + x is None or isinstance(x, A) or reveal_type(x) # revealed: Literal[1] ``` ## Mix of `and` and `or` ```py -def bool_instance() -> bool: - return True - -class A: ... - -x: A | None | Literal[1] = A() if bool_instance() else None if bool_instance() else 1 +def _(flag1: bool, flag2: bool): + class A: ... + x: A | None | Literal[1] = A() if flag1 else None if flag2 else 1 -isinstance(x, A) or x is not None and reveal_type(x) # revealed: Literal[1] + isinstance(x, A) or x is not None and reveal_type(x) # revealed: Literal[1] ``` diff --git a/crates/red_knot_python_semantic/resources/mdtest/narrow/conditionals/boolean.md b/crates/red_knot_python_semantic/resources/mdtest/narrow/conditionals/boolean.md index 854e09ff0a50b..c0e1af2f3dd9a 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/narrow/conditionals/boolean.md +++ b/crates/red_knot_python_semantic/resources/mdtest/narrow/conditionals/boolean.md @@ -6,15 +6,11 @@ class A: ... class B: ... -def instance() -> A | B: - return A() - -x = instance() - -if isinstance(x, A) and isinstance(x, B): - reveal_type(x) # revealed: A & B -else: - reveal_type(x) # revealed: B & ~A | A & ~B +def _(x: A | B): + if isinstance(x, A) and isinstance(x, B): + reveal_type(x) # revealed: A & B + else: + reveal_type(x) # revealed: B & ~A | A & ~B ``` ## Arms might not add narrowing constraints @@ -23,25 +19,18 @@ else: class A: ... class B: ... -def bool_instance() -> bool: - return True +def _(flag: bool, x: A | B): + if isinstance(x, A) and flag: + reveal_type(x) # revealed: A + else: + reveal_type(x) # revealed: A | B -def instance() -> A | B: - return A() + if flag and isinstance(x, A): + reveal_type(x) # revealed: A + else: + reveal_type(x) # revealed: A | B -x = instance() - -if isinstance(x, A) and bool_instance(): - reveal_type(x) # revealed: A -else: reveal_type(x) # revealed: A | B - -if bool_instance() and isinstance(x, A): - reveal_type(x) # revealed: A -else: - reveal_type(x) # revealed: A | B - -reveal_type(x) # revealed: A | B ``` ## Statically known arms @@ -50,39 +39,35 @@ reveal_type(x) # revealed: A | B class A: ... class B: ... -def instance() -> A | B: - return A() - -x = instance() +def _(x: A | B): + if isinstance(x, A) and True: + reveal_type(x) # revealed: A + else: + reveal_type(x) # revealed: B & ~A + + if True and isinstance(x, A): + reveal_type(x) # revealed: A + else: + reveal_type(x) # revealed: B & ~A + + if False and isinstance(x, A): + # TODO: should emit an `unreachable code` diagnostic + reveal_type(x) # revealed: A + else: + reveal_type(x) # revealed: A | B + + if False or isinstance(x, A): + reveal_type(x) # revealed: A + else: + reveal_type(x) # revealed: B & ~A + + if True or isinstance(x, A): + reveal_type(x) # revealed: A | B + else: + # TODO: should emit an `unreachable code` diagnostic + reveal_type(x) # revealed: B & ~A -if isinstance(x, A) and True: - reveal_type(x) # revealed: A -else: - reveal_type(x) # revealed: B & ~A - -if True and isinstance(x, A): - reveal_type(x) # revealed: A -else: - reveal_type(x) # revealed: B & ~A - -if False and isinstance(x, A): - # TODO: should emit an `unreachable code` diagnostic - reveal_type(x) # revealed: A -else: reveal_type(x) # revealed: A | B - -if False or isinstance(x, A): - reveal_type(x) # revealed: A -else: - reveal_type(x) # revealed: B & ~A - -if True or isinstance(x, A): - reveal_type(x) # revealed: A | B -else: - # TODO: should emit an `unreachable code` diagnostic - reveal_type(x) # revealed: B & ~A - -reveal_type(x) # revealed: A | B ``` ## The type of multiple symbols can be narrowed down @@ -91,22 +76,17 @@ reveal_type(x) # revealed: A | B class A: ... class B: ... -def instance() -> A | B: - return A() - -x = instance() -y = instance() +def _(x: A | B, y: A | B): + if isinstance(x, A) and isinstance(y, B): + reveal_type(x) # revealed: A + reveal_type(y) # revealed: B + else: + # No narrowing: Only-one or both checks might have failed + reveal_type(x) # revealed: A | B + reveal_type(y) # revealed: A | B -if isinstance(x, A) and isinstance(y, B): - reveal_type(x) # revealed: A - reveal_type(y) # revealed: B -else: - # No narrowing: Only-one or both checks might have failed reveal_type(x) # revealed: A | B reveal_type(y) # revealed: A | B - -reveal_type(x) # revealed: A | B -reveal_type(y) # revealed: A | B ``` ## Narrowing in `or` conditional @@ -116,15 +96,11 @@ class A: ... class B: ... class C: ... -def instance() -> A | B | C: - return A() - -x = instance() - -if isinstance(x, A) or isinstance(x, B): - reveal_type(x) # revealed: A | B -else: - reveal_type(x) # revealed: C & ~A & ~B +def _(x: A | B | C): + if isinstance(x, A) or isinstance(x, B): + reveal_type(x) # revealed: A | B + else: + reveal_type(x) # revealed: C & ~A & ~B ``` ## In `or`, all arms should add constraint in order to narrow @@ -134,18 +110,11 @@ class A: ... class B: ... class C: ... -def instance() -> A | B | C: - return A() - -def bool_instance() -> bool: - return True - -x = instance() - -if isinstance(x, A) or isinstance(x, B) or bool_instance(): - reveal_type(x) # revealed: A | B | C -else: - reveal_type(x) # revealed: C & ~A & ~B +def _(flag: bool, x: A | B | C): + if isinstance(x, A) or isinstance(x, B) or flag: + reveal_type(x) # revealed: A | B | C + else: + reveal_type(x) # revealed: C & ~A & ~B ``` ## in `or`, all arms should narrow the same set of symbols @@ -155,28 +124,23 @@ class A: ... class B: ... class C: ... -def instance() -> A | B | C: - return A() - -x = instance() -y = instance() - -if isinstance(x, A) or isinstance(y, A): - # The predicate might be satisfied by the right side, so the type of `x` can’t be narrowed down here. - reveal_type(x) # revealed: A | B | C - # The same for `y` - reveal_type(y) # revealed: A | B | C -else: - reveal_type(x) # revealed: B & ~A | C & ~A - reveal_type(y) # revealed: B & ~A | C & ~A - -if (isinstance(x, A) and isinstance(y, A)) or (isinstance(x, B) and isinstance(y, B)): - # Here, types of `x` and `y` can be narrowd since all `or` arms constraint them. - reveal_type(x) # revealed: A | B - reveal_type(y) # revealed: A | B -else: - reveal_type(x) # revealed: A | B | C - reveal_type(y) # revealed: A | B | C +def _(x: A | B | C, y: A | B | C): + if isinstance(x, A) or isinstance(y, A): + # The predicate might be satisfied by the right side, so the type of `x` can’t be narrowed down here. + reveal_type(x) # revealed: A | B | C + # The same for `y` + reveal_type(y) # revealed: A | B | C + else: + reveal_type(x) # revealed: B & ~A | C & ~A + reveal_type(y) # revealed: B & ~A | C & ~A + + if (isinstance(x, A) and isinstance(y, A)) or (isinstance(x, B) and isinstance(y, B)): + # Here, types of `x` and `y` can be narrowd since all `or` arms constraint them. + reveal_type(x) # revealed: A | B + reveal_type(y) # revealed: A | B + else: + reveal_type(x) # revealed: A | B | C + reveal_type(y) # revealed: A | B | C ``` ## mixing `and` and `not` @@ -186,16 +150,12 @@ class A: ... class B: ... class C: ... -def instance() -> A | B | C: - return A() - -x = instance() - -if isinstance(x, B) and not isinstance(x, C): - reveal_type(x) # revealed: B & ~C -else: - # ~(B & ~C) -> ~B | C -> (A & ~B) | (C & ~B) | C -> (A & ~B) | C - reveal_type(x) # revealed: A & ~B | C +def _(x: A | B | C): + if isinstance(x, B) and not isinstance(x, C): + reveal_type(x) # revealed: B & ~C + else: + # ~(B & ~C) -> ~B | C -> (A & ~B) | (C & ~B) | C -> (A & ~B) | C + reveal_type(x) # revealed: A & ~B | C ``` ## mixing `or` and `not` @@ -205,15 +165,11 @@ class A: ... class B: ... class C: ... -def instance() -> A | B | C: - return A() - -x = instance() - -if isinstance(x, B) or not isinstance(x, C): - reveal_type(x) # revealed: B | A & ~C -else: - reveal_type(x) # revealed: C & ~B +def _(x: A | B | C): + if isinstance(x, B) or not isinstance(x, C): + reveal_type(x) # revealed: B | A & ~C + else: + reveal_type(x) # revealed: C & ~B ``` ## `or` with nested `and` @@ -223,16 +179,12 @@ class A: ... class B: ... class C: ... -def instance() -> A | B | C: - return A() - -x = instance() - -if isinstance(x, A) or (isinstance(x, B) and not isinstance(x, C)): - reveal_type(x) # revealed: A | B & ~C -else: - # ~(A | (B & ~C)) -> ~A & ~(B & ~C) -> ~A & (~B | C) -> (~A & C) | (~A ~ B) - reveal_type(x) # revealed: C & ~A +def _(x: A | B | C): + if isinstance(x, A) or (isinstance(x, B) and not isinstance(x, C)): + reveal_type(x) # revealed: A | B & ~C + else: + # ~(A | (B & ~C)) -> ~A & ~(B & ~C) -> ~A & (~B | C) -> (~A & C) | (~A ~ B) + reveal_type(x) # revealed: C & ~A ``` ## `and` with nested `or` @@ -242,41 +194,32 @@ class A: ... class B: ... class C: ... -def instance() -> A | B | C: - return A() - -x = instance() - -if isinstance(x, A) and (isinstance(x, B) or not isinstance(x, C)): - # A & (B | ~C) -> (A & B) | (A & ~C) - reveal_type(x) # revealed: A & B | A & ~C -else: - # ~((A & B) | (A & ~C)) -> - # ~(A & B) & ~(A & ~C) -> - # (~A | ~B) & (~A | C) -> - # [(~A | ~B) & ~A] | [(~A | ~B) & C] -> - # ~A | (~A & C) | (~B & C) -> - # ~A | (C & ~B) -> - # ~A | (C & ~B) The positive side of ~A is A | B | C -> - reveal_type(x) # revealed: B & ~A | C & ~A | C & ~B +def _(x: A | B | C): + if isinstance(x, A) and (isinstance(x, B) or not isinstance(x, C)): + # A & (B | ~C) -> (A & B) | (A & ~C) + reveal_type(x) # revealed: A & B | A & ~C + else: + # ~((A & B) | (A & ~C)) -> + # ~(A & B) & ~(A & ~C) -> + # (~A | ~B) & (~A | C) -> + # [(~A | ~B) & ~A] | [(~A | ~B) & C] -> + # ~A | (~A & C) | (~B & C) -> + # ~A | (C & ~B) -> + # ~A | (C & ~B) The positive side of ~A is A | B | C -> + reveal_type(x) # revealed: B & ~A | C & ~A | C & ~B ``` ## Boolean expression internal narrowing ```py -def optional_string() -> str | None: - return None - -x = optional_string() -y = optional_string() - -if x is None and y is not x: - reveal_type(y) # revealed: str +def _(x: str | None, y: str | None): + if x is None and y is not x: + reveal_type(y) # revealed: str -# Neither of the conditions alone is sufficient for narrowing y's type: -if x is None: - reveal_type(y) # revealed: str | None + # Neither of the conditions alone is sufficient for narrowing y's type: + if x is None: + reveal_type(y) # revealed: str | None -if y is not x: - reveal_type(y) # revealed: str | None + if y is not x: + reveal_type(y) # revealed: str | None ``` diff --git a/crates/red_knot_python_semantic/resources/mdtest/narrow/conditionals/elif_else.md b/crates/red_knot_python_semantic/resources/mdtest/narrow/conditionals/elif_else.md index 7ac16f4b8d597..76eae880ef39e 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/narrow/conditionals/elif_else.md +++ b/crates/red_knot_python_semantic/resources/mdtest/narrow/conditionals/elif_else.md @@ -3,55 +3,47 @@ ## Positive contributions become negative in elif-else blocks ```py -def int_instance() -> int: - return 42 - -x = int_instance() - -if x == 1: - # cannot narrow; could be a subclass of `int` - reveal_type(x) # revealed: int -elif x == 2: - reveal_type(x) # revealed: int & ~Literal[1] -elif x != 3: - reveal_type(x) # revealed: int & ~Literal[1] & ~Literal[2] & ~Literal[3] +def _(x: int): + if x == 1: + # cannot narrow; could be a subclass of `int` + reveal_type(x) # revealed: int + elif x == 2: + reveal_type(x) # revealed: int & ~Literal[1] + elif x != 3: + reveal_type(x) # revealed: int & ~Literal[1] & ~Literal[2] & ~Literal[3] ``` ## Positive contributions become negative in elif-else blocks, with simplification ```py -def bool_instance() -> bool: - return True - -x = 1 if bool_instance() else 2 if bool_instance() else 3 - -if x == 1: - # TODO should be Literal[1] - reveal_type(x) # revealed: Literal[1, 2, 3] -elif x == 2: - # TODO should be Literal[2] - reveal_type(x) # revealed: Literal[2, 3] -else: - reveal_type(x) # revealed: Literal[3] +def _(flag1: bool, flag2: bool): + x = 1 if flag1 else 2 if flag2 else 3 + + if x == 1: + # TODO should be Literal[1] + reveal_type(x) # revealed: Literal[1, 2, 3] + elif x == 2: + # TODO should be Literal[2] + reveal_type(x) # revealed: Literal[2, 3] + else: + reveal_type(x) # revealed: Literal[3] ``` ## Multiple negative contributions using elif, with simplification ```py -def bool_instance() -> bool: - return True - -x = 1 if bool_instance() else 2 if bool_instance() else 3 - -if x != 1: - reveal_type(x) # revealed: Literal[2, 3] -elif x != 2: - # TODO should be `Literal[1]` - reveal_type(x) # revealed: Literal[1, 3] -elif x == 3: - # TODO should be Never - reveal_type(x) # revealed: Literal[1, 2, 3] -else: - # TODO should be Never - reveal_type(x) # revealed: Literal[1, 2] +def _(flag1: bool, flag2: bool): + x = 1 if flag1 else 2 if flag2 else 3 + + if x != 1: + reveal_type(x) # revealed: Literal[2, 3] + elif x != 2: + # TODO should be `Literal[1]` + reveal_type(x) # revealed: Literal[1, 3] + elif x == 3: + # TODO should be Never + reveal_type(x) # revealed: Literal[1, 2, 3] + else: + # TODO should be Never + reveal_type(x) # revealed: Literal[1, 2] ``` diff --git a/crates/red_knot_python_semantic/resources/mdtest/narrow/conditionals/is.md b/crates/red_knot_python_semantic/resources/mdtest/narrow/conditionals/is.md index b9cd22897d18d..c0aa140b408d3 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/narrow/conditionals/is.md +++ b/crates/red_knot_python_semantic/resources/mdtest/narrow/conditionals/is.md @@ -3,77 +3,64 @@ ## `is None` ```py -def bool_instance() -> bool: - return True +def _(flag: bool): + x = None if flag else 1 -flag = bool_instance() -x = None if flag else 1 + if x is None: + reveal_type(x) # revealed: None + else: + reveal_type(x) # revealed: Literal[1] -if x is None: - reveal_type(x) # revealed: None -else: - reveal_type(x) # revealed: Literal[1] - -reveal_type(x) # revealed: None | Literal[1] + reveal_type(x) # revealed: None | Literal[1] ``` ## `is` for other types ```py -def bool_instance() -> bool: - return True - -flag = bool_instance() - -class A: ... +def _(flag: bool): + class A: ... + x = A() + y = x if flag else None -x = A() -y = x if flag else None + if y is x: + reveal_type(y) # revealed: A + else: + reveal_type(y) # revealed: A | None -if y is x: - reveal_type(y) # revealed: A -else: reveal_type(y) # revealed: A | None - -reveal_type(y) # revealed: A | None ``` ## `is` in chained comparisons ```py -def bool_instance() -> bool: - return True - -x_flag, y_flag = bool_instance(), bool_instance() -x = True if x_flag else False -y = True if y_flag else False +def _(x_flag: bool, y_flag: bool): + x = True if x_flag else False + y = True if y_flag else False -reveal_type(x) # revealed: bool -reveal_type(y) # revealed: bool - -if y is x is False: # Interpreted as `(y is x) and (x is False)` - reveal_type(x) # revealed: Literal[False] - reveal_type(y) # revealed: bool -else: - # The negation of the clause above is (y is not x) or (x is not False) - # So we can't narrow the type of x or y here, because each arm of the `or` could be true reveal_type(x) # revealed: bool reveal_type(y) # revealed: bool + + if y is x is False: # Interpreted as `(y is x) and (x is False)` + reveal_type(x) # revealed: Literal[False] + reveal_type(y) # revealed: bool + else: + # The negation of the clause above is (y is not x) or (x is not False) + # So we can't narrow the type of x or y here, because each arm of the `or` could be true + reveal_type(x) # revealed: bool + reveal_type(y) # revealed: bool ``` ## `is` in elif clause ```py -def bool_instance() -> bool: - return True - -x = None if bool_instance() else (1 if bool_instance() else True) - -reveal_type(x) # revealed: None | Literal[1] | Literal[True] -if x is None: - reveal_type(x) # revealed: None -elif x is True: - reveal_type(x) # revealed: Literal[True] -else: - reveal_type(x) # revealed: Literal[1] +def _(flag1: bool, flag2: bool): + x = None if flag1 else (1 if flag2 else True) + + reveal_type(x) # revealed: None | Literal[1] | Literal[True] + if x is None: + reveal_type(x) # revealed: None + elif x is True: + reveal_type(x) # revealed: Literal[True] + else: + reveal_type(x) # revealed: Literal[1] ``` diff --git a/crates/red_knot_python_semantic/resources/mdtest/narrow/conditionals/is_not.md b/crates/red_knot_python_semantic/resources/mdtest/narrow/conditionals/is_not.md index ed89f3f7ae87d..980a66a68d2ee 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/narrow/conditionals/is_not.md +++ b/crates/red_knot_python_semantic/resources/mdtest/narrow/conditionals/is_not.md @@ -5,34 +5,28 @@ The type guard removes `None` from the union type: ```py -def bool_instance() -> bool: - return True +def _(flag: bool): + x = None if flag else 1 -flag = bool_instance() -x = None if flag else 1 + if x is not None: + reveal_type(x) # revealed: Literal[1] + else: + reveal_type(x) # revealed: None -if x is not None: - reveal_type(x) # revealed: Literal[1] -else: - reveal_type(x) # revealed: None - -reveal_type(x) # revealed: None | Literal[1] + reveal_type(x) # revealed: None | Literal[1] ``` ## `is not` for other singleton types ```py -def bool_instance() -> bool: - return True - -flag = bool_instance() -x = True if flag else False -reveal_type(x) # revealed: bool +def _(flag: bool): + x = True if flag else False + reveal_type(x) # revealed: bool -if x is not False: - reveal_type(x) # revealed: Literal[True] -else: - reveal_type(x) # revealed: Literal[False] + if x is not False: + reveal_type(x) # revealed: Literal[True] + else: + reveal_type(x) # revealed: Literal[False] ``` ## `is not` for non-singleton types @@ -53,20 +47,17 @@ else: ## `is not` for other types ```py -def bool_instance() -> bool: - return True - -class A: ... +def _(flag: bool): + class A: ... + x = A() + y = x if flag else None -x = A() -y = x if bool_instance() else None + if y is not x: + reveal_type(y) # revealed: A | None + else: + reveal_type(y) # revealed: A -if y is not x: reveal_type(y) # revealed: A | None -else: - reveal_type(y) # revealed: A - -reveal_type(y) # revealed: A | None ``` ## `is not` in chained comparisons @@ -74,23 +65,20 @@ reveal_type(y) # revealed: A | None The type guard removes `False` from the union type of the tested value only. ```py -def bool_instance() -> bool: - return True - -x_flag, y_flag = bool_instance(), bool_instance() -x = True if x_flag else False -y = True if y_flag else False - -reveal_type(x) # revealed: bool -reveal_type(y) # revealed: bool - -if y is not x is not False: # Interpreted as `(y is not x) and (x is not False)` - reveal_type(x) # revealed: Literal[True] - reveal_type(y) # revealed: bool -else: - # The negation of the clause above is (y is x) or (x is False) - # So we can't narrow the type of x or y here, because each arm of the `or` could be true +def _(x_flag: bool, y_flag: bool): + x = True if x_flag else False + y = True if y_flag else False reveal_type(x) # revealed: bool reveal_type(y) # revealed: bool + + if y is not x is not False: # Interpreted as `(y is not x) and (x is not False)` + reveal_type(x) # revealed: Literal[True] + reveal_type(y) # revealed: bool + else: + # The negation of the clause above is (y is x) or (x is False) + # So we can't narrow the type of x or y here, because each arm of the `or` could be true + + reveal_type(x) # revealed: bool + reveal_type(y) # revealed: bool ``` diff --git a/crates/red_knot_python_semantic/resources/mdtest/narrow/conditionals/nested.md b/crates/red_knot_python_semantic/resources/mdtest/narrow/conditionals/nested.md index cc0f79165e742..fa69fe8863bc0 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/narrow/conditionals/nested.md +++ b/crates/red_knot_python_semantic/resources/mdtest/narrow/conditionals/nested.md @@ -3,54 +3,45 @@ ## Multiple negative contributions ```py -def int_instance() -> int: - return 42 - -x = int_instance() - -if x != 1: - if x != 2: - if x != 3: - reveal_type(x) # revealed: int & ~Literal[1] & ~Literal[2] & ~Literal[3] +def _(x: int): + if x != 1: + if x != 2: + if x != 3: + reveal_type(x) # revealed: int & ~Literal[1] & ~Literal[2] & ~Literal[3] ``` ## Multiple negative contributions with simplification ```py -def bool_instance() -> bool: - return True - -flag1, flag2 = bool_instance(), bool_instance() -x = 1 if flag1 else 2 if flag2 else 3 +def _(flag1: bool, flag2: bool): + x = 1 if flag1 else 2 if flag2 else 3 -if x != 1: - reveal_type(x) # revealed: Literal[2, 3] - if x != 2: - reveal_type(x) # revealed: Literal[3] + if x != 1: + reveal_type(x) # revealed: Literal[2, 3] + if x != 2: + reveal_type(x) # revealed: Literal[3] ``` ## elif-else blocks ```py -def bool_instance() -> bool: - return True - -x = 1 if bool_instance() else 2 if bool_instance() else 3 +def _(flag1: bool, flag2: bool): + x = 1 if flag1 else 2 if flag2 else 3 -if x != 1: - reveal_type(x) # revealed: Literal[2, 3] - if x == 2: - # TODO should be `Literal[2]` + if x != 1: reveal_type(x) # revealed: Literal[2, 3] - elif x == 3: - reveal_type(x) # revealed: Literal[3] + if x == 2: + # TODO should be `Literal[2]` + reveal_type(x) # revealed: Literal[2, 3] + elif x == 3: + reveal_type(x) # revealed: Literal[3] + else: + reveal_type(x) # revealed: Never + + elif x != 2: + # TODO should be Literal[1] + reveal_type(x) # revealed: Literal[1, 3] else: - reveal_type(x) # revealed: Never - -elif x != 2: - # TODO should be Literal[1] - reveal_type(x) # revealed: Literal[1, 3] -else: - # TODO should be Never - reveal_type(x) # revealed: Literal[1, 2, 3] + # TODO should be Never + reveal_type(x) # revealed: Literal[1, 2, 3] ``` diff --git a/crates/red_knot_python_semantic/resources/mdtest/narrow/conditionals/not.md b/crates/red_knot_python_semantic/resources/mdtest/narrow/conditionals/not.md index 0cf341496c337..c0a305d1eaf22 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/narrow/conditionals/not.md +++ b/crates/red_knot_python_semantic/resources/mdtest/narrow/conditionals/not.md @@ -5,29 +5,25 @@ The `not` operator negates a constraint. ## `not is None` ```py -def bool_instance() -> bool: - return True +def _(flag: bool): + x = None if flag else 1 -x = None if bool_instance() else 1 + if not x is None: + reveal_type(x) # revealed: Literal[1] + else: + reveal_type(x) # revealed: None -if not x is None: - reveal_type(x) # revealed: Literal[1] -else: - reveal_type(x) # revealed: None - -reveal_type(x) # revealed: None | Literal[1] + reveal_type(x) # revealed: None | Literal[1] ``` ## `not isinstance` ```py -def bool_instance() -> bool: - return True - -x = 1 if bool_instance() else "a" +def _(flag: bool): + x = 1 if flag else "a" -if not isinstance(x, (int)): - reveal_type(x) # revealed: Literal["a"] -else: - reveal_type(x) # revealed: Literal[1] + if not isinstance(x, (int)): + reveal_type(x) # revealed: Literal["a"] + else: + reveal_type(x) # revealed: Literal[1] ``` diff --git a/crates/red_knot_python_semantic/resources/mdtest/narrow/conditionals/not_eq.md b/crates/red_knot_python_semantic/resources/mdtest/narrow/conditionals/not_eq.md index 3ad8ebcb68e10..abe0c4d5aaea1 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/narrow/conditionals/not_eq.md +++ b/crates/red_knot_python_semantic/resources/mdtest/narrow/conditionals/not_eq.md @@ -3,82 +3,66 @@ ## `x != None` ```py -def bool_instance() -> bool: - return True - -flag = bool_instance() -x = None if flag else 1 - -if x != None: - reveal_type(x) # revealed: Literal[1] -else: - # TODO should be None - reveal_type(x) # revealed: None | Literal[1] +def _(flag: bool): + x = None if flag else 1 + + if x != None: + reveal_type(x) # revealed: Literal[1] + else: + # TODO should be None + reveal_type(x) # revealed: None | Literal[1] ``` ## `!=` for other singleton types ```py -def bool_instance() -> bool: - return True - -flag = bool_instance() -x = True if flag else False - -if x != False: - reveal_type(x) # revealed: Literal[True] -else: - # TODO should be Literal[False] - reveal_type(x) # revealed: bool +def _(flag: bool): + x = True if flag else False + + if x != False: + reveal_type(x) # revealed: Literal[True] + else: + # TODO should be Literal[False] + reveal_type(x) # revealed: bool ``` ## `x != y` where `y` is of literal type ```py -def bool_instance() -> bool: - return True +def _(flag: bool): + x = 1 if flag else 2 -flag = bool_instance() -x = 1 if flag else 2 - -if x != 1: - reveal_type(x) # revealed: Literal[2] + if x != 1: + reveal_type(x) # revealed: Literal[2] ``` ## `x != y` where `y` is a single-valued type ```py -def bool_instance() -> bool: - return True - -flag = bool_instance() - -class A: ... -class B: ... - -C = A if flag else B - -if C != A: - reveal_type(C) # revealed: Literal[B] -else: - # TODO should be Literal[A] - reveal_type(C) # revealed: Literal[A, B] +def _(flag: bool): + class A: ... + class B: ... + C = A if flag else B + + if C != A: + reveal_type(C) # revealed: Literal[B] + else: + # TODO should be Literal[A] + reveal_type(C) # revealed: Literal[A, B] ``` ## `x != y` where `y` has multiple single-valued options ```py -def bool_instance() -> bool: - return True - -x = 1 if bool_instance() else 2 -y = 2 if bool_instance() else 3 - -if x != y: - reveal_type(x) # revealed: Literal[1, 2] -else: - # TODO should be Literal[2] - reveal_type(x) # revealed: Literal[1, 2] +def _(flag1: bool, flag2: bool): + x = 1 if flag1 else 2 + y = 2 if flag2 else 3 + + if x != y: + reveal_type(x) # revealed: Literal[1, 2] + else: + # TODO should be Literal[2] + reveal_type(x) # revealed: Literal[1, 2] ``` ## `!=` for non-single-valued types @@ -86,34 +70,22 @@ else: Only single-valued types should narrow the type: ```py -def bool_instance() -> bool: - return True - -def int_instance() -> int: - return 42 +def _(flag: bool, a: int, y: int): + x = a if flag else None -flag = bool_instance() -x = int_instance() if flag else None -y = int_instance() - -if x != y: - reveal_type(x) # revealed: int | None + if x != y: + reveal_type(x) # revealed: int | None ``` ## Mix of single-valued and non-single-valued types ```py -def int_instance() -> int: - return 42 - -def bool_instance() -> bool: - return True - -x = 1 if bool_instance() else 2 -y = 2 if bool_instance() else int_instance() - -if x != y: - reveal_type(x) # revealed: Literal[1, 2] -else: - reveal_type(x) # revealed: Literal[1, 2] +def _(flag1: bool, flag2: bool, a: int): + x = 1 if flag1 else 2 + y = 2 if flag2 else a + + if x != y: + reveal_type(x) # revealed: Literal[1, 2] + else: + reveal_type(x) # revealed: Literal[1, 2] ``` diff --git a/crates/red_knot_python_semantic/resources/mdtest/narrow/isinstance.md b/crates/red_knot_python_semantic/resources/mdtest/narrow/isinstance.md index 849d5f802ce13..f2a24ae9656a7 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/narrow/isinstance.md +++ b/crates/red_knot_python_semantic/resources/mdtest/narrow/isinstance.md @@ -5,23 +5,19 @@ Narrowing for `isinstance(object, classinfo)` expressions. ## `classinfo` is a single type ```py -def bool_instance() -> bool: - return True +def _(flag: bool): + x = 1 if flag else "a" -flag = bool_instance() - -x = 1 if flag else "a" - -if isinstance(x, int): - reveal_type(x) # revealed: Literal[1] - -if isinstance(x, str): - reveal_type(x) # revealed: Literal["a"] if isinstance(x, int): - reveal_type(x) # revealed: Never + reveal_type(x) # revealed: Literal[1] -if isinstance(x, (int, object)): - reveal_type(x) # revealed: Literal[1] | Literal["a"] + if isinstance(x, str): + reveal_type(x) # revealed: Literal["a"] + if isinstance(x, int): + reveal_type(x) # revealed: Never + + if isinstance(x, (int, object)): + reveal_type(x) # revealed: Literal[1] | Literal["a"] ``` ## `classinfo` is a tuple of types @@ -30,56 +26,48 @@ Note: `isinstance(x, (int, str))` should not be confused with `isinstance(x, tup The former is equivalent to `isinstance(x, int | str)`: ```py -def bool_instance() -> bool: - return True - -flag, flag1, flag2 = bool_instance(), bool_instance(), bool_instance() - -x = 1 if flag else "a" +def _(flag: bool, flag1: bool, flag2: bool): + x = 1 if flag else "a" -if isinstance(x, (int, str)): - reveal_type(x) # revealed: Literal[1] | Literal["a"] -else: - reveal_type(x) # revealed: Never + if isinstance(x, (int, str)): + reveal_type(x) # revealed: Literal[1] | Literal["a"] + else: + reveal_type(x) # revealed: Never -if isinstance(x, (int, bytes)): - reveal_type(x) # revealed: Literal[1] + if isinstance(x, (int, bytes)): + reveal_type(x) # revealed: Literal[1] -if isinstance(x, (bytes, str)): - reveal_type(x) # revealed: Literal["a"] + if isinstance(x, (bytes, str)): + reveal_type(x) # revealed: Literal["a"] -# No narrowing should occur if a larger type is also -# one of the possibilities: -if isinstance(x, (int, object)): - reveal_type(x) # revealed: Literal[1] | Literal["a"] -else: - reveal_type(x) # revealed: Never + # No narrowing should occur if a larger type is also + # one of the possibilities: + if isinstance(x, (int, object)): + reveal_type(x) # revealed: Literal[1] | Literal["a"] + else: + reveal_type(x) # revealed: Never -y = 1 if flag1 else "a" if flag2 else b"b" -if isinstance(y, (int, str)): - reveal_type(y) # revealed: Literal[1] | Literal["a"] + y = 1 if flag1 else "a" if flag2 else b"b" + if isinstance(y, (int, str)): + reveal_type(y) # revealed: Literal[1] | Literal["a"] -if isinstance(y, (int, bytes)): - reveal_type(y) # revealed: Literal[1] | Literal[b"b"] + if isinstance(y, (int, bytes)): + reveal_type(y) # revealed: Literal[1] | Literal[b"b"] -if isinstance(y, (str, bytes)): - reveal_type(y) # revealed: Literal["a"] | Literal[b"b"] + if isinstance(y, (str, bytes)): + reveal_type(y) # revealed: Literal["a"] | Literal[b"b"] ``` ## `classinfo` is a nested tuple of types ```py -def bool_instance() -> bool: - return True - -flag = bool_instance() +def _(flag: bool): + x = 1 if flag else "a" -x = 1 if flag else "a" - -if isinstance(x, (bool, (bytes, int))): - reveal_type(x) # revealed: Literal[1] -else: - reveal_type(x) # revealed: Literal["a"] + if isinstance(x, (bool, (bytes, int))): + reveal_type(x) # revealed: Literal[1] + else: + reveal_type(x) # revealed: Literal["a"] ``` ## Class types @@ -89,9 +77,7 @@ class A: ... class B: ... class C: ... -def get_object() -> object: ... - -x = get_object() +x = object() if isinstance(x, A): reveal_type(x) # revealed: A @@ -112,50 +98,40 @@ else: ## No narrowing for instances of `builtins.type` ```py -def bool_instance() -> bool: - return True +def _(flag: bool): + t = type("t", (), {}) -flag = bool_instance() + # This isn't testing what we want it to test if we infer anything more precise here: + reveal_type(t) # revealed: type -t = type("t", (), {}) + x = 1 if flag else "foo" -# This isn't testing what we want it to test if we infer anything more precise here: -reveal_type(t) # revealed: type -x = 1 if flag else "foo" - -if isinstance(x, t): - reveal_type(x) # revealed: Literal[1] | Literal["foo"] + if isinstance(x, t): + reveal_type(x) # revealed: Literal[1] | Literal["foo"] ``` ## Do not use custom `isinstance` for narrowing ```py -def bool_instance() -> bool: - return True - -flag = bool_instance() - -def isinstance(x, t): - return True +def _(flag: bool): + def isinstance(x, t): + return True + x = 1 if flag else "a" -x = 1 if flag else "a" -if isinstance(x, int): - reveal_type(x) # revealed: Literal[1] | Literal["a"] + if isinstance(x, int): + reveal_type(x) # revealed: Literal[1] | Literal["a"] ``` ## Do support narrowing if `isinstance` is aliased ```py -def bool_instance() -> bool: - return True - -flag = bool_instance() +def _(flag: bool): + isinstance_alias = isinstance -isinstance_alias = isinstance + x = 1 if flag else "a" -x = 1 if flag else "a" -if isinstance_alias(x, int): - reveal_type(x) # revealed: Literal[1] + if isinstance_alias(x, int): + reveal_type(x) # revealed: Literal[1] ``` ## Do support narrowing if `isinstance` is imported @@ -163,46 +139,38 @@ if isinstance_alias(x, int): ```py from builtins import isinstance as imported_isinstance -def bool_instance() -> bool: - return True +def _(flag: bool): + x = 1 if flag else "a" -flag = bool_instance() -x = 1 if flag else "a" -if imported_isinstance(x, int): - reveal_type(x) # revealed: Literal[1] + if imported_isinstance(x, int): + reveal_type(x) # revealed: Literal[1] ``` ## Do not narrow if second argument is not a type ```py -def bool_instance() -> bool: - return True - -flag = bool_instance() -x = 1 if flag else "a" - -# TODO: this should cause us to emit a diagnostic during -# type checking -if isinstance(x, "a"): - reveal_type(x) # revealed: Literal[1] | Literal["a"] - -# TODO: this should cause us to emit a diagnostic during -# type checking -if isinstance(x, "int"): - reveal_type(x) # revealed: Literal[1] | Literal["a"] +def _(flag: bool): + x = 1 if flag else "a" + + # TODO: this should cause us to emit a diagnostic during + # type checking + if isinstance(x, "a"): + reveal_type(x) # revealed: Literal[1] | Literal["a"] + + # TODO: this should cause us to emit a diagnostic during + # type checking + if isinstance(x, "int"): + reveal_type(x) # revealed: Literal[1] | Literal["a"] ``` ## Do not narrow if there are keyword arguments ```py -def bool_instance() -> bool: - return True - -flag = bool_instance() -x = 1 if flag else "a" +def _(flag: bool): + x = 1 if flag else "a" -# TODO: this should cause us to emit a diagnostic -# (`isinstance` has no `foo` parameter) -if isinstance(x, int, foo="bar"): - reveal_type(x) # revealed: Literal[1] | Literal["a"] + # TODO: this should cause us to emit a diagnostic + # (`isinstance` has no `foo` parameter) + if isinstance(x, int, foo="bar"): + reveal_type(x) # revealed: Literal[1] | Literal["a"] ``` diff --git a/crates/red_knot_python_semantic/resources/mdtest/narrow/issubclass.md b/crates/red_knot_python_semantic/resources/mdtest/narrow/issubclass.md index 44184f634c73b..7da1ad3e36126 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/narrow/issubclass.md +++ b/crates/red_knot_python_semantic/resources/mdtest/narrow/issubclass.md @@ -7,45 +7,43 @@ Narrowing for `issubclass(class, classinfo)` expressions. ### Basic example ```py -def flag() -> bool: ... - -t = int if flag() else str +def _(flag: bool): + t = int if flag else str -if issubclass(t, bytes): - reveal_type(t) # revealed: Never + if issubclass(t, bytes): + reveal_type(t) # revealed: Never -if issubclass(t, object): - reveal_type(t) # revealed: Literal[int, str] + if issubclass(t, object): + reveal_type(t) # revealed: Literal[int, str] -if issubclass(t, int): - reveal_type(t) # revealed: Literal[int] -else: - reveal_type(t) # revealed: Literal[str] - -if issubclass(t, str): - reveal_type(t) # revealed: Literal[str] if issubclass(t, int): - reveal_type(t) # revealed: Never + reveal_type(t) # revealed: Literal[int] + else: + reveal_type(t) # revealed: Literal[str] + + if issubclass(t, str): + reveal_type(t) # revealed: Literal[str] + if issubclass(t, int): + reveal_type(t) # revealed: Never ``` ### Proper narrowing in `elif` and `else` branches ```py -def flag() -> bool: ... - -t = int if flag() else str if flag() else bytes +def _(flag1: bool, flag2: bool): + t = int if flag1 else str if flag2 else bytes -if issubclass(t, int): - reveal_type(t) # revealed: Literal[int] -else: - reveal_type(t) # revealed: Literal[str, bytes] + if issubclass(t, int): + reveal_type(t) # revealed: Literal[int] + else: + reveal_type(t) # revealed: Literal[str, bytes] -if issubclass(t, int): - reveal_type(t) # revealed: Literal[int] -elif issubclass(t, str): - reveal_type(t) # revealed: Literal[str] -else: - reveal_type(t) # revealed: Literal[bytes] + if issubclass(t, int): + reveal_type(t) # revealed: Literal[int] + elif issubclass(t, str): + reveal_type(t) # revealed: Literal[str] + else: + reveal_type(t) # revealed: Literal[bytes] ``` ### Multiple derived classes @@ -56,29 +54,28 @@ class Derived1(Base): ... class Derived2(Base): ... class Unrelated: ... -def flag() -> bool: ... +def _(flag1: bool, flag2: bool, flag3: bool): + t1 = Derived1 if flag1 else Derived2 -t1 = Derived1 if flag() else Derived2 + if issubclass(t1, Base): + reveal_type(t1) # revealed: Literal[Derived1, Derived2] -if issubclass(t1, Base): - reveal_type(t1) # revealed: Literal[Derived1, Derived2] + if issubclass(t1, Derived1): + reveal_type(t1) # revealed: Literal[Derived1] + else: + reveal_type(t1) # revealed: Literal[Derived2] -if issubclass(t1, Derived1): - reveal_type(t1) # revealed: Literal[Derived1] -else: - reveal_type(t1) # revealed: Literal[Derived2] + t2 = Derived1 if flag2 else Base -t2 = Derived1 if flag() else Base + if issubclass(t2, Base): + reveal_type(t2) # revealed: Literal[Derived1, Base] -if issubclass(t2, Base): - reveal_type(t2) # revealed: Literal[Derived1, Base] + t3 = Derived1 if flag3 else Unrelated -t3 = Derived1 if flag() else Unrelated - -if issubclass(t3, Base): - reveal_type(t3) # revealed: Literal[Derived1] -else: - reveal_type(t3) # revealed: Literal[Unrelated] + if issubclass(t3, Base): + reveal_type(t3) # revealed: Literal[Derived1] + else: + reveal_type(t3) # revealed: Literal[Unrelated] ``` ### Narrowing for non-literals @@ -87,16 +84,13 @@ else: class A: ... class B: ... -def get_class() -> type[object]: ... - -t = get_class() - -if issubclass(t, A): - reveal_type(t) # revealed: type[A] - if issubclass(t, B): - reveal_type(t) # revealed: type[A] & type[B] -else: - reveal_type(t) # revealed: type[object] & ~type[A] +def _(t: type[object]): + if issubclass(t, A): + reveal_type(t) # revealed: type[A] + if issubclass(t, B): + reveal_type(t) # revealed: type[A] & type[B] + else: + reveal_type(t) # revealed: type[object] & ~type[A] ``` ### Handling of `None` @@ -107,16 +101,15 @@ else: # error: [possibly-unbound-import] "Member `NoneType` of module `types` is possibly unbound" from types import NoneType -def flag() -> bool: ... - -t = int if flag() else NoneType +def _(flag: bool): + t = int if flag else NoneType -if issubclass(t, NoneType): - reveal_type(t) # revealed: Literal[NoneType] + if issubclass(t, NoneType): + reveal_type(t) # revealed: Literal[NoneType] -if issubclass(t, type(None)): - # TODO: this should be just `Literal[NoneType]` - reveal_type(t) # revealed: Literal[int, NoneType] + if issubclass(t, type(None)): + # TODO: this should be just `Literal[NoneType]` + reveal_type(t) # revealed: Literal[int, NoneType] ``` ## `classinfo` contains multiple types @@ -126,14 +119,13 @@ if issubclass(t, type(None)): ```py class Unrelated: ... -def flag() -> bool: ... - -t = int if flag() else str if flag() else bytes +def _(flag1: bool, flag2: bool): + t = int if flag1 else str if flag2 else bytes -if issubclass(t, (int, (Unrelated, (bytes,)))): - reveal_type(t) # revealed: Literal[int, bytes] -else: - reveal_type(t) # revealed: Literal[str] + if issubclass(t, (int, (Unrelated, (bytes,)))): + reveal_type(t) # revealed: Literal[int, bytes] + else: + reveal_type(t) # revealed: Literal[str] ``` ## Special cases @@ -148,9 +140,7 @@ to `issubclass`: ```py class A: ... -def get_object() -> object: ... - -t = get_object() +t = object() # TODO: we should emit a diagnostic here if issubclass(t, A): diff --git a/crates/red_knot_python_semantic/resources/mdtest/narrow/match.md b/crates/red_knot_python_semantic/resources/mdtest/narrow/match.md index 2144f91f9eff7..da5678f3109a7 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/narrow/match.md +++ b/crates/red_knot_python_semantic/resources/mdtest/narrow/match.md @@ -3,19 +3,16 @@ ## Single `match` pattern ```py -def bool_instance() -> bool: - return True +def _(flag: bool): + x = None if flag else 1 -flag = bool_instance() + reveal_type(x) # revealed: None | Literal[1] -x = None if flag else 1 -reveal_type(x) # revealed: None | Literal[1] + y = 0 -y = 0 + match x: + case None: + y = x -match x: - case None: - y = x - -reveal_type(y) # revealed: Literal[0] | None + reveal_type(y) # revealed: Literal[0] | None ``` diff --git a/crates/red_knot_python_semantic/resources/mdtest/narrow/post_if_statement.md b/crates/red_knot_python_semantic/resources/mdtest/narrow/post_if_statement.md index 267e820d595da..fbbe5794d8355 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/narrow/post_if_statement.md +++ b/crates/red_knot_python_semantic/resources/mdtest/narrow/post_if_statement.md @@ -3,62 +3,49 @@ ## After if-else statements, narrowing has no effect if the variable is not mutated in any branch ```py -def optional_int() -> int | None: ... +def _(x: int | None): + if x is None: + pass + else: + pass -x = optional_int() - -if x is None: - pass -else: - pass - -reveal_type(x) # revealed: int | None + reveal_type(x) # revealed: int | None ``` ## Narrowing can have a persistent effect if the variable is mutated in one branch ```py -def optional_int() -> int | None: ... - -x = optional_int() - -if x is None: - x = 10 -else: - pass +def _(x: int | None): + if x is None: + x = 10 + else: + pass -reveal_type(x) # revealed: int + reveal_type(x) # revealed: int ``` ## An if statement without an explicit `else` branch is equivalent to one with a no-op `else` branch ```py -def optional_int() -> int | None: ... +def _(x: int | None, y: int | None): + if x is None: + x = 0 -x = optional_int() -y = optional_int() + if y is None: + pass -if x is None: - x = 0 - -if y is None: - pass - -reveal_type(x) # revealed: int -reveal_type(y) # revealed: int | None + reveal_type(x) # revealed: int + reveal_type(y) # revealed: int | None ``` ## An if-elif without an explicit else branch is equivalent to one with an empty else branch ```py -def optional_int() -> int | None: ... - -x = optional_int() - -if x is None: - x = 0 -elif x > 50: - x = 50 +def _(x: int | None): + if x is None: + x = 0 + elif x > 50: + x = 50 -reveal_type(x) # revealed: int + reveal_type(x) # revealed: int ``` diff --git a/crates/red_knot_python_semantic/resources/mdtest/narrow/type.md b/crates/red_knot_python_semantic/resources/mdtest/narrow/type.md index 01d2646198f80..386a508aab3ad 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/narrow/type.md +++ b/crates/red_knot_python_semantic/resources/mdtest/narrow/type.md @@ -6,18 +6,14 @@ class A: ... class B: ... -def get_a_or_b() -> A | B: - return A() - -x = get_a_or_b() - -if type(x) is A: - reveal_type(x) # revealed: A -else: - # It would be wrong to infer `B` here. The type - # of `x` could be a subclass of `A`, so we need - # to infer the full union type: - reveal_type(x) # revealed: A | B +def _(x: A | B): + if type(x) is A: + reveal_type(x) # revealed: A + else: + # It would be wrong to infer `B` here. The type + # of `x` could be a subclass of `A`, so we need + # to infer the full union type: + reveal_type(x) # revealed: A | B ``` ## `type(x) is not C` @@ -26,16 +22,12 @@ else: class A: ... class B: ... -def get_a_or_b() -> A | B: - return A() - -x = get_a_or_b() - -if type(x) is not A: - # Same reasoning as above: no narrowing should occur here. - reveal_type(x) # revealed: A | B -else: - reveal_type(x) # revealed: A +def _(x: A | B): + if type(x) is not A: + # Same reasoning as above: no narrowing should occur here. + reveal_type(x) # revealed: A | B + else: + reveal_type(x) # revealed: A ``` ## `type(x) == C`, `type(x) != C` @@ -54,16 +46,12 @@ class IsEqualToEverything(type): class A(metaclass=IsEqualToEverything): ... class B(metaclass=IsEqualToEverything): ... -def get_a_or_b() -> A | B: - return B() - -x = get_a_or_b() - -if type(x) == A: - reveal_type(x) # revealed: A | B +def _(x: A | B): + if type(x) == A: + reveal_type(x) # revealed: A | B -if type(x) != A: - reveal_type(x) # revealed: A | B + if type(x) != A: + reveal_type(x) # revealed: A | B ``` ## No narrowing for custom `type` callable @@ -75,15 +63,11 @@ class B: ... def type(x): return int -def get_a_or_b() -> A | B: - return A() - -x = get_a_or_b() - -if type(x) is A: - reveal_type(x) # revealed: A | B -else: - reveal_type(x) # revealed: A | B +def _(x: A | B): + if type(x) is A: + reveal_type(x) # revealed: A | B + else: + reveal_type(x) # revealed: A | B ``` ## No narrowing for multiple arguments @@ -91,15 +75,11 @@ else: No narrowing should occur if `type` is used to dynamically create a class: ```py -def get_str_or_int() -> str | int: - return "test" - -x = get_str_or_int() - -if type(x, (), {}) is str: - reveal_type(x) # revealed: str | int -else: - reveal_type(x) # revealed: str | int +def _(x: str | int): + if type(x, (), {}) is str: + reveal_type(x) # revealed: str | int + else: + reveal_type(x) # revealed: str | int ``` ## No narrowing for keyword arguments @@ -107,14 +87,10 @@ else: `type` can't be used with a keyword argument: ```py -def get_str_or_int() -> str | int: - return "test" - -x = get_str_or_int() - -# TODO: we could issue a diagnostic here -if type(object=x) is str: - reveal_type(x) # revealed: str | int +def _(x: str | int): + # TODO: we could issue a diagnostic here + if type(object=x) is str: + reveal_type(x) # revealed: str | int ``` ## Narrowing if `type` is aliased @@ -125,13 +101,9 @@ class B: ... alias_for_type = type -def get_a_or_b() -> A | B: - return A() - -x = get_a_or_b() - -if alias_for_type(x) is A: - reveal_type(x) # revealed: A +def _(x: A | B): + if alias_for_type(x) is A: + reveal_type(x) # revealed: A ``` ## Limitations @@ -140,13 +112,9 @@ if alias_for_type(x) is A: class Base: ... class Derived(Base): ... -def get_base() -> Base: - return Base() - -x = get_base() - -if type(x) is Base: - # Ideally, this could be narrower, but there is now way to - # express a constraint like `Base & ~ProperSubtypeOf[Base]`. - reveal_type(x) # revealed: Base +def _(x: Base): + if type(x) is Base: + # Ideally, this could be narrower, but there is now way to + # express a constraint like `Base & ~ProperSubtypeOf[Base]`. + reveal_type(x) # revealed: Base ``` diff --git a/crates/red_knot_python_semantic/resources/mdtest/scopes/unbound.md b/crates/red_knot_python_semantic/resources/mdtest/scopes/unbound.md index fc6ee0de10aa6..0a50dee2b22ca 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/scopes/unbound.md +++ b/crates/red_knot_python_semantic/resources/mdtest/scopes/unbound.md @@ -5,10 +5,10 @@ Name lookups within a class scope fall back to globals, but lookups of class attributes don't. ```py -def bool_instance() -> bool: +def coinflip() -> bool: return True -flag = bool_instance() +flag = coinflip() x = 1 class C: @@ -24,14 +24,14 @@ reveal_type(C.y) # revealed: Literal[1] ## Possibly unbound in class and global scope ```py -def bool_instance() -> bool: +def coinflip() -> bool: return True -if bool_instance(): +if coinflip(): x = "abc" class C: - if bool_instance(): + if coinflip(): x = 1 # error: [possibly-unresolved-reference] diff --git a/crates/red_knot_python_semantic/resources/mdtest/shadowing/variable_declaration.md b/crates/red_knot_python_semantic/resources/mdtest/shadowing/variable_declaration.md index 44a93668c9801..8c4a9259ed5b1 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/shadowing/variable_declaration.md +++ b/crates/red_knot_python_semantic/resources/mdtest/shadowing/variable_declaration.md @@ -3,14 +3,11 @@ ## Shadow after incompatible declarations is OK ```py -def bool_instance() -> bool: - return True +def _(flag: bool): + if flag: + x: str + else: + x: int -flag = bool_instance() - -if flag: - x: str -else: - x: int -x: bytes = b"foo" + x: bytes = b"foo" ``` diff --git a/crates/red_knot_python_semantic/resources/mdtest/subscript/bytes.md b/crates/red_knot_python_semantic/resources/mdtest/subscript/bytes.md index 71c7775b7614c..beb52730e6a04 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/subscript/bytes.md +++ b/crates/red_knot_python_semantic/resources/mdtest/subscript/bytes.md @@ -22,12 +22,10 @@ reveal_type(x) # revealed: Unknown y = b[-6] # error: [index-out-of-bounds] "Index -6 is out of bounds for bytes literal `Literal[b"\x00abc\xff"]` with length 5" reveal_type(y) # revealed: Unknown -def int_instance() -> int: - return 42 - -a = b"abcde"[int_instance()] -# TODO: Support overloads... Should be `bytes` -reveal_type(a) # revealed: @Todo(return type) +def _(n: int): + a = b"abcde"[n] + # TODO: Support overloads... Should be `bytes` + reveal_type(a) # revealed: @Todo(return type) ``` ## Slices @@ -43,15 +41,13 @@ b[:4:0] # error: [zero-stepsize-in-slice] b[0::0] # error: [zero-stepsize-in-slice] b[::0] # error: [zero-stepsize-in-slice] -def int_instance() -> int: ... - -byte_slice1 = b[int_instance() : int_instance()] -# TODO: Support overloads... Should be `bytes` -reveal_type(byte_slice1) # revealed: @Todo(return type) - -def bytes_instance() -> bytes: ... +def _(m: int, n: int): + byte_slice1 = b[m:n] + # TODO: Support overloads... Should be `bytes` + reveal_type(byte_slice1) # revealed: @Todo(return type) -byte_slice2 = bytes_instance()[0:5] -# TODO: Support overloads... Should be `bytes` -reveal_type(byte_slice2) # revealed: @Todo(return type) +def _(s: bytes) -> bytes: + byte_slice2 = s[0:5] + # TODO: Support overloads... Should be `bytes` + reveal_type(byte_slice2) # revealed: @Todo(return type) ``` diff --git a/crates/red_knot_python_semantic/resources/mdtest/subscript/class.md b/crates/red_knot_python_semantic/resources/mdtest/subscript/class.md index ee26274ae027c..2903153b07f1b 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/subscript/class.md +++ b/crates/red_knot_python_semantic/resources/mdtest/subscript/class.md @@ -21,77 +21,66 @@ reveal_type(Identity[0]) # revealed: str ## Class getitem union ```py -def bool_instance() -> bool: - return True +def _(flag: bool): + class UnionClassGetItem: + if flag: + def __class_getitem__(cls, item: int) -> str: + return item + else: + def __class_getitem__(cls, item: int) -> int: + return item + + reveal_type(UnionClassGetItem[0]) # revealed: str | int +``` -class UnionClassGetItem: - if bool_instance(): +## Class getitem with class union +```py +def _(flag: bool): + class A: def __class_getitem__(cls, item: int) -> str: return item - else: + class B: def __class_getitem__(cls, item: int) -> int: return item -reveal_type(UnionClassGetItem[0]) # revealed: str | int -``` - -## Class getitem with class union - -```py -def bool_instance() -> bool: - return True - -class A: - def __class_getitem__(cls, item: int) -> str: - return item - -class B: - def __class_getitem__(cls, item: int) -> int: - return item - -x = A if bool_instance() else B + x = A if flag else B -reveal_type(x) # revealed: Literal[A, B] -reveal_type(x[0]) # revealed: str | int + reveal_type(x) # revealed: Literal[A, B] + reveal_type(x[0]) # revealed: str | int ``` ## Class getitem with unbound method union ```py -def bool_instance() -> bool: - return True +def _(flag: bool): + if flag: + class Spam: + def __class_getitem__(self, x: int) -> str: + return "foo" -if bool_instance(): - class Spam: - def __class_getitem__(self, x: int) -> str: - return "foo" - -else: - class Spam: ... - -# error: [call-possibly-unbound-method] "Method `__class_getitem__` of type `Literal[Spam, Spam]` is possibly unbound" -# revealed: str -reveal_type(Spam[42]) + else: + class Spam: ... + # error: [call-possibly-unbound-method] "Method `__class_getitem__` of type `Literal[Spam, Spam]` is possibly unbound" + # revealed: str + reveal_type(Spam[42]) ``` ## TODO: Class getitem non-class union ```py -def bool_instance() -> bool: - return True +def _(flag: bool): + if flag: + class Eggs: + def __class_getitem__(self, x: int) -> str: + return "foo" -if bool_instance(): - class Eggs: - def __class_getitem__(self, x: int) -> str: - return "foo" - -else: - Eggs = 1 + else: + Eggs = 1 -a = Eggs[42] # error: "Cannot subscript object of type `Literal[Eggs] | Literal[1]` with no `__getitem__` method" + a = Eggs[42] # error: "Cannot subscript object of type `Literal[Eggs] | Literal[1]` with no `__getitem__` method" -# TODO: should _probably_ emit `str | Unknown` -reveal_type(a) # revealed: Unknown + # TODO: should _probably_ emit `str | Unknown` + reveal_type(a) # revealed: Unknown ``` diff --git a/crates/red_knot_python_semantic/resources/mdtest/subscript/instance.md b/crates/red_knot_python_semantic/resources/mdtest/subscript/instance.md index b9d17ffb002f1..eeb251dbcbbe7 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/subscript/instance.md +++ b/crates/red_knot_python_semantic/resources/mdtest/subscript/instance.md @@ -30,18 +30,14 @@ reveal_type(Identity()[0]) # revealed: int ## Getitem union ```py -def bool_instance() -> bool: - return True - -class Identity: - if bool_instance(): - - def __getitem__(self, index: int) -> int: - return index - else: - - def __getitem__(self, index: int) -> str: - return str(index) - -reveal_type(Identity()[0]) # revealed: int | str +def _(flag: bool): + class Identity: + if flag: + def __getitem__(self, index: int) -> int: + return index + else: + def __getitem__(self, index: int) -> str: + return str(index) + + reveal_type(Identity()[0]) # revealed: int | str ``` diff --git a/crates/red_knot_python_semantic/resources/mdtest/subscript/string.md b/crates/red_knot_python_semantic/resources/mdtest/subscript/string.md index 46274074596c8..f6fa36ce7c6a2 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/subscript/string.md +++ b/crates/red_knot_python_semantic/resources/mdtest/subscript/string.md @@ -19,11 +19,10 @@ reveal_type(a) # revealed: Unknown b = s[-8] # error: [index-out-of-bounds] "Index -8 is out of bounds for string `Literal["abcde"]` with length 5" reveal_type(b) # revealed: Unknown -def int_instance() -> int: ... - -a = "abcde"[int_instance()] -# TODO: Support overloads... Should be `str` -reveal_type(a) # revealed: @Todo(return type) +def _(n: int): + a = "abcde"[n] + # TODO: Support overloads... Should be `str` + reveal_type(a) # revealed: @Todo(return type) ``` ## Slices @@ -74,17 +73,14 @@ s[:4:0] # error: [zero-stepsize-in-slice] s[0::0] # error: [zero-stepsize-in-slice] s[::0] # error: [zero-stepsize-in-slice] -def int_instance() -> int: ... - -substring1 = s[int_instance() : int_instance()] -# TODO: Support overloads... Should be `LiteralString` -reveal_type(substring1) # revealed: @Todo(return type) - -def str_instance() -> str: ... +def _(m: int, n: int, s2: str): + substring1 = s[m:n] + # TODO: Support overloads... Should be `LiteralString` + reveal_type(substring1) # revealed: @Todo(return type) -substring2 = str_instance()[0:5] -# TODO: Support overloads... Should be `str` -reveal_type(substring2) # revealed: @Todo(return type) + substring2 = s2[0:5] + # TODO: Support overloads... Should be `str` + reveal_type(substring2) # revealed: @Todo(return type) ``` ## Unsupported slice types diff --git a/crates/red_knot_python_semantic/resources/mdtest/subscript/tuple.md b/crates/red_knot_python_semantic/resources/mdtest/subscript/tuple.md index cb088f009fd74..5582fa8a920c4 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/subscript/tuple.md +++ b/crates/red_knot_python_semantic/resources/mdtest/subscript/tuple.md @@ -67,9 +67,8 @@ t[:4:0] # error: [zero-stepsize-in-slice] t[0::0] # error: [zero-stepsize-in-slice] t[::0] # error: [zero-stepsize-in-slice] -def int_instance() -> int: ... - -tuple_slice = t[int_instance() : int_instance()] -# TODO: Support overloads... Should be `tuple[Literal[1, 'a', b"b"] | None, ...]` -reveal_type(tuple_slice) # revealed: @Todo(return type) +def _(m: int, n: int): + tuple_slice = t[m:n] + # TODO: Support overloads... Should be `tuple[Literal[1, 'a', b"b"] | None, ...]` + reveal_type(tuple_slice) # revealed: @Todo(return type) ``` diff --git a/crates/red_knot_python_semantic/resources/mdtest/type_of/basic.md b/crates/red_knot_python_semantic/resources/mdtest/type_of/basic.md index 642e070c7934a..723c8a904973f 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/type_of/basic.md +++ b/crates/red_knot_python_semantic/resources/mdtest/type_of/basic.md @@ -5,10 +5,8 @@ ```py class A: ... -def f() -> type[A]: - return A - -reveal_type(f()) # revealed: type[A] +def _(c: type[A]): + reveal_type(c) # revealed: type[A] ``` ## Nested class literal @@ -17,10 +15,8 @@ reveal_type(f()) # revealed: type[A] class A: class B: ... -def f() -> type[A.B]: - return A.B - -reveal_type(f()) # revealed: type[B] +def f(c: type[A.B]): + reveal_type(c) # revealed: type[B] ``` ## Deeply nested class literal @@ -30,10 +26,8 @@ class A: class B: class C: ... -def f() -> type[A.B.C]: - return A.B.C - -reveal_type(f()) # revealed: type[C] +def f(c: type[A.B.C]): + reveal_type(c) # revealed: type[C] ``` ## Class literal from another module @@ -41,10 +35,8 @@ reveal_type(f()) # revealed: type[C] ```py from a import A -def f() -> type[A]: - return A - -reveal_type(f()) # revealed: type[A] +def f(c: type[A]): + reveal_type(c) # revealed: type[A] ``` ```py path=a.py @@ -56,10 +48,8 @@ class A: ... ```py import a -def f() -> type[a.B]: - return a.B - -reveal_type(f()) # revealed: type[B] +def f(c: type[a.B]): + reveal_type(c) # revealed: type[B] ``` ```py path=a.py @@ -73,12 +63,8 @@ import a.b # TODO: no diagnostic # error: [unresolved-attribute] -def f() -> type[a.b.C]: - # TODO: no diagnostic - # error: [unresolved-attribute] - return a.b.C - -reveal_type(f()) # revealed: @Todo(unsupported type[X] special form) +def f(c: type[a.b.C]): + reveal_type(c) # revealed: @Todo(unsupported type[X] special form) ``` ```py path=a/__init__.py @@ -98,11 +84,9 @@ class A: class B: class C: ... -def get_user() -> type[BasicUser | ProUser | A.B.C]: - return BasicUser - -# revealed: type[BasicUser] | type[ProUser] | type[C] -reveal_type(get_user()) +def _(u: type[BasicUser | ProUser | A.B.C]): + # revealed: type[BasicUser] | type[ProUser] | type[C] + reveal_type(u) ``` ## Old-style union of classes @@ -147,6 +131,5 @@ class A: ... class B: ... # error: [invalid-type-form] -def get_user() -> type[A, B]: - return A +_: type[A, B] ``` diff --git a/crates/red_knot_python_semantic/resources/mdtest/unary/not.md b/crates/red_knot_python_semantic/resources/mdtest/unary/not.md index a193437328326..0b887ee036458 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/unary/not.md +++ b/crates/red_knot_python_semantic/resources/mdtest/unary/not.md @@ -35,29 +35,25 @@ y = 1 ## Union ```py -def bool_instance() -> bool: - return True - -flag = bool_instance() - -if flag: - p = 1 - q = 3.3 - r = "hello" - s = "world" - t = 0 -else: - p = "hello" - q = 4 - r = "" - s = 0 - t = "" - -reveal_type(not p) # revealed: Literal[False] -reveal_type(not q) # revealed: bool -reveal_type(not r) # revealed: bool -reveal_type(not s) # revealed: bool -reveal_type(not t) # revealed: Literal[True] +def _(flag: bool): + if flag: + p = 1 + q = 3.3 + r = "hello" + s = "world" + t = 0 + else: + p = "hello" + q = 4 + r = "" + s = 0 + t = "" + + reveal_type(not p) # revealed: Literal[False] + reveal_type(not q) # revealed: bool + reveal_type(not r) # revealed: bool + reveal_type(not s) # revealed: bool + reveal_type(not t) # revealed: Literal[True] ``` ## Integer literal diff --git a/crates/red_knot_python_semantic/resources/mdtest/with/sync.md b/crates/red_knot_python_semantic/resources/mdtest/with/sync.md index ab7745223b980..408a9f9c232e3 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/with/sync.md +++ b/crates/red_knot_python_semantic/resources/mdtest/with/sync.md @@ -21,25 +21,23 @@ with Manager() as f: ## Union context manager ```py -def coinflip() -> bool: - return True - -class Manager1: - def __enter__(self) -> str: - return "foo" +def _(flag: bool): + class Manager1: + def __enter__(self) -> str: + return "foo" - def __exit__(self, exc_type, exc_value, traceback): ... + def __exit__(self, exc_type, exc_value, traceback): ... -class Manager2: - def __enter__(self) -> int: - return 42 + class Manager2: + def __enter__(self) -> int: + return 42 - def __exit__(self, exc_type, exc_value, traceback): ... + def __exit__(self, exc_type, exc_value, traceback): ... -context_expr = Manager1() if coinflip() else Manager2() + context_expr = Manager1() if flag else Manager2() -with context_expr as f: - reveal_type(f) # revealed: str | int + with context_expr as f: + reveal_type(f) # revealed: str | int ``` ## Context manager without an `__enter__` or `__exit__` method @@ -103,39 +101,34 @@ with Manager(): ## Context expression with possibly-unbound union variants ```py -def coinflip() -> bool: - return True - -class Manager1: - def __enter__(self) -> str: - return "foo" - - def __exit__(self, exc_type, exc_value, traceback): ... +def _(flag: bool): + class Manager1: + def __enter__(self) -> str: + return "foo" -class NotAContextManager: ... + def __exit__(self, exc_type, exc_value, traceback): ... -context_expr = Manager1() if coinflip() else NotAContextManager() + class NotAContextManager: ... + context_expr = Manager1() if flag else NotAContextManager() -# error: [invalid-context-manager] "Object of type `Manager1 | NotAContextManager` cannot be used with `with` because the method `__enter__` is possibly unbound" -# error: [invalid-context-manager] "Object of type `Manager1 | NotAContextManager` cannot be used with `with` because the method `__exit__` is possibly unbound" -with context_expr as f: - reveal_type(f) # revealed: str + # error: [invalid-context-manager] "Object of type `Manager1 | NotAContextManager` cannot be used with `with` because the method `__enter__` is possibly unbound" + # error: [invalid-context-manager] "Object of type `Manager1 | NotAContextManager` cannot be used with `with` because the method `__exit__` is possibly unbound" + with context_expr as f: + reveal_type(f) # revealed: str ``` ## Context expression with "sometimes" callable `__enter__` method ```py -def coinflip() -> bool: - return True - -class Manager: - if coinflip(): - def __enter__(self) -> str: - return "abcd" +def _(flag: bool): + class Manager: + if flag: + def __enter__(self) -> str: + return "abcd" - def __exit__(self, *args): ... + def __exit__(self, *args): ... -# error: [invalid-context-manager] "Object of type `Manager` cannot be used with `with` because the method `__enter__` is possibly unbound" -with Manager() as f: - reveal_type(f) # revealed: str + # error: [invalid-context-manager] "Object of type `Manager` cannot be used with `with` because the method `__enter__` is possibly unbound" + with Manager() as f: + reveal_type(f) # revealed: str ```