Compare commits
1 Commits
alex/subsc
...
david/repr
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d3ad7722f7 |
@@ -1,24 +1,12 @@
|
||||
# Call expression
|
||||
|
||||
## Simple
|
||||
## A
|
||||
|
||||
```py
|
||||
def get_int() -> int:
|
||||
return 42
|
||||
|
||||
reveal_type(get_int()) # revealed: int
|
||||
reveal_type(1)
|
||||
```
|
||||
|
||||
## Async
|
||||
|
||||
```py
|
||||
async def get_int_async() -> int:
|
||||
return 42
|
||||
|
||||
reveal_type(get_int_async()) # revealed: CoroutineType[Any, Any, int]
|
||||
```
|
||||
|
||||
## Generic
|
||||
## B
|
||||
|
||||
```toml
|
||||
[environment]
|
||||
@@ -26,618 +14,5 @@ python-version = "3.12"
|
||||
```
|
||||
|
||||
```py
|
||||
def get_int[T]() -> int:
|
||||
return 42
|
||||
|
||||
reveal_type(get_int()) # revealed: int
|
||||
```
|
||||
|
||||
## Decorated
|
||||
|
||||
```py
|
||||
from typing import Callable
|
||||
|
||||
def foo() -> int:
|
||||
return 42
|
||||
|
||||
def decorator(func) -> Callable[[], int]:
|
||||
return foo
|
||||
|
||||
@decorator
|
||||
def bar() -> str:
|
||||
return "bar"
|
||||
|
||||
reveal_type(bar()) # revealed: int
|
||||
```
|
||||
|
||||
## Invalid callable
|
||||
|
||||
```py
|
||||
nonsense = 123
|
||||
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
|
||||
```
|
||||
|
||||
## Splatted arguments
|
||||
|
||||
### Unknown argument length
|
||||
|
||||
```py
|
||||
def takes_zero() -> None: ...
|
||||
def takes_one(x: int) -> None: ...
|
||||
def takes_two(x: int, y: int) -> None: ...
|
||||
def takes_two_positional_only(x: int, y: int, /) -> None: ...
|
||||
def takes_two_different(x: int, y: str) -> None: ...
|
||||
def takes_two_different_positional_only(x: int, y: str, /) -> None: ...
|
||||
def takes_at_least_zero(*args) -> None: ...
|
||||
def takes_at_least_one(x: int, *args) -> None: ...
|
||||
def takes_at_least_two(x: int, y: int, *args) -> None: ...
|
||||
def takes_at_least_two_positional_only(x: int, y: int, /, *args) -> None: ...
|
||||
|
||||
# Test all of the above with a number of different splatted argument types
|
||||
|
||||
def _(args: list[int]) -> None:
|
||||
takes_zero(*args)
|
||||
takes_one(*args)
|
||||
takes_two(*args)
|
||||
takes_two_positional_only(*args)
|
||||
takes_two_different(*args) # error: [invalid-argument-type]
|
||||
takes_two_different_positional_only(*args) # error: [invalid-argument-type]
|
||||
takes_at_least_zero(*args)
|
||||
takes_at_least_one(*args)
|
||||
takes_at_least_two(*args)
|
||||
takes_at_least_two_positional_only(*args)
|
||||
|
||||
def _(args: tuple[int, ...]) -> None:
|
||||
takes_zero(*args)
|
||||
takes_one(*args)
|
||||
takes_two(*args)
|
||||
takes_two_positional_only(*args)
|
||||
takes_two_different(*args) # error: [invalid-argument-type]
|
||||
takes_two_different_positional_only(*args) # error: [invalid-argument-type]
|
||||
takes_at_least_zero(*args)
|
||||
takes_at_least_one(*args)
|
||||
takes_at_least_two(*args)
|
||||
takes_at_least_two_positional_only(*args)
|
||||
```
|
||||
|
||||
### Fixed-length tuple argument
|
||||
|
||||
```py
|
||||
def takes_zero() -> None: ...
|
||||
def takes_one(x: int) -> None: ...
|
||||
def takes_two(x: int, y: int) -> None: ...
|
||||
def takes_two_positional_only(x: int, y: int, /) -> None: ...
|
||||
def takes_two_different(x: int, y: str) -> None: ...
|
||||
def takes_two_different_positional_only(x: int, y: str, /) -> None: ...
|
||||
def takes_at_least_zero(*args) -> None: ...
|
||||
def takes_at_least_one(x: int, *args) -> None: ...
|
||||
def takes_at_least_two(x: int, y: int, *args) -> None: ...
|
||||
def takes_at_least_two_positional_only(x: int, y: int, /, *args) -> None: ...
|
||||
|
||||
# Test all of the above with a number of different splatted argument types
|
||||
|
||||
def _(args: tuple[int]) -> None:
|
||||
takes_zero(*args) # error: [too-many-positional-arguments]
|
||||
takes_one(*args)
|
||||
takes_two(*args) # error: [missing-argument]
|
||||
takes_two_positional_only(*args) # error: [missing-argument]
|
||||
takes_two_different(*args) # error: [missing-argument]
|
||||
takes_two_different_positional_only(*args) # error: [missing-argument]
|
||||
takes_at_least_zero(*args)
|
||||
takes_at_least_one(*args)
|
||||
takes_at_least_two(*args) # error: [missing-argument]
|
||||
takes_at_least_two_positional_only(*args) # error: [missing-argument]
|
||||
|
||||
def _(args: tuple[int, int]) -> None:
|
||||
takes_zero(*args) # error: [too-many-positional-arguments]
|
||||
takes_one(*args) # error: [too-many-positional-arguments]
|
||||
takes_two(*args)
|
||||
takes_two_positional_only(*args)
|
||||
takes_two_different(*args) # error: [invalid-argument-type]
|
||||
takes_two_different_positional_only(*args) # error: [invalid-argument-type]
|
||||
takes_at_least_zero(*args)
|
||||
takes_at_least_one(*args)
|
||||
takes_at_least_two(*args)
|
||||
takes_at_least_two_positional_only(*args)
|
||||
|
||||
def _(args: tuple[int, str]) -> None:
|
||||
takes_zero(*args) # error: [too-many-positional-arguments]
|
||||
takes_one(*args) # error: [too-many-positional-arguments]
|
||||
takes_two(*args) # error: [invalid-argument-type]
|
||||
takes_two_positional_only(*args) # error: [invalid-argument-type]
|
||||
takes_two_different(*args)
|
||||
takes_two_different_positional_only(*args)
|
||||
takes_at_least_zero(*args)
|
||||
takes_at_least_one(*args)
|
||||
takes_at_least_two(*args) # error: [invalid-argument-type]
|
||||
takes_at_least_two_positional_only(*args) # error: [invalid-argument-type]
|
||||
```
|
||||
|
||||
### Mixed tuple argument
|
||||
|
||||
```toml
|
||||
[environment]
|
||||
python-version = "3.11"
|
||||
```
|
||||
|
||||
```py
|
||||
def takes_zero() -> None: ...
|
||||
def takes_one(x: int) -> None: ...
|
||||
def takes_two(x: int, y: int) -> None: ...
|
||||
def takes_two_positional_only(x: int, y: int, /) -> None: ...
|
||||
def takes_two_different(x: int, y: str) -> None: ...
|
||||
def takes_two_different_positional_only(x: int, y: str, /) -> None: ...
|
||||
def takes_at_least_zero(*args) -> None: ...
|
||||
def takes_at_least_one(x: int, *args) -> None: ...
|
||||
def takes_at_least_two(x: int, y: int, *args) -> None: ...
|
||||
def takes_at_least_two_positional_only(x: int, y: int, /, *args) -> None: ...
|
||||
|
||||
# Test all of the above with a number of different splatted argument types
|
||||
|
||||
def _(args: tuple[int, *tuple[int, ...]]) -> None:
|
||||
takes_zero(*args) # error: [too-many-positional-arguments]
|
||||
takes_one(*args)
|
||||
takes_two(*args)
|
||||
takes_two_positional_only(*args)
|
||||
takes_two_different(*args) # error: [invalid-argument-type]
|
||||
takes_two_different_positional_only(*args) # error: [invalid-argument-type]
|
||||
takes_at_least_zero(*args)
|
||||
takes_at_least_one(*args)
|
||||
takes_at_least_two(*args)
|
||||
takes_at_least_two_positional_only(*args)
|
||||
|
||||
def _(args: tuple[int, *tuple[str, ...]]) -> None:
|
||||
takes_zero(*args) # error: [too-many-positional-arguments]
|
||||
takes_one(*args)
|
||||
takes_two(*args) # error: [invalid-argument-type]
|
||||
takes_two_positional_only(*args) # error: [invalid-argument-type]
|
||||
takes_two_different(*args)
|
||||
takes_two_different_positional_only(*args)
|
||||
takes_at_least_zero(*args)
|
||||
takes_at_least_one(*args)
|
||||
takes_at_least_two(*args) # error: [invalid-argument-type]
|
||||
takes_at_least_two_positional_only(*args) # error: [invalid-argument-type]
|
||||
|
||||
def _(args: tuple[int, int, *tuple[int, ...]]) -> None:
|
||||
takes_zero(*args) # error: [too-many-positional-arguments]
|
||||
takes_one(*args) # error: [too-many-positional-arguments]
|
||||
takes_two(*args)
|
||||
takes_two_positional_only(*args)
|
||||
takes_two_different(*args) # error: [invalid-argument-type]
|
||||
takes_two_different_positional_only(*args) # error: [invalid-argument-type]
|
||||
takes_at_least_zero(*args)
|
||||
takes_at_least_one(*args)
|
||||
takes_at_least_two(*args)
|
||||
takes_at_least_two_positional_only(*args)
|
||||
|
||||
def _(args: tuple[int, int, *tuple[str, ...]]) -> None:
|
||||
takes_zero(*args) # error: [too-many-positional-arguments]
|
||||
takes_one(*args) # error: [too-many-positional-arguments]
|
||||
takes_two(*args)
|
||||
takes_two_positional_only(*args)
|
||||
takes_two_different(*args) # error: [invalid-argument-type]
|
||||
takes_two_different_positional_only(*args) # error: [invalid-argument-type]
|
||||
takes_at_least_zero(*args)
|
||||
takes_at_least_one(*args)
|
||||
takes_at_least_two(*args)
|
||||
takes_at_least_two_positional_only(*args)
|
||||
|
||||
def _(args: tuple[int, *tuple[int, ...], int]) -> None:
|
||||
takes_zero(*args) # error: [too-many-positional-arguments]
|
||||
takes_one(*args) # error: [too-many-positional-arguments]
|
||||
takes_two(*args)
|
||||
takes_two_positional_only(*args)
|
||||
takes_two_different(*args) # error: [invalid-argument-type]
|
||||
takes_two_different_positional_only(*args) # error: [invalid-argument-type]
|
||||
takes_at_least_zero(*args)
|
||||
takes_at_least_one(*args)
|
||||
takes_at_least_two(*args)
|
||||
takes_at_least_two_positional_only(*args)
|
||||
|
||||
def _(args: tuple[int, *tuple[str, ...], int]) -> None:
|
||||
takes_zero(*args) # error: [too-many-positional-arguments]
|
||||
takes_one(*args) # error: [too-many-positional-arguments]
|
||||
takes_two(*args) # error: [invalid-argument-type]
|
||||
takes_two_positional_only(*args) # error: [invalid-argument-type]
|
||||
takes_two_different(*args)
|
||||
takes_two_different_positional_only(*args)
|
||||
takes_at_least_zero(*args)
|
||||
takes_at_least_one(*args)
|
||||
takes_at_least_two(*args) # error: [invalid-argument-type]
|
||||
takes_at_least_two_positional_only(*args) # error: [invalid-argument-type]
|
||||
```
|
||||
|
||||
### String argument
|
||||
|
||||
```py
|
||||
from typing import Literal
|
||||
|
||||
def takes_zero() -> None: ...
|
||||
def takes_one(x: str) -> None: ...
|
||||
def takes_two(x: str, y: str) -> None: ...
|
||||
def takes_two_positional_only(x: str, y: str, /) -> None: ...
|
||||
def takes_two_different(x: int, y: str) -> None: ...
|
||||
def takes_two_different_positional_only(x: int, y: str, /) -> None: ...
|
||||
def takes_at_least_zero(*args) -> None: ...
|
||||
def takes_at_least_one(x: str, *args) -> None: ...
|
||||
def takes_at_least_two(x: str, y: str, *args) -> None: ...
|
||||
def takes_at_least_two_positional_only(x: str, y: str, /, *args) -> None: ...
|
||||
|
||||
# Test all of the above with a number of different splatted argument types
|
||||
|
||||
def _(args: Literal["a"]) -> None:
|
||||
takes_zero(*args) # error: [too-many-positional-arguments]
|
||||
takes_one(*args)
|
||||
takes_two(*args) # error: [missing-argument]
|
||||
takes_two_positional_only(*args) # error: [missing-argument]
|
||||
# error: [invalid-argument-type]
|
||||
# error: [missing-argument]
|
||||
takes_two_different(*args)
|
||||
# error: [invalid-argument-type]
|
||||
# error: [missing-argument]
|
||||
takes_two_different_positional_only(*args)
|
||||
takes_at_least_zero(*args)
|
||||
takes_at_least_one(*args)
|
||||
takes_at_least_two(*args) # error: [missing-argument]
|
||||
takes_at_least_two_positional_only(*args) # error: [missing-argument]
|
||||
|
||||
def _(args: Literal["ab"]) -> None:
|
||||
takes_zero(*args) # error: [too-many-positional-arguments]
|
||||
takes_one(*args) # error: [too-many-positional-arguments]
|
||||
takes_two(*args)
|
||||
takes_two_positional_only(*args)
|
||||
takes_two_different(*args) # error: [invalid-argument-type]
|
||||
takes_two_different_positional_only(*args) # error: [invalid-argument-type]
|
||||
takes_at_least_zero(*args)
|
||||
takes_at_least_one(*args)
|
||||
takes_at_least_two(*args)
|
||||
takes_at_least_two_positional_only(*args)
|
||||
|
||||
def _(args: Literal["abc"]) -> None:
|
||||
takes_zero(*args) # error: [too-many-positional-arguments]
|
||||
takes_one(*args) # error: [too-many-positional-arguments]
|
||||
takes_two(*args) # error: [too-many-positional-arguments]
|
||||
takes_two_positional_only(*args) # error: [too-many-positional-arguments]
|
||||
# error: [invalid-argument-type]
|
||||
# error: [too-many-positional-arguments]
|
||||
takes_two_different(*args)
|
||||
# error: [invalid-argument-type]
|
||||
# error: [too-many-positional-arguments]
|
||||
takes_two_different_positional_only(*args)
|
||||
takes_at_least_zero(*args)
|
||||
takes_at_least_one(*args)
|
||||
takes_at_least_two(*args)
|
||||
takes_at_least_two_positional_only(*args)
|
||||
|
||||
def _(args: str) -> None:
|
||||
takes_zero(*args)
|
||||
takes_one(*args)
|
||||
takes_two(*args)
|
||||
takes_two_positional_only(*args)
|
||||
takes_two_different(*args) # error: [invalid-argument-type]
|
||||
takes_two_different_positional_only(*args) # error: [invalid-argument-type]
|
||||
takes_at_least_zero(*args)
|
||||
takes_at_least_one(*args)
|
||||
takes_at_least_two(*args)
|
||||
takes_at_least_two_positional_only(*args)
|
||||
```
|
||||
|
||||
### Argument expansion regression
|
||||
|
||||
This is a regression that was highlighted by the ecosystem check, which shows that we might need to
|
||||
rethink how we perform argument expansion during overload resolution. In particular, we might need
|
||||
to retry both `match_parameters` _and_ `check_types` for each expansion. Currently we only retry
|
||||
`check_types`.
|
||||
|
||||
The issue is that argument expansion might produce a splatted value with a different arity than what
|
||||
we originally inferred for the unexpanded value, and that in turn can affect which parameters the
|
||||
splatted value is matched with.
|
||||
|
||||
The first example correctly produces an error. The `tuple[int, str]` union element has a precise
|
||||
arity of two, and so parameter matching chooses the first overload. The second element of the tuple
|
||||
does not match the second parameter type, which yielding an `invalid-argument-type` error.
|
||||
|
||||
The third example should produce the same error. However, because we have a union, we do not see the
|
||||
precise arity of each union element during parameter matching. Instead, we infer an arity of "zero
|
||||
or more" for the union as a whole, and use that less precise arity when matching parameters. We
|
||||
therefore consider the second overload to still be a potential candidate for the `tuple[int, str]`
|
||||
union element. During type checking, we have to force the arity of each union element to match the
|
||||
inferred arity of the union as a whole (turning `tuple[int, str]` into `tuple[int | str, ...]`).
|
||||
That less precise tuple type-checks successfully against the second overload, making us incorrectly
|
||||
think that `tuple[int, str]` is a valid splatted call.
|
||||
|
||||
If we update argument expansion to retry parameter matching with the precise arity of each union
|
||||
element, we will correctly rule out the second overload for `tuple[int, str]`, just like we do when
|
||||
splatting that tuple directly (instead of as part of a union).
|
||||
|
||||
```py
|
||||
from typing import overload
|
||||
|
||||
@overload
|
||||
def f(x: int, y: int) -> None: ...
|
||||
@overload
|
||||
def f(x: int, y: str, z: int) -> None: ...
|
||||
def f(*args): ...
|
||||
|
||||
# Test all of the above with a number of different splatted argument types
|
||||
|
||||
def _(t: tuple[int, str]) -> None:
|
||||
f(*t) # error: [invalid-argument-type]
|
||||
|
||||
def _(t: tuple[int, str, int]) -> None:
|
||||
f(*t)
|
||||
|
||||
def _(t: tuple[int, str] | tuple[int, str, int]) -> None:
|
||||
# TODO: error: [invalid-argument-type]
|
||||
f(*t)
|
||||
```
|
||||
|
||||
## Wrong argument type
|
||||
|
||||
### Positional argument, positional-or-keyword parameter
|
||||
|
||||
```py
|
||||
def f(x: int) -> int:
|
||||
return 1
|
||||
|
||||
# error: 15 [invalid-argument-type] "Argument to function `f` is incorrect: Expected `int`, found `Literal["foo"]`"
|
||||
reveal_type(f("foo")) # revealed: int
|
||||
```
|
||||
|
||||
### Positional argument, positional-only parameter
|
||||
|
||||
```py
|
||||
def f(x: int, /) -> int:
|
||||
return 1
|
||||
|
||||
# error: 15 [invalid-argument-type] "Argument to function `f` is incorrect: Expected `int`, found `Literal["foo"]`"
|
||||
reveal_type(f("foo")) # revealed: int
|
||||
```
|
||||
|
||||
### Positional argument, variadic parameter
|
||||
|
||||
```py
|
||||
def f(*args: int) -> int:
|
||||
return 1
|
||||
|
||||
# error: 15 [invalid-argument-type] "Argument to function `f` is incorrect: Expected `int`, found `Literal["foo"]`"
|
||||
reveal_type(f("foo")) # revealed: int
|
||||
```
|
||||
|
||||
### Keyword argument, positional-or-keyword parameter
|
||||
|
||||
```py
|
||||
def f(x: int) -> int:
|
||||
return 1
|
||||
|
||||
# error: 15 [invalid-argument-type] "Argument to function `f` is incorrect: Expected `int`, found `Literal["foo"]`"
|
||||
reveal_type(f(x="foo")) # revealed: int
|
||||
```
|
||||
|
||||
### Keyword argument, keyword-only parameter
|
||||
|
||||
```py
|
||||
def f(*, x: int) -> int:
|
||||
return 1
|
||||
|
||||
# error: 15 [invalid-argument-type] "Argument to function `f` is incorrect: Expected `int`, found `Literal["foo"]`"
|
||||
reveal_type(f(x="foo")) # revealed: int
|
||||
```
|
||||
|
||||
### Keyword argument, keywords parameter
|
||||
|
||||
```py
|
||||
def f(**kwargs: int) -> int:
|
||||
return 1
|
||||
|
||||
# error: 15 [invalid-argument-type] "Argument to function `f` is incorrect: Expected `int`, found `Literal["foo"]`"
|
||||
reveal_type(f(x="foo")) # revealed: int
|
||||
```
|
||||
|
||||
### Correctly match keyword out-of-order
|
||||
|
||||
```py
|
||||
def f(x: int = 1, y: str = "foo") -> int:
|
||||
return 1
|
||||
|
||||
# error: 15 [invalid-argument-type] "Argument to function `f` is incorrect: Expected `str`, found `Literal[2]`"
|
||||
# error: 20 [invalid-argument-type] "Argument to function `f` is incorrect: Expected `int`, found `Literal["bar"]`"
|
||||
reveal_type(f(y=2, x="bar")) # revealed: int
|
||||
```
|
||||
|
||||
## Too many positional arguments
|
||||
|
||||
### One too many
|
||||
|
||||
```py
|
||||
def f() -> int:
|
||||
return 1
|
||||
|
||||
# error: 15 [too-many-positional-arguments] "Too many positional arguments to function `f`: expected 0, got 1"
|
||||
reveal_type(f("foo")) # revealed: int
|
||||
```
|
||||
|
||||
### Two too many
|
||||
|
||||
```py
|
||||
def f() -> int:
|
||||
return 1
|
||||
|
||||
# error: 15 [too-many-positional-arguments] "Too many positional arguments to function `f`: expected 0, got 2"
|
||||
reveal_type(f("foo", "bar")) # revealed: int
|
||||
```
|
||||
|
||||
### No too-many-positional if variadic is taken
|
||||
|
||||
```py
|
||||
def f(*args: int) -> int:
|
||||
return 1
|
||||
|
||||
reveal_type(f(1, 2, 3)) # revealed: int
|
||||
```
|
||||
|
||||
### Multiple keyword arguments map to keyword variadic parameter
|
||||
|
||||
```py
|
||||
def f(**kwargs: int) -> int:
|
||||
return 1
|
||||
|
||||
reveal_type(f(foo=1, bar=2)) # revealed: int
|
||||
```
|
||||
|
||||
## Missing arguments
|
||||
|
||||
### No defaults or variadic
|
||||
|
||||
```py
|
||||
def f(x: int) -> int:
|
||||
return 1
|
||||
|
||||
# error: 13 [missing-argument] "No argument provided for required parameter `x` of function `f`"
|
||||
reveal_type(f()) # revealed: int
|
||||
```
|
||||
|
||||
### With default
|
||||
|
||||
```py
|
||||
def f(x: int, y: str = "foo") -> int:
|
||||
return 1
|
||||
|
||||
# error: 13 [missing-argument] "No argument provided for required parameter `x` of function `f`"
|
||||
reveal_type(f()) # revealed: int
|
||||
```
|
||||
|
||||
### Defaulted argument is not required
|
||||
|
||||
```py
|
||||
def f(x: int = 1) -> int:
|
||||
return 1
|
||||
|
||||
reveal_type(f()) # revealed: int
|
||||
```
|
||||
|
||||
### With variadic
|
||||
|
||||
```py
|
||||
def f(x: int, *y: str) -> int:
|
||||
return 1
|
||||
|
||||
# error: 13 [missing-argument] "No argument provided for required parameter `x` of function `f`"
|
||||
reveal_type(f()) # revealed: int
|
||||
```
|
||||
|
||||
### Variadic argument is not required
|
||||
|
||||
```py
|
||||
def f(*args: int) -> int:
|
||||
return 1
|
||||
|
||||
reveal_type(f()) # revealed: int
|
||||
```
|
||||
|
||||
### Keywords argument is not required
|
||||
|
||||
```py
|
||||
def f(**kwargs: int) -> int:
|
||||
return 1
|
||||
|
||||
reveal_type(f()) # revealed: int
|
||||
```
|
||||
|
||||
### Multiple
|
||||
|
||||
```py
|
||||
def f(x: int, y: int) -> int:
|
||||
return 1
|
||||
|
||||
# error: 13 [missing-argument] "No arguments provided for required parameters `x`, `y` of function `f`"
|
||||
reveal_type(f()) # revealed: int
|
||||
```
|
||||
|
||||
## Unknown argument
|
||||
|
||||
```py
|
||||
def f(x: int) -> int:
|
||||
return 1
|
||||
|
||||
# error: 20 [unknown-argument] "Argument `y` does not match any known parameter of function `f`"
|
||||
reveal_type(f(x=1, y=2)) # revealed: int
|
||||
```
|
||||
|
||||
## Parameter already assigned
|
||||
|
||||
```py
|
||||
def f(x: int) -> int:
|
||||
return 1
|
||||
|
||||
# error: 18 [parameter-already-assigned] "Multiple values provided for parameter `x` of function `f`"
|
||||
reveal_type(f(1, x=2)) # revealed: int
|
||||
```
|
||||
|
||||
## Special functions
|
||||
|
||||
Some functions require special handling in type inference. Here, we make sure that we still emit
|
||||
proper diagnostics in case of missing or superfluous arguments.
|
||||
|
||||
### `reveal_type`
|
||||
|
||||
```py
|
||||
from typing_extensions import reveal_type
|
||||
|
||||
# error: [missing-argument] "No argument provided for required parameter `obj` of function `reveal_type`"
|
||||
reveal_type()
|
||||
|
||||
# error: [too-many-positional-arguments] "Too many positional arguments to function `reveal_type`: expected 1, got 2"
|
||||
reveal_type(1, 2)
|
||||
```
|
||||
|
||||
### `static_assert`
|
||||
|
||||
```py
|
||||
from ty_extensions import static_assert
|
||||
|
||||
# error: [missing-argument] "No argument provided for required parameter `condition` of function `static_assert`"
|
||||
static_assert()
|
||||
|
||||
# error: [too-many-positional-arguments] "Too many positional arguments to function `static_assert`: expected 2, got 3"
|
||||
static_assert(True, 2, 3)
|
||||
```
|
||||
|
||||
### `len`
|
||||
|
||||
```py
|
||||
# error: [missing-argument] "No argument provided for required parameter `obj` of function `len`"
|
||||
len()
|
||||
|
||||
# error: [too-many-positional-arguments] "Too many positional arguments to function `len`: expected 1, got 2"
|
||||
len([], 1)
|
||||
```
|
||||
|
||||
### Type property predicates
|
||||
|
||||
```py
|
||||
from ty_extensions import is_subtype_of
|
||||
|
||||
# error: [missing-argument]
|
||||
is_subtype_of()
|
||||
|
||||
# error: [missing-argument]
|
||||
is_subtype_of(int)
|
||||
|
||||
# error: [too-many-positional-arguments]
|
||||
is_subtype_of(int, int, int)
|
||||
|
||||
# error: [too-many-positional-arguments]
|
||||
is_subtype_of(int, int, int, int)
|
||||
reveal_type(1)
|
||||
```
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
# `TypedDict`
|
||||
|
||||
We do not support `TypedDict`s yet. This test mainly exists to make sure that we do not emit any
|
||||
errors for the definition of a `TypedDict`.
|
||||
## Basic
|
||||
|
||||
```py
|
||||
from typing_extensions import TypedDict, Required
|
||||
from typing import TypedDict
|
||||
|
||||
class Person(TypedDict):
|
||||
name: str
|
||||
@@ -12,6 +11,46 @@ class Person(TypedDict):
|
||||
|
||||
alice: Person = {"name": "Alice", "age": 30}
|
||||
|
||||
# TODO: this should be `str`
|
||||
reveal_type(alice["name"]) # revealed: Unknown
|
||||
# TODO: this should be `int | None`
|
||||
reveal_type(alice["age"]) # revealed: Unknown
|
||||
|
||||
reveal_type(Person.__required_keys__) # revealed: @Todo(Support for `TypedDict`)
|
||||
|
||||
# TODO: this should be an error
|
||||
bob: Person = {"name": b"Bob"}
|
||||
```
|
||||
|
||||
## Structural subtyping
|
||||
|
||||
Subtyping between `TypedDict` types is structural, that is, it is based on the presence of keys and
|
||||
their types, rather than the class hierarchy.
|
||||
|
||||
```py
|
||||
from typing import TypedDict
|
||||
|
||||
class Person(TypedDict):
|
||||
name: str
|
||||
|
||||
class Employee(TypedDict):
|
||||
name: str
|
||||
employee_id: int
|
||||
|
||||
def accepts_person(p: Person) -> None:
|
||||
pass
|
||||
|
||||
def f(e: Employee) -> None:
|
||||
accepts_person(e) # This is fine, as `Employee` has all keys of `Person`
|
||||
```
|
||||
|
||||
## Function/assignment syntax
|
||||
|
||||
This is not yet supported. Make sure that we do not emit false positives for this syntax:
|
||||
|
||||
```py
|
||||
from typing import TypedDict, Required
|
||||
|
||||
# Alternative syntax
|
||||
Message = TypedDict("Message", {"id": Required[int], "content": str}, total=False)
|
||||
|
||||
@@ -20,6 +59,5 @@ msg = Message(id=1, content="Hello")
|
||||
# No errors for yet-unsupported features (`closed`):
|
||||
OtherMessage = TypedDict("OtherMessage", {"id": int, "content": str}, closed=True)
|
||||
|
||||
reveal_type(Person.__required_keys__) # revealed: @Todo(Support for `TypedDict`)
|
||||
reveal_type(Message.__required_keys__) # revealed: @Todo(Support for `TypedDict`)
|
||||
```
|
||||
|
||||
@@ -5197,7 +5197,23 @@ impl<'db> Type<'db> {
|
||||
KnownClass::Float.to_instance(db),
|
||||
],
|
||||
),
|
||||
_ => Type::instance(db, class.default_specialization(db)),
|
||||
_ => {
|
||||
for base in class.explicit_bases(db) {
|
||||
if *base == Type::SpecialForm(SpecialFormType::TypedDict) {
|
||||
return Ok(todo_type!("TypedDict"));
|
||||
}
|
||||
}
|
||||
Type::instance(db, class.default_specialization(db))
|
||||
// todo_type!("TypedDict")
|
||||
// if class
|
||||
// .iter_mro(db, None)
|
||||
// .any(|base| matches!(base, ClassBase::TypedDict))
|
||||
// {
|
||||
// todo_type!("TypedDict")
|
||||
// } else {
|
||||
// Type::instance(db, class.default_specialization(db))
|
||||
// }
|
||||
}
|
||||
};
|
||||
Ok(ty)
|
||||
}
|
||||
@@ -6233,9 +6249,6 @@ pub enum DynamicType {
|
||||
/// A special Todo-variant for type aliases declared using `typing.TypeAlias`.
|
||||
/// A temporary variant to detect and special-case the handling of these aliases in autocomplete suggestions.
|
||||
TodoTypeAlias,
|
||||
/// A special Todo-variant for classes inheriting from `TypedDict`.
|
||||
/// A temporary variant to avoid false positives while we wait for full support.
|
||||
TodoTypedDict,
|
||||
}
|
||||
|
||||
impl DynamicType {
|
||||
@@ -6267,13 +6280,6 @@ impl std::fmt::Display for DynamicType {
|
||||
f.write_str("@Todo")
|
||||
}
|
||||
}
|
||||
DynamicType::TodoTypedDict => {
|
||||
if cfg!(debug_assertions) {
|
||||
f.write_str("@Todo(Support for `TypedDict`)")
|
||||
} else {
|
||||
f.write_str("@Todo")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ use crate::types::signatures::{CallableSignature, Parameter, Parameters, Signatu
|
||||
use crate::types::tuple::{TupleSpec, TupleType};
|
||||
use crate::types::{
|
||||
BareTypeAliasType, Binding, BoundSuperError, BoundSuperType, CallableType, DataclassParams,
|
||||
DeprecatedInstance, DynamicType, KnownInstanceType, TypeAliasType, TypeMapping, TypeRelation,
|
||||
DeprecatedInstance, KnownInstanceType, TypeAliasType, TypeMapping, TypeRelation,
|
||||
TypeTransformer, TypeVarBoundOrConstraints, TypeVarInstance, TypeVarKind, declaration_type,
|
||||
infer_definition_types,
|
||||
};
|
||||
@@ -418,15 +418,6 @@ impl<'db> ClassType<'db> {
|
||||
other: Self,
|
||||
relation: TypeRelation,
|
||||
) -> bool {
|
||||
// TODO: remove this branch once we have proper support for TypedDicts.
|
||||
if self.is_known(db, KnownClass::Dict)
|
||||
&& other
|
||||
.iter_mro(db)
|
||||
.any(|b| matches!(b, ClassBase::Dynamic(DynamicType::TodoTypedDict)))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
self.iter_mro(db).any(|base| {
|
||||
match base {
|
||||
ClassBase::Dynamic(_) => match relation {
|
||||
@@ -450,6 +441,10 @@ impl<'db> ClassType<'db> {
|
||||
(ClassType::Generic(_), ClassType::NonGeneric(_))
|
||||
| (ClassType::NonGeneric(_), ClassType::Generic(_)) => false,
|
||||
},
|
||||
|
||||
ClassBase::TypedDict => {
|
||||
todo!("Implement TypedDict relation checking")
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -1656,6 +1651,9 @@ impl<'db> ClassLiteral<'db> {
|
||||
lookup_error.or_fall_back_to(db, class.own_class_member(db, name))
|
||||
});
|
||||
}
|
||||
ClassBase::TypedDict => {
|
||||
todo!("Member lookup on TypedDict")
|
||||
}
|
||||
}
|
||||
if lookup_result.is_ok() {
|
||||
break;
|
||||
@@ -2110,6 +2108,9 @@ impl<'db> ClassLiteral<'db> {
|
||||
union = union.add(ty);
|
||||
}
|
||||
}
|
||||
ClassBase::TypedDict => {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@ pub enum ClassBase<'db> {
|
||||
/// but nonetheless appears in the MRO of classes that inherit from `Generic[T]`,
|
||||
/// `Protocol[T]`, or bare `Protocol`.
|
||||
Generic,
|
||||
TypedDict,
|
||||
}
|
||||
|
||||
impl<'db> ClassBase<'db> {
|
||||
@@ -39,7 +40,7 @@ impl<'db> ClassBase<'db> {
|
||||
match self {
|
||||
Self::Dynamic(dynamic) => Self::Dynamic(dynamic.normalized()),
|
||||
Self::Class(class) => Self::Class(class.normalized_impl(db, visitor)),
|
||||
Self::Protocol | Self::Generic => self,
|
||||
Self::Protocol | Self::Generic | Self::TypedDict => self,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,11 +52,11 @@ impl<'db> ClassBase<'db> {
|
||||
ClassBase::Dynamic(
|
||||
DynamicType::Todo(_)
|
||||
| DynamicType::TodoPEP695ParamSpec
|
||||
| DynamicType::TodoTypeAlias
|
||||
| DynamicType::TodoTypedDict,
|
||||
| DynamicType::TodoTypeAlias,
|
||||
) => "@Todo",
|
||||
ClassBase::Protocol => "Protocol",
|
||||
ClassBase::Generic => "Generic",
|
||||
ClassBase::TypedDict => "TypedDict",
|
||||
}
|
||||
}
|
||||
|
||||
@@ -234,7 +235,14 @@ impl<'db> ClassBase<'db> {
|
||||
SpecialFormType::OrderedDict => {
|
||||
Self::try_from_type(db, KnownClass::OrderedDict.to_class_literal(db))
|
||||
}
|
||||
SpecialFormType::TypedDict => Some(Self::Dynamic(DynamicType::TodoTypedDict)),
|
||||
SpecialFormType::TypedDict => Some(ClassBase::Class(
|
||||
KnownClass::Dict
|
||||
.to_specialized_class_type(
|
||||
db,
|
||||
[KnownClass::Str.to_instance(db), Type::unknown()],
|
||||
)
|
||||
.unwrap(),
|
||||
)),
|
||||
SpecialFormType::Callable => {
|
||||
Self::try_from_type(db, todo_type!("Support for Callable as a base class"))
|
||||
}
|
||||
@@ -245,14 +253,14 @@ impl<'db> ClassBase<'db> {
|
||||
pub(super) fn into_class(self) -> Option<ClassType<'db>> {
|
||||
match self {
|
||||
Self::Class(class) => Some(class),
|
||||
Self::Dynamic(_) | Self::Generic | Self::Protocol => None,
|
||||
Self::Dynamic(_) | Self::Generic | Self::Protocol | Self::TypedDict => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn apply_type_mapping<'a>(self, db: &'db dyn Db, type_mapping: &TypeMapping<'a, 'db>) -> Self {
|
||||
match self {
|
||||
Self::Class(class) => Self::Class(class.apply_type_mapping(db, type_mapping)),
|
||||
Self::Dynamic(_) | Self::Generic | Self::Protocol => self,
|
||||
Self::Dynamic(_) | Self::Generic | Self::Protocol | Self::TypedDict => self,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -276,7 +284,10 @@ impl<'db> ClassBase<'db> {
|
||||
.try_mro(db, specialization)
|
||||
.is_err_and(MroError::is_cycle)
|
||||
}
|
||||
ClassBase::Dynamic(_) | ClassBase::Generic | ClassBase::Protocol => false,
|
||||
ClassBase::Dynamic(_)
|
||||
| ClassBase::Generic
|
||||
| ClassBase::Protocol
|
||||
| ClassBase::TypedDict => false,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -292,6 +303,14 @@ impl<'db> ClassBase<'db> {
|
||||
ClassBase::Class(class) => {
|
||||
ClassBaseMroIterator::from_class(db, class, additional_specialization)
|
||||
}
|
||||
ClassBase::TypedDict => ClassBaseMroIterator::from_class(
|
||||
db,
|
||||
KnownClass::Dict
|
||||
.to_class_literal(db)
|
||||
.to_class_type(db)
|
||||
.unwrap(),
|
||||
additional_specialization,
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -309,6 +328,7 @@ impl<'db> From<ClassBase<'db>> for Type<'db> {
|
||||
ClassBase::Class(class) => class.into(),
|
||||
ClassBase::Protocol => Type::SpecialForm(SpecialFormType::Protocol),
|
||||
ClassBase::Generic => Type::SpecialForm(SpecialFormType::Generic),
|
||||
ClassBase::TypedDict => Type::SpecialForm(SpecialFormType::TypedDict),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7070,8 +7070,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||
todo @ Type::Dynamic(
|
||||
DynamicType::Todo(_)
|
||||
| DynamicType::TodoPEP695ParamSpec
|
||||
| DynamicType::TodoTypeAlias
|
||||
| DynamicType::TodoTypedDict,
|
||||
| DynamicType::TodoTypeAlias,
|
||||
),
|
||||
_,
|
||||
_,
|
||||
@@ -7081,8 +7080,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||
todo @ Type::Dynamic(
|
||||
DynamicType::Todo(_)
|
||||
| DynamicType::TodoPEP695ParamSpec
|
||||
| DynamicType::TodoTypeAlias
|
||||
| DynamicType::TodoTypedDict,
|
||||
| DynamicType::TodoTypeAlias,
|
||||
),
|
||||
_,
|
||||
) => Some(todo),
|
||||
|
||||
@@ -269,7 +269,10 @@ impl<'db> Mro<'db> {
|
||||
continue;
|
||||
}
|
||||
match base {
|
||||
ClassBase::Class(_) | ClassBase::Generic | ClassBase::Protocol => {
|
||||
ClassBase::Class(_)
|
||||
| ClassBase::Generic
|
||||
| ClassBase::Protocol
|
||||
| ClassBase::TypedDict => {
|
||||
errors.push(DuplicateBaseError {
|
||||
duplicate_base: base,
|
||||
first_index: *first_index,
|
||||
|
||||
@@ -166,6 +166,9 @@ pub(super) fn union_or_intersection_elements_ordering<'db>(
|
||||
(ClassBase::Generic, _) => Ordering::Less,
|
||||
(_, ClassBase::Generic) => Ordering::Greater,
|
||||
|
||||
(ClassBase::TypedDict, _) => Ordering::Less,
|
||||
(_, ClassBase::TypedDict) => Ordering::Greater,
|
||||
|
||||
(ClassBase::Dynamic(left), ClassBase::Dynamic(right)) => {
|
||||
dynamic_elements_ordering(left, right)
|
||||
}
|
||||
@@ -257,9 +260,6 @@ fn dynamic_elements_ordering(left: DynamicType, right: DynamicType) -> Ordering
|
||||
|
||||
(DynamicType::TodoTypeAlias, _) => Ordering::Less,
|
||||
(_, DynamicType::TodoTypeAlias) => Ordering::Greater,
|
||||
|
||||
(DynamicType::TodoTypedDict, _) => Ordering::Less,
|
||||
(_, DynamicType::TodoTypedDict) => Ordering::Greater,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user