Skip to content

Commit

Permalink
Refactor CallOutcome to Result (#16161)
Browse files Browse the repository at this point in the history
  • Loading branch information
MichaReiser authored Feb 18, 2025
1 parent 5cd0de3 commit 4ed5db0
Show file tree
Hide file tree
Showing 19 changed files with 719 additions and 739 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ class C:
return 42

x = C()
# error: [invalid-argument-type]
# error: [unsupported-operator] "Operator `-=` is unsupported between objects of type `C` and `Literal[1]`"
x -= 1

reveal_type(x) # revealed: int
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -244,10 +244,7 @@ class B:
def __rsub__(self, other: A) -> B:
return B()

# TODO: this should be `B` (the return annotation of `B.__rsub__`),
# because `A.__sub__` is annotated as only accepting `A`,
# but `B.__rsub__` will accept `A`.
reveal_type(A() - B()) # revealed: A
reveal_type(A() - B()) # revealed: B
```

## Callable instances as dunders
Expand All @@ -263,7 +260,10 @@ class B:
__add__ = A()

# TODO: this could be `int` if we declare `B.__add__` using a `Callable` type
reveal_type(B() + B()) # revealed: Unknown | int
# TODO: Should not be an error: `A` instance is not a method descriptor, don't prepend `self` arg.
# Revealed type should be `Unknown | int`.
# error: [unsupported-operator] "Operator `+` is unsupported between objects of type `B` and `B`"
reveal_type(B() + B()) # revealed: Unknown
```

## Integration test: numbers from typeshed
Expand All @@ -277,22 +277,14 @@ return annotations from the widening, and preserve a bit more precision here?
reveal_type(3j + 3.14) # revealed: int | float | complex
reveal_type(4.2 + 42) # revealed: int | float
reveal_type(3j + 3) # revealed: int | float | complex

# TODO should be int | float | complex, need to check arg type and fall back to `rhs.__radd__`
reveal_type(3.14 + 3j) # revealed: int | float

# TODO should be int | float, need to check arg type and fall back to `rhs.__radd__`
reveal_type(42 + 4.2) # revealed: int

# TODO should be int | float | complex, need to check arg type and fall back to `rhs.__radd__`
reveal_type(3 + 3j) # revealed: int
reveal_type(3.14 + 3j) # revealed: int | float | complex
reveal_type(42 + 4.2) # revealed: int | float
reveal_type(3 + 3j) # revealed: int | float | complex

def _(x: bool, y: int):
reveal_type(x + y) # revealed: int
reveal_type(4.2 + x) # revealed: int | float

# TODO should be float, need to check arg type and fall back to `rhs.__radd__`
reveal_type(y + 4.12) # revealed: int
reveal_type(y + 4.12) # revealed: int | float
```

## With literal types
Expand All @@ -309,8 +301,7 @@ class A:
return self

reveal_type(A() + 1) # revealed: A
# TODO should be `A` since `int.__add__` doesn't support `A` instances
reveal_type(1 + A()) # revealed: int
reveal_type(1 + A()) # revealed: A

reveal_type(A() + "foo") # revealed: A
# TODO should be `A` since `str.__add__` doesn't support `A` instances
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,10 @@ reveal_type(-3 // 3) # revealed: Literal[-1]
reveal_type(-3 / 3) # revealed: float
reveal_type(5 % 3) # revealed: Literal[2]

# TODO: We don't currently verify that the actual parameter to int.__add__ matches the declared
# formal parameter type.
reveal_type(2 + "f") # revealed: int
# TODO: This should emit an unsupported-operator error but we don't currently
# verify that the actual parameter to `int.__add__` matches the declared
# formal parameter type.
reveal_type(2 + "f") # revealed: Unknown

def lhs(x: int):
reveal_type(x + 1) # revealed: int
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ class NonCallable:
__call__ = 1

a = NonCallable()
# error: "Object of type `Unknown | Literal[1]` is not callable (due to union element `Literal[1]`)"
# error: [call-non-callable] "Object of type `Literal[1]` is not callable"
reveal_type(a()) # revealed: Unknown
```

Expand All @@ -67,8 +67,8 @@ def _(flag: bool):
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
# error: [call-non-callable] "Object of type `Literal[1]` is not callable"
reveal_type(a()) # revealed: int | Unknown
```

## Call binding errors
Expand Down Expand Up @@ -99,3 +99,26 @@ c = C()
# error: 13 [invalid-argument-type] "Object of type `C` cannot be assigned to parameter 1 (`self`) of function `__call__`; expected type `int`"
reveal_type(c()) # revealed: int
```

## Union over callables

### Possibly unbound `__call__`

```py
def outer(cond1: bool):
class Test:
if cond1:
def __call__(self): ...

class Other:
def __call__(self): ...

def inner(cond2: bool):
if cond2:
a = Test()
else:
a = Other()

# error: [call-non-callable] "Object of type `Test` is not callable (possibly unbound `__call__` method)"
a()
```
Original file line number Diff line number Diff line change
Expand Up @@ -278,10 +278,10 @@ proper diagnostics in case of missing or superfluous arguments.
from typing_extensions import reveal_type

# error: [missing-argument] "No argument provided for required parameter `obj` of function `reveal_type`"
reveal_type() # revealed: Unknown
reveal_type()

# error: [too-many-positional-arguments] "Too many positional arguments to function `reveal_type`: expected 1, got 2"
reveal_type(1, 2) # revealed: Literal[1]
reveal_type(1, 2)
```

### `static_assert`
Expand All @@ -290,7 +290,6 @@ reveal_type(1, 2) # revealed: Literal[1]
from knot_extensions import static_assert

# error: [missing-argument] "No argument provided for required parameter `condition` of function `static_assert`"
# error: [static-assert-error]
static_assert()

# error: [too-many-positional-arguments] "Too many positional arguments to function `static_assert`: expected 2, got 3"
Expand Down
43 changes: 38 additions & 5 deletions crates/red_knot_python_semantic/resources/mdtest/call/union.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,8 @@ def _(flag: bool):
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
x = f() # error: [call-non-callable] "Object of type `Literal[1]` is not callable"
reveal_type(x) # revealed: int | Unknown
```

## Multiple non-callable elements in a union
Expand All @@ -56,8 +56,8 @@ def _(flag: bool, flag2: bool):
else:
def f() -> int:
return 1
# error: "Object of type `Literal[1, "foo"] | Literal[f]` is not callable (due to union elements Literal[1], Literal["foo"])"
# revealed: Unknown | int
# error: [call-non-callable] "Object of type `Literal[1]` is not callable"
# revealed: int | Unknown
reveal_type(f())
```

Expand All @@ -72,6 +72,39 @@ def _(flag: bool):
else:
f = "foo"

x = f() # error: "Object of type `Literal[1, "foo"]` is not callable"
x = f() # error: [call-non-callable] "Object of type `Literal[1, "foo"]` is not callable"
reveal_type(x) # revealed: Unknown
```

## Mismatching signatures

Calling a union where the arguments don't match the signature of all variants.

```py
def f1(a: int) -> int: ...
def f2(a: str) -> str: ...
def _(flag: bool):
if flag:
f = f1
else:
f = f2

# error: [invalid-argument-type] "Object of type `Literal[3]` cannot be assigned to parameter 1 (`a`) of function `f2`; expected type `str`"
x = f(3)
reveal_type(x) # revealed: int | str
```

## Any non-callable variant

```py
def f1(a: int): ...
def _(flag: bool):
if flag:
f = f1
else:
f = "This is a string literal"

# error: [call-non-callable] "Object of type `Literal["This is a string literal"]` is not callable"
x = f(3)
reveal_type(x) # revealed: Unknown
```
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,9 @@ class A:

reveal_type("hello" in A()) # revealed: bool
reveal_type("hello" not in A()) # revealed: bool
# TODO: should emit diagnostic, need to check arg type, will fail
# error: [unsupported-operator] "Operator `in` is not supported for types `int` and `A`, in comparing `Literal[42]` with `A`"
reveal_type(42 in A()) # revealed: bool
# error: [unsupported-operator] "Operator `not in` is not supported for types `int` and `A`, in comparing `Literal[42]` with `A`"
reveal_type(42 not in A()) # revealed: bool
```

Expand Down Expand Up @@ -126,9 +127,9 @@ class A:

reveal_type(CheckContains() in A()) # revealed: bool

# TODO: should emit diagnostic, need to check arg type,
# should not fall back to __iter__ or __getitem__
# error: [unsupported-operator] "Operator `in` is not supported for types `CheckIter` and `A`"
reveal_type(CheckIter() in A()) # revealed: bool
# error: [unsupported-operator] "Operator `in` is not supported for types `CheckGetItem` and `A`"
reveal_type(CheckGetItem() in A()) # revealed: bool

class B:
Expand All @@ -154,7 +155,8 @@ class A:
def __getitem__(self, key: str) -> str:
return "foo"

# TODO should emit a diagnostic
# error: [unsupported-operator] "Operator `in` is not supported for types `int` and `A`, in comparing `Literal[42]` with `A`"
reveal_type(42 in A()) # revealed: bool
# error: [unsupported-operator] "Operator `in` is not supported for types `str` and `A`, in comparing `Literal["hello"]` with `A`"
reveal_type("hello" in A()) # revealed: bool
```
Original file line number Diff line number Diff line change
Expand Up @@ -117,14 +117,11 @@ class B:
def __ne__(self, other: str) -> B:
return B()

# TODO: should be `int` and `bytearray`.
# Need to check arg type and fall back to `rhs.__eq__` and `rhs.__ne__`.
#
# Because `object.__eq__` and `object.__ne__` accept `object` in typeshed,
# this can only happen with an invalid override of these methods,
# but we still support it.
reveal_type(B() == A()) # revealed: B
reveal_type(B() != A()) # revealed: B
reveal_type(B() == A()) # revealed: int
reveal_type(B() != A()) # revealed: bytearray

reveal_type(B() < A()) # revealed: list
reveal_type(B() <= A()) # revealed: set
Expand Down Expand Up @@ -222,9 +219,8 @@ class B(A):
def __gt__(self, other: int) -> B:
return B()

# TODO: should be `A`, need to check argument type and fall back to LHS method
reveal_type(A() < B()) # revealed: B
reveal_type(A() > B()) # revealed: B
reveal_type(A() < B()) # revealed: A
reveal_type(A() > B()) # revealed: A
```

## Operations involving instances of classes inheriting from `Any`
Expand Down Expand Up @@ -272,9 +268,8 @@ class A:
def __ne__(self, other: int) -> A:
return A()

# TODO: it should be `bool`, need to check arg type and fall back to `is` and `is not`
reveal_type(A() == A()) # revealed: A
reveal_type(A() != A()) # revealed: A
reveal_type(A() == A()) # revealed: bool
reveal_type(A() != A()) # revealed: bool
```

## Object Comparisons with Typeshed
Expand Down Expand Up @@ -305,12 +300,14 @@ reveal_type(1 >= 1.0) # revealed: bool
reveal_type(1 == 2j) # revealed: bool
reveal_type(1 != 2j) # revealed: bool

# TODO: should be Unknown and emit diagnostic,
# need to check arg type and should be failed
reveal_type(1 < 2j) # revealed: bool
reveal_type(1 <= 2j) # revealed: bool
reveal_type(1 > 2j) # revealed: bool
reveal_type(1 >= 2j) # revealed: bool
# error: [unsupported-operator] "Operator `<` is not supported for types `int` and `complex`, in comparing `Literal[1]` with `complex`"
reveal_type(1 < 2j) # revealed: Unknown
# error: [unsupported-operator] "Operator `<=` is not supported for types `int` and `complex`, in comparing `Literal[1]` with `complex`"
reveal_type(1 <= 2j) # revealed: Unknown
# error: [unsupported-operator] "Operator `>` is not supported for types `int` and `complex`, in comparing `Literal[1]` with `complex`"
reveal_type(1 > 2j) # revealed: Unknown
# error: [unsupported-operator] "Operator `>=` is not supported for types `int` and `complex`, in comparing `Literal[1]` with `complex`"
reveal_type(1 >= 2j) # revealed: Unknown

def f(x: bool, y: int):
reveal_type(x < y) # revealed: bool
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ reveal_type(1 is 1) # revealed: bool
reveal_type(1 is not 1) # revealed: bool
reveal_type(1 is 2) # revealed: Literal[False]
reveal_type(1 is not 7) # revealed: Literal[True]
# TODO: should be Unknown, and emit diagnostic, once we check call argument types
reveal_type(1 <= "" and 0 < 1) # revealed: bool
# error: [unsupported-operator] "Operator `<=` is not supported for types `int` and `str`, in comparing `Literal[1]` with `Literal[""]`"
reveal_type(1 <= "" and 0 < 1) # revealed: Unknown & ~AlwaysTruthy | Literal[True]
```

## Integer instance
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@ types, we can infer that the result for the intersection type is also true/false
```py
from typing import Literal

class Base: ...
class Base:
def __gt__(self, other) -> bool:
return False

class Child1(Base):
def __eq__(self, other) -> Literal[True]:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ from __future__ import annotations

class A:
def __lt__(self, other) -> A: ...
def __gt__(self, other) -> bool: ...

class B:
def __lt__(self, other) -> B: ...
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,11 +92,14 @@ reveal_type(a == b) # revealed: bool
# TODO: should be Literal[True], once we implement (in)equality for mismatched literals
reveal_type(a != b) # revealed: bool

# TODO: should be Unknown and add more informative diagnostics
reveal_type(a < b) # revealed: bool
reveal_type(a <= b) # revealed: bool
reveal_type(a > b) # revealed: bool
reveal_type(a >= b) # revealed: bool
# error: [unsupported-operator] "Operator `<` is not supported for types `int` and `str`, in comparing `tuple[Literal[1], Literal[2]]` with `tuple[Literal[1], Literal["hello"]]`"
reveal_type(a < b) # revealed: Unknown
# error: [unsupported-operator] "Operator `<=` is not supported for types `int` and `str`, in comparing `tuple[Literal[1], Literal[2]]` with `tuple[Literal[1], Literal["hello"]]`"
reveal_type(a <= b) # revealed: Unknown
# error: [unsupported-operator] "Operator `>` is not supported for types `int` and `str`, in comparing `tuple[Literal[1], Literal[2]]` with `tuple[Literal[1], Literal["hello"]]`"
reveal_type(a > b) # revealed: Unknown
# error: [unsupported-operator] "Operator `>=` is not supported for types `int` and `str`, in comparing `tuple[Literal[1], Literal[2]]` with `tuple[Literal[1], Literal["hello"]]`"
reveal_type(a >= b) # revealed: Unknown
```

However, if the lexicographic comparison completes without reaching a point where str and int are
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,28 +9,22 @@ def _(flag: bool, flag1: bool, flag2: 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`")
# error: [unsupported-operator] "Operator `<` is not supported for types `object` and `int`, in comparing `object` with `Literal[5]`"
c = object() < 5
# TODO: should be Unknown, once operand type check is implemented
reveal_type(c) # revealed: bool
reveal_type(c) # revealed: Unknown

# TODO: should error, once operand type check is implemented
# ("Operator `<` is not supported for types `int` and `object`")
# error: [unsupported-operator] "Operator `<` is not supported for types `int` and `object`, in comparing `Literal[5]` with `object`"
d = 5 < object()
# TODO: should be Unknown, once operand type check is implemented
reveal_type(d) # revealed: bool
reveal_type(d) # revealed: Unknown

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, "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"]]`
# error: [unsupported-operator] "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
reveal_type(f) # revealed: Unknown

# 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())
Expand Down
Loading

0 comments on commit 4ed5db0

Please sign in to comment.