mirror of
https://github.com/astral-sh/ruff.git
synced 2025-08-14 23:51:03 +00:00
Rename Red Knot (#17820)
This commit is contained in:
parent
e6a798b962
commit
b51c4f82ea
1564 changed files with 1598 additions and 1578 deletions
|
@ -0,0 +1,114 @@
|
|||
# Function parameter types
|
||||
|
||||
Within a function scope, the declared type of each parameter is its annotated type (or Unknown if
|
||||
not annotated). The initial inferred type is the union of the declared type with the type of the
|
||||
default value expression (if any). If both are fully static types, this union should simplify to the
|
||||
annotated type (since the default value type must be assignable to the annotated type, and for fully
|
||||
static types this means subtype-of, which simplifies in unions). But if the annotated type is
|
||||
Unknown or another non-fully-static type, the default value type may still be relevant as lower
|
||||
bound.
|
||||
|
||||
The variadic parameter is a variadic tuple of its annotated type; the variadic-keywords parameter is
|
||||
a dictionary from strings to its annotated type.
|
||||
|
||||
## Parameter kinds
|
||||
|
||||
```py
|
||||
from typing import Literal
|
||||
|
||||
def f(a, b: int, c=1, d: int = 2, /, e=3, f: Literal[4] = 4, *args: object, g=5, h: Literal[6] = 6, **kwargs: str):
|
||||
reveal_type(a) # revealed: Unknown
|
||||
reveal_type(b) # revealed: int
|
||||
reveal_type(c) # revealed: Unknown | Literal[1]
|
||||
reveal_type(d) # revealed: int
|
||||
reveal_type(e) # revealed: Unknown | Literal[3]
|
||||
reveal_type(f) # revealed: Literal[4]
|
||||
reveal_type(g) # revealed: Unknown | Literal[5]
|
||||
reveal_type(h) # revealed: Literal[6]
|
||||
# TODO: should be `tuple[object, ...]` (needs generics)
|
||||
reveal_type(args) # revealed: tuple
|
||||
reveal_type(kwargs) # revealed: dict[str, str]
|
||||
```
|
||||
|
||||
## Unannotated variadic parameters
|
||||
|
||||
...are inferred as tuple of Unknown or dict from string to Unknown.
|
||||
|
||||
```py
|
||||
def g(*args, **kwargs):
|
||||
# TODO: should be `tuple[Unknown, ...]` (needs generics)
|
||||
reveal_type(args) # revealed: tuple
|
||||
reveal_type(kwargs) # revealed: dict[str, Unknown]
|
||||
```
|
||||
|
||||
## Annotation is present but not a fully static type
|
||||
|
||||
The default value type should be a lower bound on the inferred type.
|
||||
|
||||
```py
|
||||
from typing import Any
|
||||
|
||||
def f(x: Any = 1):
|
||||
reveal_type(x) # revealed: Any | Literal[1]
|
||||
```
|
||||
|
||||
## Default value type must be assignable to annotated type
|
||||
|
||||
The default value type must be assignable to the annotated type. If not, we emit a diagnostic, and
|
||||
fall back to inferring the annotated type, ignoring the default value type.
|
||||
|
||||
```py
|
||||
# error: [invalid-parameter-default]
|
||||
def f(x: int = "foo"):
|
||||
reveal_type(x) # revealed: int
|
||||
|
||||
# The check is assignable-to, not subtype-of, so this is fine:
|
||||
from typing import Any
|
||||
|
||||
def g(x: Any = "foo"):
|
||||
reveal_type(x) # revealed: Any | Literal["foo"]
|
||||
```
|
||||
|
||||
## Stub functions
|
||||
|
||||
```toml
|
||||
[environment]
|
||||
python-version = "3.12"
|
||||
```
|
||||
|
||||
### In Protocol
|
||||
|
||||
```py
|
||||
from typing import Protocol
|
||||
|
||||
class Foo(Protocol):
|
||||
def x(self, y: bool = ...): ...
|
||||
def y[T](self, y: T = ...) -> T: ...
|
||||
|
||||
class GenericFoo[T](Protocol):
|
||||
def x(self, y: bool = ...) -> T: ...
|
||||
```
|
||||
|
||||
### In abstract method
|
||||
|
||||
```py
|
||||
from abc import abstractmethod
|
||||
|
||||
class Bar:
|
||||
@abstractmethod
|
||||
def x(self, y: bool = ...): ...
|
||||
@abstractmethod
|
||||
def y[T](self, y: T = ...) -> T: ...
|
||||
```
|
||||
|
||||
### In function overload
|
||||
|
||||
```py
|
||||
from typing import overload
|
||||
|
||||
@overload
|
||||
def x(y: None = ...) -> None: ...
|
||||
@overload
|
||||
def x(y: int) -> str: ...
|
||||
def x(y: int | None = None) -> str | None: ...
|
||||
```
|
|
@ -0,0 +1,342 @@
|
|||
# Function return type
|
||||
|
||||
When a function's return type is annotated, all return statements are checked to ensure that the
|
||||
type of the returned value is assignable to the annotated return type.
|
||||
|
||||
## Basic examples
|
||||
|
||||
A return value assignable to the annotated return type is valid.
|
||||
|
||||
```py
|
||||
def f() -> int:
|
||||
return 1
|
||||
```
|
||||
|
||||
The type of the value obtained by calling a function is the annotated return type, not the inferred
|
||||
return type.
|
||||
|
||||
```py
|
||||
reveal_type(f()) # revealed: int
|
||||
```
|
||||
|
||||
A `raise` is equivalent to a return of `Never`, which is assignable to any annotated return type.
|
||||
|
||||
```py
|
||||
def f() -> str:
|
||||
raise ValueError()
|
||||
|
||||
reveal_type(f()) # revealed: str
|
||||
```
|
||||
|
||||
## Stub functions
|
||||
|
||||
"Stub" function definitions (that is, function definitions with an empty body) are permissible in
|
||||
stub files, or in a few other locations: Protocol method definitions, abstract methods, and
|
||||
overloads. In this case the function body is considered to be omitted (thus no return type checking
|
||||
is performed on it), not assumed to implicitly return `None`.
|
||||
|
||||
A stub function's "empty" body may contain only an optional docstring, followed (optionally) by an
|
||||
ellipsis (`...`) or `pass`.
|
||||
|
||||
### In stub file
|
||||
|
||||
```pyi
|
||||
def f() -> int: ...
|
||||
|
||||
def f() -> int:
|
||||
pass
|
||||
|
||||
def f() -> int:
|
||||
"""Some docstring"""
|
||||
|
||||
def f() -> int:
|
||||
"""Some docstring"""
|
||||
...
|
||||
```
|
||||
|
||||
### In Protocol
|
||||
|
||||
```toml
|
||||
[environment]
|
||||
python-version = "3.12"
|
||||
```
|
||||
|
||||
```py
|
||||
from typing import Protocol, TypeVar
|
||||
|
||||
class Bar(Protocol):
|
||||
def f(self) -> int: ...
|
||||
|
||||
class Baz(Bar):
|
||||
# error: [invalid-return-type]
|
||||
def f(self) -> int: ...
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
class Qux(Protocol[T]):
|
||||
def f(self) -> int: ...
|
||||
|
||||
class Foo(Protocol):
|
||||
def f[T](self, v: T) -> T: ...
|
||||
|
||||
t = (Protocol, int)
|
||||
reveal_type(t[0]) # revealed: typing.Protocol
|
||||
|
||||
class Lorem(t[0]):
|
||||
def f(self) -> int: ...
|
||||
```
|
||||
|
||||
### In abstract method
|
||||
|
||||
```toml
|
||||
[environment]
|
||||
python-version = "3.12"
|
||||
```
|
||||
|
||||
```py
|
||||
from abc import ABC, abstractmethod
|
||||
|
||||
class Foo(ABC):
|
||||
@abstractmethod
|
||||
def f(self) -> int: ...
|
||||
@abstractmethod
|
||||
def g[T](self, x: T) -> T: ...
|
||||
|
||||
class Bar[T](ABC):
|
||||
@abstractmethod
|
||||
def f(self) -> int: ...
|
||||
@abstractmethod
|
||||
def g[T](self, x: T) -> T: ...
|
||||
|
||||
# error: [invalid-return-type]
|
||||
def f() -> int: ...
|
||||
@abstractmethod # Semantically meaningless, accepted nevertheless
|
||||
def g() -> int: ...
|
||||
```
|
||||
|
||||
### In overload
|
||||
|
||||
```py
|
||||
from typing import overload
|
||||
|
||||
@overload
|
||||
def f(x: int) -> int: ...
|
||||
@overload
|
||||
def f(x: str) -> str: ...
|
||||
def f(x: int | str):
|
||||
return x
|
||||
```
|
||||
|
||||
## Conditional return type
|
||||
|
||||
```py
|
||||
def f(cond: bool) -> int:
|
||||
if cond:
|
||||
return 1
|
||||
else:
|
||||
return 2
|
||||
|
||||
def f(cond: bool) -> int | None:
|
||||
if cond:
|
||||
return 1
|
||||
else:
|
||||
return
|
||||
|
||||
def f(cond: bool) -> int:
|
||||
if cond:
|
||||
return 1
|
||||
else:
|
||||
raise ValueError()
|
||||
|
||||
def f(cond: bool) -> str | int:
|
||||
if cond:
|
||||
return "a"
|
||||
else:
|
||||
return 1
|
||||
```
|
||||
|
||||
## Implicit return type
|
||||
|
||||
```py
|
||||
def f(cond: bool) -> int | None:
|
||||
if cond:
|
||||
return 1
|
||||
|
||||
# no implicit return
|
||||
def f() -> int:
|
||||
if True:
|
||||
return 1
|
||||
|
||||
# no implicit return
|
||||
def f(cond: bool) -> int:
|
||||
cond = True
|
||||
if cond:
|
||||
return 1
|
||||
|
||||
def f(cond: bool) -> int:
|
||||
if cond:
|
||||
cond = True
|
||||
else:
|
||||
return 1
|
||||
if cond:
|
||||
return 2
|
||||
```
|
||||
|
||||
## Invalid return type
|
||||
|
||||
<!-- snapshot-diagnostics -->
|
||||
|
||||
```py
|
||||
# error: [invalid-return-type]
|
||||
def f() -> int:
|
||||
1
|
||||
|
||||
def f() -> str:
|
||||
# error: [invalid-return-type]
|
||||
return 1
|
||||
|
||||
def f() -> int:
|
||||
# error: [invalid-return-type]
|
||||
return
|
||||
|
||||
from typing import TypeVar
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
# error: [invalid-return-type]
|
||||
def m(x: T) -> T: ...
|
||||
```
|
||||
|
||||
## Invalid return type in stub file
|
||||
|
||||
<!-- snapshot-diagnostics -->
|
||||
|
||||
```pyi
|
||||
def f() -> int:
|
||||
# error: [invalid-return-type]
|
||||
return ...
|
||||
|
||||
# error: [invalid-return-type]
|
||||
def foo() -> int:
|
||||
print("...")
|
||||
...
|
||||
|
||||
# error: [invalid-return-type]
|
||||
def foo() -> int:
|
||||
f"""{foo} is a function that ..."""
|
||||
...
|
||||
```
|
||||
|
||||
## Invalid conditional return type
|
||||
|
||||
<!-- snapshot-diagnostics -->
|
||||
|
||||
```py
|
||||
def f(cond: bool) -> str:
|
||||
if cond:
|
||||
return "a"
|
||||
else:
|
||||
# error: [invalid-return-type]
|
||||
return 1
|
||||
|
||||
def f(cond: bool) -> str:
|
||||
if cond:
|
||||
# error: [invalid-return-type]
|
||||
return 1
|
||||
else:
|
||||
# error: [invalid-return-type]
|
||||
return 2
|
||||
```
|
||||
|
||||
## Invalid implicit return type
|
||||
|
||||
<!-- snapshot-diagnostics -->
|
||||
|
||||
```py
|
||||
def f() -> None:
|
||||
if False:
|
||||
# error: [invalid-return-type]
|
||||
return 1
|
||||
|
||||
# error: [invalid-return-type]
|
||||
def f(cond: bool) -> int:
|
||||
if cond:
|
||||
return 1
|
||||
|
||||
# error: [invalid-return-type]
|
||||
def f(cond: bool) -> int:
|
||||
if cond:
|
||||
raise ValueError()
|
||||
|
||||
# error: [invalid-return-type]
|
||||
def f(cond: bool) -> int:
|
||||
if cond:
|
||||
cond = False
|
||||
else:
|
||||
return 1
|
||||
if cond:
|
||||
return 2
|
||||
```
|
||||
|
||||
## NotImplemented
|
||||
|
||||
### Default Python version
|
||||
|
||||
`NotImplemented` is a special symbol in Python. It is commonly used to control the fallback behavior
|
||||
of special dunder methods. You can find more details in the
|
||||
[documentation](https://docs.python.org/3/library/numbers.html#implementing-the-arithmetic-operations).
|
||||
|
||||
```py
|
||||
from __future__ import annotations
|
||||
|
||||
class A:
|
||||
def __add__(self, o: A) -> A:
|
||||
return NotImplemented
|
||||
```
|
||||
|
||||
However, as shown below, `NotImplemented` should not cause issues with the declared return type.
|
||||
|
||||
```py
|
||||
def f() -> int:
|
||||
return NotImplemented
|
||||
|
||||
def f(cond: bool) -> int:
|
||||
if cond:
|
||||
return 1
|
||||
else:
|
||||
return NotImplemented
|
||||
|
||||
def f(x: int) -> int | str:
|
||||
if x < 0:
|
||||
return -1
|
||||
elif x == 0:
|
||||
return NotImplemented
|
||||
else:
|
||||
return "test"
|
||||
|
||||
def f(cond: bool) -> str:
|
||||
return "hello" if cond else NotImplemented
|
||||
|
||||
def f(cond: bool) -> int:
|
||||
# error: [invalid-return-type] "Return type does not match returned value: Expected `int`, found `Literal["hello"]`"
|
||||
return "hello" if cond else NotImplemented
|
||||
```
|
||||
|
||||
### Python 3.10+
|
||||
|
||||
Unlike Ellipsis, `_NotImplementedType` remains in `builtins.pyi` regardless of the Python version.
|
||||
Even if `builtins._NotImplementedType` is fully replaced by `types.NotImplementedType` in the
|
||||
future, it should still work as expected.
|
||||
|
||||
```toml
|
||||
[environment]
|
||||
python-version = "3.10"
|
||||
```
|
||||
|
||||
```py
|
||||
def f() -> int:
|
||||
return NotImplemented
|
||||
|
||||
def f(cond: bool) -> str:
|
||||
return "hello" if cond else NotImplemented
|
||||
```
|
Loading…
Add table
Add a link
Reference in a new issue