mirror of
https://github.com/astral-sh/ruff.git
synced 2025-09-30 22:01:18 +00:00
Rename Red Knot (#17820)
This commit is contained in:
parent
e6a798b962
commit
b51c4f82ea
1564 changed files with 1598 additions and 1578 deletions
685
crates/ty_python_semantic/resources/mdtest/overloads.md
Normal file
685
crates/ty_python_semantic/resources/mdtest/overloads.md
Normal file
|
@ -0,0 +1,685 @@
|
|||
# Overloads
|
||||
|
||||
Reference: <https://typing.python.org/en/latest/spec/overload.html>
|
||||
|
||||
## `typing.overload`
|
||||
|
||||
The definition of `typing.overload` in typeshed is an identity function.
|
||||
|
||||
```py
|
||||
from typing import overload
|
||||
|
||||
def foo(x: int) -> int:
|
||||
return x
|
||||
|
||||
reveal_type(foo) # revealed: def foo(x: int) -> int
|
||||
bar = overload(foo)
|
||||
reveal_type(bar) # revealed: def foo(x: int) -> int
|
||||
```
|
||||
|
||||
## Functions
|
||||
|
||||
```py
|
||||
from typing import overload
|
||||
|
||||
@overload
|
||||
def add() -> None: ...
|
||||
@overload
|
||||
def add(x: int) -> int: ...
|
||||
@overload
|
||||
def add(x: int, y: int) -> int: ...
|
||||
def add(x: int | None = None, y: int | None = None) -> int | None:
|
||||
return (x or 0) + (y or 0)
|
||||
|
||||
reveal_type(add) # revealed: Overload[() -> None, (x: int) -> int, (x: int, y: int) -> int]
|
||||
reveal_type(add()) # revealed: None
|
||||
reveal_type(add(1)) # revealed: int
|
||||
reveal_type(add(1, 2)) # revealed: int
|
||||
```
|
||||
|
||||
## Overriding
|
||||
|
||||
These scenarios are to verify that the overloaded and non-overloaded definitions are correctly
|
||||
overridden by each other.
|
||||
|
||||
An overloaded function is overriding another overloaded function:
|
||||
|
||||
```py
|
||||
from typing import overload
|
||||
|
||||
@overload
|
||||
def foo() -> None: ...
|
||||
@overload
|
||||
def foo(x: int) -> int: ...
|
||||
def foo(x: int | None = None) -> int | None:
|
||||
return x
|
||||
|
||||
reveal_type(foo) # revealed: Overload[() -> None, (x: int) -> int]
|
||||
reveal_type(foo()) # revealed: None
|
||||
reveal_type(foo(1)) # revealed: int
|
||||
|
||||
@overload
|
||||
def foo() -> None: ...
|
||||
@overload
|
||||
def foo(x: str) -> str: ...
|
||||
def foo(x: str | None = None) -> str | None:
|
||||
return x
|
||||
|
||||
reveal_type(foo) # revealed: Overload[() -> None, (x: str) -> str]
|
||||
reveal_type(foo()) # revealed: None
|
||||
reveal_type(foo("")) # revealed: str
|
||||
```
|
||||
|
||||
A non-overloaded function is overriding an overloaded function:
|
||||
|
||||
```py
|
||||
def foo(x: int) -> int:
|
||||
return x
|
||||
|
||||
reveal_type(foo) # revealed: def foo(x: int) -> int
|
||||
```
|
||||
|
||||
An overloaded function is overriding a non-overloaded function:
|
||||
|
||||
```py
|
||||
reveal_type(foo) # revealed: def foo(x: int) -> int
|
||||
|
||||
@overload
|
||||
def foo() -> None: ...
|
||||
@overload
|
||||
def foo(x: bytes) -> bytes: ...
|
||||
def foo(x: bytes | None = None) -> bytes | None:
|
||||
return x
|
||||
|
||||
reveal_type(foo) # revealed: Overload[() -> None, (x: bytes) -> bytes]
|
||||
reveal_type(foo()) # revealed: None
|
||||
reveal_type(foo(b"")) # revealed: bytes
|
||||
```
|
||||
|
||||
## Methods
|
||||
|
||||
```py
|
||||
from typing import overload
|
||||
|
||||
class Foo1:
|
||||
@overload
|
||||
def method(self) -> None: ...
|
||||
@overload
|
||||
def method(self, x: int) -> int: ...
|
||||
def method(self, x: int | None = None) -> int | None:
|
||||
return x
|
||||
|
||||
foo1 = Foo1()
|
||||
reveal_type(foo1.method) # revealed: Overload[() -> None, (x: int) -> int]
|
||||
reveal_type(foo1.method()) # revealed: None
|
||||
reveal_type(foo1.method(1)) # revealed: int
|
||||
|
||||
class Foo2:
|
||||
@overload
|
||||
def method(self) -> None: ...
|
||||
@overload
|
||||
def method(self, x: str) -> str: ...
|
||||
def method(self, x: str | None = None) -> str | None:
|
||||
return x
|
||||
|
||||
foo2 = Foo2()
|
||||
reveal_type(foo2.method) # revealed: Overload[() -> None, (x: str) -> str]
|
||||
reveal_type(foo2.method()) # revealed: None
|
||||
reveal_type(foo2.method("")) # revealed: str
|
||||
```
|
||||
|
||||
## Constructor
|
||||
|
||||
```py
|
||||
from typing import overload
|
||||
|
||||
class Foo:
|
||||
@overload
|
||||
def __init__(self) -> None: ...
|
||||
@overload
|
||||
def __init__(self, x: int) -> None: ...
|
||||
def __init__(self, x: int | None = None) -> None:
|
||||
self.x = x
|
||||
|
||||
foo = Foo()
|
||||
reveal_type(foo) # revealed: Foo
|
||||
reveal_type(foo.x) # revealed: Unknown | int | None
|
||||
|
||||
foo1 = Foo(1)
|
||||
reveal_type(foo1) # revealed: Foo
|
||||
reveal_type(foo1.x) # revealed: Unknown | int | None
|
||||
```
|
||||
|
||||
## Version specific
|
||||
|
||||
Function definitions can vary between multiple Python versions.
|
||||
|
||||
### Overload and non-overload (3.9)
|
||||
|
||||
Here, the same function is overloaded in one version and not in another.
|
||||
|
||||
```toml
|
||||
[environment]
|
||||
python-version = "3.9"
|
||||
```
|
||||
|
||||
```py
|
||||
import sys
|
||||
from typing import overload
|
||||
|
||||
if sys.version_info < (3, 10):
|
||||
def func(x: int) -> int:
|
||||
return x
|
||||
|
||||
elif sys.version_info <= (3, 12):
|
||||
@overload
|
||||
def func() -> None: ...
|
||||
@overload
|
||||
def func(x: int) -> int: ...
|
||||
def func(x: int | None = None) -> int | None:
|
||||
return x
|
||||
|
||||
reveal_type(func) # revealed: def func(x: int) -> int
|
||||
func() # error: [missing-argument]
|
||||
```
|
||||
|
||||
### Overload and non-overload (3.10)
|
||||
|
||||
```toml
|
||||
[environment]
|
||||
python-version = "3.10"
|
||||
```
|
||||
|
||||
```py
|
||||
import sys
|
||||
from typing import overload
|
||||
|
||||
if sys.version_info < (3, 10):
|
||||
def func(x: int) -> int:
|
||||
return x
|
||||
|
||||
elif sys.version_info <= (3, 12):
|
||||
@overload
|
||||
def func() -> None: ...
|
||||
@overload
|
||||
def func(x: int) -> int: ...
|
||||
def func(x: int | None = None) -> int | None:
|
||||
return x
|
||||
|
||||
reveal_type(func) # revealed: Overload[() -> None, (x: int) -> int]
|
||||
reveal_type(func()) # revealed: None
|
||||
reveal_type(func(1)) # revealed: int
|
||||
```
|
||||
|
||||
### Some overloads are version specific (3.9)
|
||||
|
||||
```toml
|
||||
[environment]
|
||||
python-version = "3.9"
|
||||
```
|
||||
|
||||
`overloaded.pyi`:
|
||||
|
||||
```pyi
|
||||
import sys
|
||||
from typing import overload
|
||||
|
||||
if sys.version_info >= (3, 10):
|
||||
@overload
|
||||
def func() -> None: ...
|
||||
|
||||
@overload
|
||||
def func(x: int) -> int: ...
|
||||
@overload
|
||||
def func(x: str) -> str: ...
|
||||
```
|
||||
|
||||
`main.py`:
|
||||
|
||||
```py
|
||||
from overloaded import func
|
||||
|
||||
reveal_type(func) # revealed: Overload[(x: int) -> int, (x: str) -> str]
|
||||
func() # error: [no-matching-overload]
|
||||
reveal_type(func(1)) # revealed: int
|
||||
reveal_type(func("")) # revealed: str
|
||||
```
|
||||
|
||||
### Some overloads are version specific (3.10)
|
||||
|
||||
```toml
|
||||
[environment]
|
||||
python-version = "3.10"
|
||||
```
|
||||
|
||||
`overloaded.pyi`:
|
||||
|
||||
```pyi
|
||||
import sys
|
||||
from typing import overload
|
||||
|
||||
@overload
|
||||
def func() -> None: ...
|
||||
|
||||
if sys.version_info >= (3, 10):
|
||||
@overload
|
||||
def func(x: int) -> int: ...
|
||||
|
||||
@overload
|
||||
def func(x: str) -> str: ...
|
||||
```
|
||||
|
||||
`main.py`:
|
||||
|
||||
```py
|
||||
from overloaded import func
|
||||
|
||||
reveal_type(func) # revealed: Overload[() -> None, (x: int) -> int, (x: str) -> str]
|
||||
reveal_type(func()) # revealed: None
|
||||
reveal_type(func(1)) # revealed: int
|
||||
reveal_type(func("")) # revealed: str
|
||||
```
|
||||
|
||||
## Generic
|
||||
|
||||
```toml
|
||||
[environment]
|
||||
python-version = "3.12"
|
||||
```
|
||||
|
||||
For an overloaded generic function, it's not necessary for all overloads to be generic.
|
||||
|
||||
```py
|
||||
from typing import overload
|
||||
|
||||
@overload
|
||||
def func() -> None: ...
|
||||
@overload
|
||||
def func[T](x: T) -> T: ...
|
||||
def func[T](x: T | None = None) -> T | None:
|
||||
return x
|
||||
|
||||
reveal_type(func) # revealed: Overload[() -> None, (x: T) -> T]
|
||||
reveal_type(func()) # revealed: None
|
||||
reveal_type(func(1)) # revealed: Literal[1]
|
||||
reveal_type(func("")) # revealed: Literal[""]
|
||||
```
|
||||
|
||||
## Invalid
|
||||
|
||||
### At least two overloads
|
||||
|
||||
<!-- snapshot-diagnostics -->
|
||||
|
||||
At least two `@overload`-decorated definitions must be present.
|
||||
|
||||
```py
|
||||
from typing import overload
|
||||
|
||||
@overload
|
||||
def func(x: int) -> int: ...
|
||||
|
||||
# error: [invalid-overload]
|
||||
def func(x: int | str) -> int | str:
|
||||
return x
|
||||
```
|
||||
|
||||
```pyi
|
||||
from typing import overload
|
||||
|
||||
@overload
|
||||
# error: [invalid-overload]
|
||||
def func(x: int) -> int: ...
|
||||
```
|
||||
|
||||
### Overload without an implementation
|
||||
|
||||
#### Regular modules
|
||||
|
||||
<!-- snapshot-diagnostics -->
|
||||
|
||||
In regular modules, a series of `@overload`-decorated definitions must be followed by exactly one
|
||||
non-`@overload`-decorated definition (for the same function/method).
|
||||
|
||||
```py
|
||||
from typing import overload
|
||||
|
||||
@overload
|
||||
def func(x: int) -> int: ...
|
||||
@overload
|
||||
# error: [invalid-overload] "Overloaded non-stub function `func` must have an implementation"
|
||||
def func(x: str) -> str: ...
|
||||
|
||||
class Foo:
|
||||
@overload
|
||||
def method(self, x: int) -> int: ...
|
||||
@overload
|
||||
# error: [invalid-overload] "Overloaded non-stub function `method` must have an implementation"
|
||||
def method(self, x: str) -> str: ...
|
||||
```
|
||||
|
||||
#### Stub files
|
||||
|
||||
Overload definitions within stub files are exempt from this check.
|
||||
|
||||
```pyi
|
||||
from typing import overload
|
||||
|
||||
@overload
|
||||
def func(x: int) -> int: ...
|
||||
@overload
|
||||
def func(x: str) -> str: ...
|
||||
```
|
||||
|
||||
#### Protocols
|
||||
|
||||
Overload definitions within protocols are exempt from this check.
|
||||
|
||||
```py
|
||||
from typing import Protocol, overload
|
||||
|
||||
class Foo(Protocol):
|
||||
@overload
|
||||
def f(self, x: int) -> int: ...
|
||||
@overload
|
||||
def f(self, x: str) -> str: ...
|
||||
```
|
||||
|
||||
#### Abstract methods
|
||||
|
||||
Overload definitions within abstract base classes are exempt from this check.
|
||||
|
||||
```py
|
||||
from abc import ABC, abstractmethod
|
||||
from typing import overload
|
||||
|
||||
class AbstractFoo(ABC):
|
||||
@overload
|
||||
@abstractmethod
|
||||
def f(self, x: int) -> int: ...
|
||||
@overload
|
||||
@abstractmethod
|
||||
def f(self, x: str) -> str: ...
|
||||
```
|
||||
|
||||
Using the `@abstractmethod` decorator requires that the class's metaclass is `ABCMeta` or is derived
|
||||
from it.
|
||||
|
||||
```py
|
||||
class Foo:
|
||||
@overload
|
||||
@abstractmethod
|
||||
def f(self, x: int) -> int: ...
|
||||
@overload
|
||||
@abstractmethod
|
||||
# error: [invalid-overload]
|
||||
def f(self, x: str) -> str: ...
|
||||
```
|
||||
|
||||
And, the `@abstractmethod` decorator must be present on all the `@overload`-ed methods.
|
||||
|
||||
```py
|
||||
class PartialFoo1(ABC):
|
||||
@overload
|
||||
@abstractmethod
|
||||
def f(self, x: int) -> int: ...
|
||||
@overload
|
||||
# error: [invalid-overload]
|
||||
def f(self, x: str) -> str: ...
|
||||
|
||||
class PartialFoo(ABC):
|
||||
@overload
|
||||
def f(self, x: int) -> int: ...
|
||||
@overload
|
||||
@abstractmethod
|
||||
# error: [invalid-overload]
|
||||
def f(self, x: str) -> str: ...
|
||||
```
|
||||
|
||||
### Inconsistent decorators
|
||||
|
||||
#### `@staticmethod`
|
||||
|
||||
If one overload signature is decorated with `@staticmethod`, all overload signatures must be
|
||||
similarly decorated. The implementation, if present, must also have a consistent decorator.
|
||||
|
||||
```py
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import overload
|
||||
|
||||
class CheckStaticMethod:
|
||||
# TODO: error because `@staticmethod` does not exist on all overloads
|
||||
@overload
|
||||
def method1(x: int) -> int: ...
|
||||
@overload
|
||||
def method1(x: str) -> str: ...
|
||||
@staticmethod
|
||||
def method1(x: int | str) -> int | str:
|
||||
return x
|
||||
# TODO: error because `@staticmethod` does not exist on all overloads
|
||||
@overload
|
||||
def method2(x: int) -> int: ...
|
||||
@overload
|
||||
@staticmethod
|
||||
def method2(x: str) -> str: ...
|
||||
@staticmethod
|
||||
def method2(x: int | str) -> int | str:
|
||||
return x
|
||||
# TODO: error because `@staticmethod` does not exist on the implementation
|
||||
@overload
|
||||
@staticmethod
|
||||
def method3(x: int) -> int: ...
|
||||
@overload
|
||||
@staticmethod
|
||||
def method3(x: str) -> str: ...
|
||||
def method3(x: int | str) -> int | str:
|
||||
return x
|
||||
|
||||
@overload
|
||||
@staticmethod
|
||||
def method4(x: int) -> int: ...
|
||||
@overload
|
||||
@staticmethod
|
||||
def method4(x: str) -> str: ...
|
||||
@staticmethod
|
||||
def method4(x: int | str) -> int | str:
|
||||
return x
|
||||
```
|
||||
|
||||
#### `@classmethod`
|
||||
|
||||
<!-- snapshot-diagnostics -->
|
||||
|
||||
The same rules apply for `@classmethod` as for [`@staticmethod`](#staticmethod).
|
||||
|
||||
```py
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import overload
|
||||
|
||||
class CheckClassMethod:
|
||||
def __init__(self, x: int) -> None:
|
||||
self.x = x
|
||||
|
||||
@overload
|
||||
@classmethod
|
||||
def try_from1(cls, x: int) -> CheckClassMethod: ...
|
||||
@overload
|
||||
def try_from1(cls, x: str) -> None: ...
|
||||
@classmethod
|
||||
# error: [invalid-overload] "Overloaded function `try_from1` does not use the `@classmethod` decorator consistently"
|
||||
def try_from1(cls, x: int | str) -> CheckClassMethod | None:
|
||||
if isinstance(x, int):
|
||||
return cls(x)
|
||||
return None
|
||||
|
||||
@overload
|
||||
def try_from2(cls, x: int) -> CheckClassMethod: ...
|
||||
@overload
|
||||
@classmethod
|
||||
def try_from2(cls, x: str) -> None: ...
|
||||
@classmethod
|
||||
# error: [invalid-overload]
|
||||
def try_from2(cls, x: int | str) -> CheckClassMethod | None:
|
||||
if isinstance(x, int):
|
||||
return cls(x)
|
||||
return None
|
||||
|
||||
@overload
|
||||
@classmethod
|
||||
def try_from3(cls, x: int) -> CheckClassMethod: ...
|
||||
@overload
|
||||
@classmethod
|
||||
def try_from3(cls, x: str) -> None: ...
|
||||
# error: [invalid-overload]
|
||||
def try_from3(cls, x: int | str) -> CheckClassMethod | None:
|
||||
if isinstance(x, int):
|
||||
return cls(x)
|
||||
return None
|
||||
|
||||
@overload
|
||||
@classmethod
|
||||
def try_from4(cls, x: int) -> CheckClassMethod: ...
|
||||
@overload
|
||||
@classmethod
|
||||
def try_from4(cls, x: str) -> None: ...
|
||||
@classmethod
|
||||
def try_from4(cls, x: int | str) -> CheckClassMethod | None:
|
||||
if isinstance(x, int):
|
||||
return cls(x)
|
||||
return None
|
||||
```
|
||||
|
||||
#### `@final`
|
||||
|
||||
<!-- snapshot-diagnostics -->
|
||||
|
||||
If a `@final` decorator is supplied for a function with overloads, the decorator should be applied
|
||||
only to the overload implementation if it is present.
|
||||
|
||||
```py
|
||||
from typing_extensions import final, overload
|
||||
|
||||
class Foo:
|
||||
@overload
|
||||
def method1(self, x: int) -> int: ...
|
||||
@overload
|
||||
def method1(self, x: str) -> str: ...
|
||||
@final
|
||||
def method1(self, x: int | str) -> int | str:
|
||||
return x
|
||||
|
||||
@overload
|
||||
@final
|
||||
def method2(self, x: int) -> int: ...
|
||||
@overload
|
||||
def method2(self, x: str) -> str: ...
|
||||
# error: [invalid-overload]
|
||||
def method2(self, x: int | str) -> int | str:
|
||||
return x
|
||||
|
||||
@overload
|
||||
def method3(self, x: int) -> int: ...
|
||||
@overload
|
||||
@final
|
||||
def method3(self, x: str) -> str: ...
|
||||
# error: [invalid-overload]
|
||||
def method3(self, x: int | str) -> int | str:
|
||||
return x
|
||||
```
|
||||
|
||||
If an overload implementation isn't present (for example, in a stub file), the `@final` decorator
|
||||
should be applied only to the first overload.
|
||||
|
||||
```pyi
|
||||
from typing_extensions import final, overload
|
||||
|
||||
class Foo:
|
||||
@overload
|
||||
@final
|
||||
def method1(self, x: int) -> int: ...
|
||||
@overload
|
||||
def method1(self, x: str) -> str: ...
|
||||
|
||||
@overload
|
||||
def method2(self, x: int) -> int: ...
|
||||
@final
|
||||
@overload
|
||||
# error: [invalid-overload]
|
||||
def method2(self, x: str) -> str: ...
|
||||
```
|
||||
|
||||
#### `@override`
|
||||
|
||||
<!-- snapshot-diagnostics -->
|
||||
|
||||
The same rules apply for `@override` as for [`@final`](#final).
|
||||
|
||||
```py
|
||||
from typing_extensions import overload, override
|
||||
|
||||
class Base:
|
||||
@overload
|
||||
def method(self, x: int) -> int: ...
|
||||
@overload
|
||||
def method(self, x: str) -> str: ...
|
||||
def method(self, x: int | str) -> int | str:
|
||||
return x
|
||||
|
||||
class Sub1(Base):
|
||||
@overload
|
||||
def method(self, x: int) -> int: ...
|
||||
@overload
|
||||
def method(self, x: str) -> str: ...
|
||||
@override
|
||||
def method(self, x: int | str) -> int | str:
|
||||
return x
|
||||
|
||||
class Sub2(Base):
|
||||
@overload
|
||||
def method(self, x: int) -> int: ...
|
||||
@overload
|
||||
@override
|
||||
def method(self, x: str) -> str: ...
|
||||
# error: [invalid-overload]
|
||||
def method(self, x: int | str) -> int | str:
|
||||
return x
|
||||
|
||||
class Sub3(Base):
|
||||
@overload
|
||||
@override
|
||||
def method(self, x: int) -> int: ...
|
||||
@overload
|
||||
def method(self, x: str) -> str: ...
|
||||
# error: [invalid-overload]
|
||||
def method(self, x: int | str) -> int | str:
|
||||
return x
|
||||
```
|
||||
|
||||
And, similarly, in stub files:
|
||||
|
||||
```pyi
|
||||
from typing_extensions import overload, override
|
||||
|
||||
class Base:
|
||||
@overload
|
||||
def method(self, x: int) -> int: ...
|
||||
@overload
|
||||
def method(self, x: str) -> str: ...
|
||||
|
||||
class Sub1(Base):
|
||||
@overload
|
||||
@override
|
||||
def method(self, x: int) -> int: ...
|
||||
@overload
|
||||
def method(self, x: str) -> str: ...
|
||||
|
||||
class Sub2(Base):
|
||||
@overload
|
||||
def method(self, x: int) -> int: ...
|
||||
@overload
|
||||
@override
|
||||
# error: [invalid-overload]
|
||||
def method(self, x: str) -> str: ...
|
||||
```
|
Loading…
Add table
Add a link
Reference in a new issue