Compare commits
2 Commits
alex/subsc
...
micha/mdte
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c5aa525d2a | ||
|
|
1566137d27 |
@@ -121,7 +121,7 @@ repos:
|
||||
hooks:
|
||||
- id: blacken-docs
|
||||
language: python # means renovate will also update `additional_dependencies`
|
||||
args: ["--pyi", "--line-length", "130"]
|
||||
args: ["--line-length", "130"]
|
||||
files: '^crates/.*/resources/mdtest/.*\.md'
|
||||
exclude: |
|
||||
(?x)^(
|
||||
|
||||
@@ -9,15 +9,19 @@
|
||||
```py
|
||||
from typing_extensions import Annotated
|
||||
|
||||
|
||||
def _(x: Annotated[int, "foo"]):
|
||||
reveal_type(x) # revealed: int
|
||||
|
||||
|
||||
def _(x: Annotated[int, lambda: 0 + 1 * 2 // 3, _(4)]):
|
||||
reveal_type(x) # revealed: int
|
||||
|
||||
|
||||
def _(x: Annotated[int, "arbitrary", "metadata", "elements", "are", "fine"]):
|
||||
reveal_type(x) # revealed: int
|
||||
|
||||
|
||||
def _(x: Annotated[tuple[str, int], bytes]):
|
||||
reveal_type(x) # revealed: tuple[str, int]
|
||||
```
|
||||
@@ -29,10 +33,12 @@ It is invalid to parameterize `Annotated` with less than two arguments.
|
||||
```py
|
||||
from typing_extensions import Annotated
|
||||
|
||||
|
||||
# error: [invalid-type-form] "`typing.Annotated` requires at least two arguments when used in a type expression"
|
||||
def _(x: Annotated):
|
||||
reveal_type(x) # revealed: Unknown
|
||||
|
||||
|
||||
def _(flag: bool):
|
||||
if flag:
|
||||
X = Annotated
|
||||
@@ -43,14 +49,17 @@ def _(flag: bool):
|
||||
def f(y: X):
|
||||
reveal_type(y) # revealed: Unknown | bool
|
||||
|
||||
|
||||
# error: [invalid-type-form] "`typing.Annotated` requires at least two arguments when used in a type expression"
|
||||
def _(x: Annotated | bool):
|
||||
reveal_type(x) # revealed: Unknown | bool
|
||||
|
||||
|
||||
# error: [invalid-type-form] "Special form `typing.Annotated` expected at least 2 arguments (one type and at least one metadata element)"
|
||||
def _(x: Annotated[()]):
|
||||
reveal_type(x) # revealed: Unknown
|
||||
|
||||
|
||||
# error: [invalid-type-form]
|
||||
def _(x: Annotated[int]):
|
||||
# `Annotated[T]` is invalid and will raise an error at runtime,
|
||||
@@ -59,6 +68,7 @@ def _(x: Annotated[int]):
|
||||
# Same for the `(int,)` form below.
|
||||
reveal_type(x) # revealed: int
|
||||
|
||||
|
||||
# error: [invalid-type-form]
|
||||
def _(x: Annotated[(int,)]):
|
||||
reveal_type(x) # revealed: int
|
||||
@@ -74,18 +84,24 @@ Inheriting from `Annotated[T, ...]` is equivalent to inheriting from `T` itself.
|
||||
from typing_extensions import Annotated
|
||||
from ty_extensions import reveal_mro
|
||||
|
||||
|
||||
class C(Annotated[int, "foo"]): ...
|
||||
|
||||
|
||||
# revealed: (<class 'C'>, <class 'int'>, <class 'object'>)
|
||||
reveal_mro(C)
|
||||
|
||||
|
||||
class D(Annotated[list[str], "foo"]): ...
|
||||
|
||||
|
||||
# revealed: (<class 'D'>, <class 'list[str]'>, <class 'MutableSequence[str]'>, <class 'Sequence[str]'>, <class 'Reversible[str]'>, <class 'Collection[str]'>, <class 'Iterable[str]'>, <class 'Container[str]'>, typing.Protocol, typing.Generic, <class 'object'>)
|
||||
reveal_mro(D)
|
||||
|
||||
|
||||
class E(Annotated[list["E"], "metadata"]): ...
|
||||
|
||||
|
||||
# error: [revealed-type] "Revealed MRO: (<class 'E'>, <class 'list[E]'>, <class 'MutableSequence[E]'>, <class 'Sequence[E]'>, <class 'Reversible[E]'>, <class 'Collection[E]'>, <class 'Iterable[E]'>, <class 'Container[E]'>, typing.Protocol, typing.Generic, <class 'object'>)"
|
||||
reveal_mro(E)
|
||||
```
|
||||
@@ -96,9 +112,11 @@ reveal_mro(E)
|
||||
from typing_extensions import Annotated
|
||||
from ty_extensions import reveal_mro
|
||||
|
||||
|
||||
# At runtime, this is an error.
|
||||
# error: [invalid-base]
|
||||
class C(Annotated): ...
|
||||
|
||||
|
||||
reveal_mro(C) # revealed: (<class 'C'>, Unknown, <class 'object'>)
|
||||
```
|
||||
|
||||
@@ -10,6 +10,7 @@ from typing import Any
|
||||
x: Any = 1
|
||||
x = "foo"
|
||||
|
||||
|
||||
def f():
|
||||
reveal_type(x) # revealed: Any
|
||||
```
|
||||
@@ -24,6 +25,7 @@ from typing import Any as RenamedAny
|
||||
x: RenamedAny = 1
|
||||
x = "foo"
|
||||
|
||||
|
||||
def f():
|
||||
reveal_type(x) # revealed: Any
|
||||
```
|
||||
@@ -36,11 +38,14 @@ isn't a spelling of the Any type.
|
||||
```py
|
||||
class Any: ...
|
||||
|
||||
|
||||
x: Any
|
||||
|
||||
|
||||
def f():
|
||||
reveal_type(x) # revealed: Any
|
||||
|
||||
|
||||
# This verifies that we're not accidentally seeing typing.Any, since str is assignable
|
||||
# to that but not to our locally defined class.
|
||||
y: Any = "not an Any" # error: [invalid-assignment]
|
||||
@@ -58,8 +63,10 @@ allowed, even when the unknown superclass is `int`. The assignment to `y` should
|
||||
from typing import Any
|
||||
from ty_extensions import reveal_mro
|
||||
|
||||
|
||||
class SubclassOfAny(Any): ...
|
||||
|
||||
|
||||
reveal_mro(SubclassOfAny) # revealed: (<class 'SubclassOfAny'>, Any, <class 'object'>)
|
||||
|
||||
x: SubclassOfAny = 1 # error: [invalid-assignment]
|
||||
@@ -72,14 +79,18 @@ possibly be a subclass of `FinalClass`:
|
||||
```py
|
||||
from typing import final
|
||||
|
||||
|
||||
@final
|
||||
class FinalClass: ...
|
||||
|
||||
|
||||
f: FinalClass = SubclassOfAny() # error: [invalid-assignment]
|
||||
|
||||
|
||||
@final
|
||||
class OtherFinalClass: ...
|
||||
|
||||
|
||||
f: FinalClass | OtherFinalClass = SubclassOfAny() # error: [invalid-assignment]
|
||||
```
|
||||
|
||||
@@ -88,33 +99,44 @@ A subclass of `Any` can also be assigned to arbitrary `Callable` and `Protocol`
|
||||
```py
|
||||
from typing import Callable, Any, Protocol
|
||||
|
||||
|
||||
def takes_callable1(f: Callable):
|
||||
f()
|
||||
|
||||
|
||||
takes_callable1(SubclassOfAny())
|
||||
|
||||
|
||||
def takes_callable2(f: Callable[[int], None]):
|
||||
f(1)
|
||||
|
||||
|
||||
takes_callable2(SubclassOfAny())
|
||||
|
||||
|
||||
class CallbackProtocol(Protocol):
|
||||
def __call__(self, x: int, /) -> None: ...
|
||||
|
||||
|
||||
def takes_callback_proto(f: CallbackProtocol):
|
||||
f(1)
|
||||
|
||||
|
||||
takes_callback_proto(SubclassOfAny())
|
||||
|
||||
|
||||
class OtherProtocol(Protocol):
|
||||
x: int
|
||||
|
||||
@property
|
||||
def foo(self) -> bytes: ...
|
||||
@foo.setter
|
||||
def foo(self, x: str) -> None: ...
|
||||
|
||||
|
||||
def takes_other_protocol(f: OtherProtocol): ...
|
||||
|
||||
|
||||
takes_other_protocol(SubclassOfAny())
|
||||
```
|
||||
|
||||
@@ -123,9 +145,11 @@ A subclass of `Any` cannot be assigned to literal types, since those cannot be s
|
||||
```py
|
||||
from typing import Any, Literal
|
||||
|
||||
|
||||
class MockAny(Any):
|
||||
pass
|
||||
|
||||
|
||||
x: Literal[1] = MockAny() # error: [invalid-assignment]
|
||||
```
|
||||
|
||||
@@ -161,6 +185,7 @@ static_assert(is_assignable_to(TypeOf[Any], type))
|
||||
```py
|
||||
from typing import Any
|
||||
|
||||
|
||||
# error: [invalid-type-form] "Special form `typing.Any` expected no type parameter"
|
||||
def f(x: Any[int]):
|
||||
reveal_type(x) # revealed: Unknown
|
||||
|
||||
@@ -22,6 +22,7 @@ A bare `Callable` without any type arguments:
|
||||
```py
|
||||
from typing import Callable
|
||||
|
||||
|
||||
def _(c: Callable):
|
||||
reveal_type(c) # revealed: (...) -> Unknown
|
||||
```
|
||||
@@ -33,6 +34,7 @@ When it's not a list:
|
||||
```py
|
||||
from typing import Callable
|
||||
|
||||
|
||||
# error: [invalid-type-form] "The first argument to `Callable` must be either a list of types, ParamSpec, Concatenate, or `...`"
|
||||
def _(c: Callable[int, str]):
|
||||
reveal_type(c) # revealed: (...) -> Unknown
|
||||
@@ -63,6 +65,7 @@ Using a parameter list:
|
||||
```py
|
||||
from typing import Callable
|
||||
|
||||
|
||||
# error: [invalid-type-form] "Special form `typing.Callable` expected exactly two arguments (parameter types and return type)"
|
||||
def _(c: Callable[[int, str]]):
|
||||
reveal_type(c) # revealed: (...) -> Unknown
|
||||
@@ -111,6 +114,7 @@ which argument corresponds to either the parameters or the return type.
|
||||
```py
|
||||
from typing import Callable
|
||||
|
||||
|
||||
# error: [invalid-type-form] "Special form `typing.Callable` expected exactly two arguments (parameter types and return type)"
|
||||
def _(c: Callable[[int], str, str]):
|
||||
reveal_type(c) # revealed: (...) -> Unknown
|
||||
@@ -151,6 +155,7 @@ def _(c: Callable[
|
||||
```py
|
||||
from typing import Callable
|
||||
|
||||
|
||||
# error: [invalid-type-form] "List literals are not allowed in this context in a type expression"
|
||||
def _(c: Callable[[int], [str]]):
|
||||
reveal_type(c) # revealed: (int, /) -> Unknown
|
||||
@@ -180,6 +185,7 @@ A simple `Callable` with multiple parameters and a return type:
|
||||
```py
|
||||
from typing import Callable
|
||||
|
||||
|
||||
def _(c: Callable[[int, str], int]):
|
||||
reveal_type(c) # revealed: (int, str, /) -> int
|
||||
```
|
||||
@@ -189,6 +195,7 @@ def _(c: Callable[[int, str], int]):
|
||||
```py
|
||||
from typing import Callable, Union
|
||||
|
||||
|
||||
def _(
|
||||
c: Callable[[Union[int, str]], int] | None,
|
||||
d: None | Callable[[Union[int, str]], int],
|
||||
@@ -205,8 +212,10 @@ def _(
|
||||
from typing import Callable, Union
|
||||
from ty_extensions import Intersection, Not
|
||||
|
||||
|
||||
class Foo: ...
|
||||
|
||||
|
||||
def _(
|
||||
c: Intersection[Callable[[Union[int, str]], int], int],
|
||||
d: Intersection[int, Callable[[Union[int, str]], int]],
|
||||
@@ -226,6 +235,7 @@ A nested `Callable` as one of the parameter types:
|
||||
```py
|
||||
from typing import Callable
|
||||
|
||||
|
||||
def _(c: Callable[[Callable[[int], str]], int]):
|
||||
reveal_type(c) # revealed: ((int, /) -> str, /) -> int
|
||||
```
|
||||
@@ -245,6 +255,7 @@ is a [gradual form] indicating that the type is consistent with any input signat
|
||||
```py
|
||||
from typing import Callable
|
||||
|
||||
|
||||
def gradual_form(c: Callable[..., str]):
|
||||
reveal_type(c) # revealed: (...) -> str
|
||||
```
|
||||
@@ -256,6 +267,7 @@ Using `Concatenate` as the first argument to `Callable`:
|
||||
```py
|
||||
from typing_extensions import Callable, Concatenate
|
||||
|
||||
|
||||
def _(c: Callable[Concatenate[int, str, ...], int]):
|
||||
# TODO: Should reveal the correct signature
|
||||
reveal_type(c) # revealed: (...) -> int
|
||||
@@ -306,6 +318,7 @@ Using a `ParamSpec` in a `Callable` annotation:
|
||||
```py
|
||||
from typing_extensions import Callable
|
||||
|
||||
|
||||
def _[**P1](c: Callable[P1, int]):
|
||||
reveal_type(P1.args) # revealed: P1@_.args
|
||||
reveal_type(P1.kwargs) # revealed: P1@_.kwargs
|
||||
@@ -320,6 +333,7 @@ from typing_extensions import ParamSpec
|
||||
|
||||
P2 = ParamSpec("P2")
|
||||
|
||||
|
||||
def _(c: Callable[P2, int]):
|
||||
reveal_type(c) # revealed: (**P2@_) -> int
|
||||
```
|
||||
@@ -333,6 +347,7 @@ from typing_extensions import Callable, TypeVarTuple
|
||||
|
||||
Ts = TypeVarTuple("Ts")
|
||||
|
||||
|
||||
def _(c: Callable[[int, *Ts], int]):
|
||||
# TODO: Should reveal the correct signature
|
||||
reveal_type(c) # revealed: (...) -> int
|
||||
@@ -343,6 +358,7 @@ And, using the legacy syntax using `Unpack`:
|
||||
```py
|
||||
from typing_extensions import Unpack
|
||||
|
||||
|
||||
def _(c: Callable[[int, Unpack[Ts]], int]):
|
||||
# TODO: Should reveal the correct signature
|
||||
reveal_type(c) # revealed: (...) -> int
|
||||
@@ -353,6 +369,7 @@ def _(c: Callable[[int, Unpack[Ts]], int]):
|
||||
```py
|
||||
from typing import Callable
|
||||
|
||||
|
||||
def _(c: Callable[[int], int]):
|
||||
reveal_type(c.__init__) # revealed: bound method object.__init__() -> None
|
||||
reveal_type(c.__class__) # revealed: type
|
||||
@@ -379,6 +396,7 @@ class MyCallable:
|
||||
def __call__(self) -> None:
|
||||
pass
|
||||
|
||||
|
||||
f_wrong(MyCallable()) # raises `AttributeError` at runtime
|
||||
```
|
||||
|
||||
@@ -388,6 +406,7 @@ of the attribute first:
|
||||
```py
|
||||
from inspect import getattr_static
|
||||
|
||||
|
||||
def f_okay(c: Callable[[], None]):
|
||||
if hasattr(c, "__qualname__"):
|
||||
reveal_type(c.__qualname__) # revealed: object
|
||||
@@ -414,13 +433,16 @@ def f_okay(c: Callable[[], None]):
|
||||
```py
|
||||
from ty_extensions import into_callable
|
||||
|
||||
|
||||
class Base:
|
||||
def __init__(self) -> None:
|
||||
pass
|
||||
|
||||
|
||||
class A(Base):
|
||||
pass
|
||||
|
||||
|
||||
# revealed: () -> A
|
||||
reveal_type(into_callable(A))
|
||||
```
|
||||
|
||||
@@ -23,11 +23,14 @@ In (regular) source files, annotations are *not* deferred. This also tests that
|
||||
```py
|
||||
from __future__ import with_statement as annotations
|
||||
|
||||
|
||||
# error: [unresolved-reference]
|
||||
def get_foo() -> Foo: ...
|
||||
|
||||
|
||||
class Foo: ...
|
||||
|
||||
|
||||
reveal_type(get_foo()) # revealed: Unknown
|
||||
```
|
||||
|
||||
@@ -38,11 +41,14 @@ If `__future__.annotations` is imported, annotations *are* deferred.
|
||||
```py
|
||||
from __future__ import annotations
|
||||
|
||||
|
||||
def get_foo() -> Foo:
|
||||
return Foo()
|
||||
|
||||
|
||||
class Foo: ...
|
||||
|
||||
|
||||
reveal_type(get_foo()) # revealed: Foo
|
||||
```
|
||||
|
||||
@@ -56,6 +62,7 @@ python-version = "3.12"
|
||||
```py
|
||||
from __future__ import annotations
|
||||
|
||||
|
||||
class Foo:
|
||||
this: Foo
|
||||
# error: [unresolved-reference]
|
||||
@@ -77,9 +84,11 @@ class Foo:
|
||||
|
||||
def f(self, x: Foo):
|
||||
return self
|
||||
|
||||
# error: [unresolved-reference]
|
||||
def g(self) -> Bar:
|
||||
return self
|
||||
|
||||
# error: [unresolved-reference]
|
||||
def h[T: Bar](self):
|
||||
pass
|
||||
@@ -108,6 +117,7 @@ class Foo:
|
||||
def h[T: Bar]():
|
||||
# error: [unresolved-reference]
|
||||
return Bar()
|
||||
|
||||
type Baz = Foo
|
||||
```
|
||||
|
||||
@@ -132,6 +142,7 @@ class Foo:
|
||||
# error: [unresolved-reference]
|
||||
def f(self, x: Foo):
|
||||
reveal_type(x) # revealed: Unknown
|
||||
|
||||
# error: [unresolved-reference]
|
||||
def g(self) -> Foo:
|
||||
_: Foo = self
|
||||
@@ -145,9 +156,11 @@ class Foo:
|
||||
# error: [unresolved-reference]
|
||||
def f(self, x: Foo):
|
||||
return self
|
||||
|
||||
# error: [unresolved-reference]
|
||||
def g(self) -> Bar:
|
||||
return self
|
||||
|
||||
# error: [unresolved-reference]
|
||||
def h[T: Bar](self):
|
||||
pass
|
||||
@@ -176,8 +189,10 @@ class Foo:
|
||||
def h[T: Bar]():
|
||||
# error: [unresolved-reference]
|
||||
return Bar()
|
||||
|
||||
type Qux = Foo
|
||||
|
||||
|
||||
def _():
|
||||
class C:
|
||||
# error: [unresolved-reference]
|
||||
@@ -192,9 +207,11 @@ def _():
|
||||
```py
|
||||
from __future__ import annotations
|
||||
|
||||
|
||||
class A(B): # error: [unresolved-reference]
|
||||
pass
|
||||
|
||||
|
||||
class B:
|
||||
pass
|
||||
```
|
||||
@@ -215,6 +232,7 @@ class B: ...
|
||||
def f(mode: int = ParseMode.test):
|
||||
pass
|
||||
|
||||
|
||||
class ParseMode:
|
||||
test = 1
|
||||
```
|
||||
@@ -246,6 +264,7 @@ def f(mode: int = NeverDefined.test): ...
|
||||
class Foo(metaclass=SomeMeta):
|
||||
pass
|
||||
|
||||
|
||||
class SomeMeta(type):
|
||||
pass
|
||||
```
|
||||
@@ -275,6 +294,7 @@ class Foo(metaclass=NeverDefined): ...
|
||||
# error: [unresolved-reference]
|
||||
f = lambda x=Foo(): x
|
||||
|
||||
|
||||
class Foo:
|
||||
pass
|
||||
```
|
||||
|
||||
@@ -11,6 +11,7 @@ Numbers = list[int]
|
||||
# this as `list[int]` is more helpful, though:
|
||||
reveal_type(Numbers) # revealed: <class 'list[int]'>
|
||||
|
||||
|
||||
def _(numbers: Numbers) -> None:
|
||||
reveal_type(numbers) # revealed: list[int]
|
||||
```
|
||||
|
||||
@@ -12,6 +12,7 @@ An annotation of `float` means `int | float`, so `int` is assignable to it:
|
||||
def takes_float(x: float):
|
||||
pass
|
||||
|
||||
|
||||
def passes_int_to_float(x: int):
|
||||
# no error!
|
||||
takes_float(x)
|
||||
@@ -31,10 +32,12 @@ It doesn't work the other way around:
|
||||
def takes_int(x: int):
|
||||
pass
|
||||
|
||||
|
||||
def passes_float_to_int(x: float):
|
||||
# error: [invalid-argument-type]
|
||||
takes_int(x)
|
||||
|
||||
|
||||
def assigns_float_to_int(x: float):
|
||||
# error: [invalid-assignment]
|
||||
y: int = x
|
||||
@@ -57,34 +60,41 @@ to it (but not the other way around):
|
||||
def takes_complex(x: complex):
|
||||
pass
|
||||
|
||||
|
||||
def passes_to_complex(x: float, y: int):
|
||||
# no errors!
|
||||
takes_complex(x)
|
||||
takes_complex(y)
|
||||
|
||||
|
||||
def assigns_to_complex(x: float, y: int):
|
||||
# no errors!
|
||||
a: complex = x
|
||||
b: complex = y
|
||||
|
||||
|
||||
def takes_int(x: int):
|
||||
pass
|
||||
|
||||
|
||||
def takes_float(x: float):
|
||||
pass
|
||||
|
||||
|
||||
def passes_complex(x: complex):
|
||||
# error: [invalid-argument-type]
|
||||
takes_int(x)
|
||||
# error: [invalid-argument-type]
|
||||
takes_float(x)
|
||||
|
||||
|
||||
def assigns_complex(x: complex):
|
||||
# error: [invalid-assignment]
|
||||
y: int = x
|
||||
# error: [invalid-assignment]
|
||||
z: float = x
|
||||
|
||||
|
||||
def f(x: complex):
|
||||
reveal_type(x) # revealed: int | float | complex
|
||||
```
|
||||
@@ -98,6 +108,7 @@ be narrowed to `int` or `float`:
|
||||
from typing_extensions import assert_type
|
||||
from ty_extensions import JustFloat
|
||||
|
||||
|
||||
def f(x: complex):
|
||||
reveal_type(x) # revealed: int | float | complex
|
||||
|
||||
|
||||
@@ -9,8 +9,10 @@ import typing
|
||||
from ty_extensions import AlwaysTruthy, AlwaysFalsy
|
||||
from typing_extensions import Literal, Never
|
||||
|
||||
|
||||
class A: ...
|
||||
|
||||
|
||||
def _(
|
||||
a: type[int],
|
||||
b: AlwaysTruthy,
|
||||
@@ -49,6 +51,7 @@ def _(
|
||||
reveal_type(i_) # revealed: Unknown
|
||||
reveal_type(j_) # revealed: Unknown
|
||||
|
||||
|
||||
# Inspired by the conformance test suite at
|
||||
# https://github.com/python/typing/blob/d4f39b27a4a47aac8b6d4019e1b0b5b3156fabdc/conformance/tests/aliases_implicit.py#L88-L122
|
||||
B = [x for x in range(42)]
|
||||
@@ -56,6 +59,7 @@ C = {x for x in range(42)}
|
||||
D = {x: y for x, y in enumerate(range(42))}
|
||||
E = (x for x in range(42))
|
||||
|
||||
|
||||
def _(
|
||||
b: B, # error: [invalid-type-form]
|
||||
c: C, # error: [invalid-type-form]
|
||||
@@ -74,11 +78,13 @@ def _(
|
||||
def bar() -> None:
|
||||
return None
|
||||
|
||||
|
||||
def outer_sync(): # `yield` from is only valid syntax inside a synchronous function
|
||||
def _(
|
||||
a: (yield from [1]), # error: [invalid-type-form] "`yield from` expressions are not allowed in type expressions"
|
||||
): ...
|
||||
|
||||
|
||||
async def baz(): ...
|
||||
async def outer_async(): # avoid unrelated syntax errors on `yield` and `await`
|
||||
def _(
|
||||
@@ -120,6 +126,7 @@ async def outer_async(): # avoid unrelated syntax errors on `yield` and `await`
|
||||
reveal_type(p) # revealed: int | Unknown
|
||||
reveal_type(q) # revealed: Unknown
|
||||
|
||||
|
||||
class Mat:
|
||||
def __init__(self, value: int):
|
||||
self.value = value
|
||||
@@ -127,6 +134,7 @@ class Mat:
|
||||
def __matmul__(self, other) -> int:
|
||||
return 42
|
||||
|
||||
|
||||
def invalid_binary_operators(
|
||||
a: "1" + "2", # error: [invalid-type-form] "Invalid binary operator `+` in type annotation"
|
||||
b: 3 - 5.0, # error: [invalid-type-form] "Invalid binary operator `-` in type annotation"
|
||||
@@ -186,10 +194,12 @@ def _(
|
||||
reveal_type(h) # revealed: Unknown
|
||||
reveal_type(i) # revealed: Unknown
|
||||
|
||||
|
||||
# error: [invalid-type-form] "List literals are not allowed in this context in a type expression: Did you mean `list[int]`?"
|
||||
class name_0[name_2: [int]]:
|
||||
pass
|
||||
|
||||
|
||||
# error: [invalid-type-form] "List literals are not allowed in this context in a type expression"
|
||||
# error: [invalid-type-form] "Dict literals are not allowed in type expressions"
|
||||
class name_4[name_1: [{}]]:
|
||||
@@ -211,6 +221,7 @@ for this case:
|
||||
```py
|
||||
import datetime
|
||||
|
||||
|
||||
def f(x: datetime): ... # error: [invalid-type-form]
|
||||
```
|
||||
|
||||
@@ -225,6 +236,7 @@ class Image: ...
|
||||
```py
|
||||
from PIL import Image
|
||||
|
||||
|
||||
def g(x: Image): ... # error: [invalid-type-form]
|
||||
```
|
||||
|
||||
|
||||
@@ -18,16 +18,19 @@ a6: Literal[True]
|
||||
a7: Literal[None]
|
||||
a8: Literal[Literal[1]]
|
||||
|
||||
|
||||
class Color(Enum):
|
||||
RED = 0
|
||||
GREEN = 1
|
||||
BLUE = 2
|
||||
|
||||
|
||||
b1: Literal[Color.RED]
|
||||
|
||||
MissingT = Enum("MissingT", {"MISSING": "MISSING"})
|
||||
b2: Literal[MissingT.MISSING]
|
||||
|
||||
|
||||
def f():
|
||||
reveal_type(mode) # revealed: Literal["w", "r"]
|
||||
reveal_type(a1) # revealed: Literal[26]
|
||||
@@ -42,6 +45,7 @@ def f():
|
||||
# TODO should be `Literal[MissingT.MISSING]`
|
||||
reveal_type(b2) # revealed: @Todo(functional `Enum` syntax)
|
||||
|
||||
|
||||
# error: [invalid-type-form]
|
||||
invalid1: Literal[3 + 4]
|
||||
# error: [invalid-type-form]
|
||||
@@ -57,9 +61,11 @@ invalid4: Literal[
|
||||
(1, 2, 3), # error: [invalid-type-form]
|
||||
]
|
||||
|
||||
|
||||
class NotAnEnum:
|
||||
x: int = 1
|
||||
|
||||
|
||||
# error: [invalid-type-form]
|
||||
invalid5: Literal[NotAnEnum.x]
|
||||
|
||||
@@ -86,10 +92,12 @@ from enum import Enum
|
||||
|
||||
import mod
|
||||
|
||||
|
||||
class E(Enum):
|
||||
A = 1
|
||||
B = 2
|
||||
|
||||
|
||||
type SingleInt = Literal[1]
|
||||
type SingleStr = Literal["foo"]
|
||||
type SingleBytes = Literal[b"bar"]
|
||||
@@ -105,6 +113,7 @@ type AnEnum2 = Literal[E.A, E.B]
|
||||
type Bool1 = bool
|
||||
type Bool2 = Literal[True, False]
|
||||
|
||||
|
||||
def _(
|
||||
single_int: Literal[SingleInt],
|
||||
single_str: Literal[SingleStr],
|
||||
@@ -149,10 +158,12 @@ type SingleInt = Literal[2]
|
||||
from typing import Literal, TypeAlias
|
||||
from enum import Enum
|
||||
|
||||
|
||||
class E(Enum):
|
||||
A = 1
|
||||
B = 2
|
||||
|
||||
|
||||
SingleInt: TypeAlias = Literal[1]
|
||||
SingleStr: TypeAlias = Literal["foo"]
|
||||
SingleBytes: TypeAlias = Literal[b"bar"]
|
||||
@@ -165,6 +176,7 @@ AnEnum2: TypeAlias = Literal[E.A, E.B]
|
||||
Bool1: TypeAlias = bool
|
||||
Bool2: TypeAlias = Literal[True, False]
|
||||
|
||||
|
||||
def _(
|
||||
single_int: Literal[SingleInt],
|
||||
single_str: Literal[SingleStr],
|
||||
@@ -203,10 +215,12 @@ def _(
|
||||
from typing import Literal
|
||||
from enum import Enum
|
||||
|
||||
|
||||
class E(Enum):
|
||||
A = 1
|
||||
B = 2
|
||||
|
||||
|
||||
SingleInt = Literal[1]
|
||||
SingleStr = Literal["foo"]
|
||||
SingleBytes = Literal[b"bar"]
|
||||
@@ -222,6 +236,7 @@ AnEnum2 = Literal[E.A, E.B]
|
||||
Bool1 = bool
|
||||
Bool2 = Literal[True, False]
|
||||
|
||||
|
||||
def _(
|
||||
single_int: Literal[SingleInt],
|
||||
single_str: Literal[SingleStr],
|
||||
@@ -258,6 +273,7 @@ the union of those types.
|
||||
```py
|
||||
from typing import Literal
|
||||
|
||||
|
||||
def x(
|
||||
a1: Literal[Literal[Literal[1, 2, 3], "foo"], 5, None],
|
||||
a2: Literal["w"] | Literal["r"],
|
||||
@@ -275,15 +291,21 @@ def x(
|
||||
```py
|
||||
from typing import Literal, Union
|
||||
|
||||
|
||||
def foo(x: int) -> int:
|
||||
return x + 1
|
||||
|
||||
|
||||
def bar(s: str) -> str:
|
||||
return s
|
||||
|
||||
|
||||
class A: ...
|
||||
|
||||
|
||||
class B: ...
|
||||
|
||||
|
||||
def union_example(
|
||||
x: Union[
|
||||
# unknown type
|
||||
@@ -333,6 +355,7 @@ from other import Literal
|
||||
# error: [invalid-type-form] "Invalid subscript of object of type `_SpecialForm` in type expression"
|
||||
a1: Literal[26]
|
||||
|
||||
|
||||
def f():
|
||||
reveal_type(a1) # revealed: Unknown
|
||||
```
|
||||
@@ -344,6 +367,7 @@ from typing_extensions import Literal
|
||||
|
||||
a1: Literal[26]
|
||||
|
||||
|
||||
def f():
|
||||
reveal_type(a1) # revealed: Literal[26]
|
||||
```
|
||||
@@ -353,6 +377,7 @@ def f():
|
||||
```py
|
||||
from typing import Literal
|
||||
|
||||
|
||||
# error: [invalid-type-form] "`typing.Literal` requires at least one argument when used in a type expression"
|
||||
def _(x: Literal):
|
||||
reveal_type(x) # revealed: Unknown
|
||||
|
||||
@@ -16,6 +16,7 @@ from typing_extensions import LiteralString
|
||||
|
||||
x: LiteralString
|
||||
|
||||
|
||||
def f():
|
||||
reveal_type(x) # revealed: LiteralString
|
||||
```
|
||||
@@ -54,6 +55,7 @@ Subclassing `LiteralString` leads to a runtime error.
|
||||
```py
|
||||
from typing_extensions import LiteralString
|
||||
|
||||
|
||||
class C(LiteralString): ... # error: [invalid-base]
|
||||
```
|
||||
|
||||
@@ -92,6 +94,7 @@ vice versa.
|
||||
```py
|
||||
from typing_extensions import Literal, LiteralString
|
||||
|
||||
|
||||
def _(flag: bool):
|
||||
foo_1: Literal["foo"] = "foo"
|
||||
bar_1: LiteralString = foo_1 # fine
|
||||
@@ -146,6 +149,7 @@ from typing import LiteralString
|
||||
|
||||
x: LiteralString = "foo"
|
||||
|
||||
|
||||
def f():
|
||||
reveal_type(x) # revealed: LiteralString
|
||||
```
|
||||
|
||||
@@ -9,9 +9,11 @@ interchangeably.
|
||||
```py
|
||||
from typing import NoReturn
|
||||
|
||||
|
||||
def stop() -> NoReturn:
|
||||
raise RuntimeError("no way")
|
||||
|
||||
|
||||
# revealed: Never
|
||||
reveal_type(stop())
|
||||
```
|
||||
@@ -28,6 +30,7 @@ a2: Never
|
||||
b1: Any
|
||||
b2: int
|
||||
|
||||
|
||||
def f():
|
||||
# revealed: Never
|
||||
reveal_type(a1)
|
||||
|
||||
@@ -9,6 +9,7 @@ from typing_extensions import NewType
|
||||
|
||||
UserId = NewType("UserId", int)
|
||||
|
||||
|
||||
def _(user_id: UserId):
|
||||
reveal_type(user_id) # revealed: UserId
|
||||
```
|
||||
@@ -38,10 +39,12 @@ Foo(Bar(Foo(42))) # allowed: `Bar` is a subtype of `int`.
|
||||
Foo(True) # allowed: `bool` is a subtype of `int`.
|
||||
Foo("forty-two") # error: [invalid-argument-type] "Argument is incorrect: Expected `int`, found `Literal["forty-two"]`"
|
||||
|
||||
|
||||
def f(_: int): ...
|
||||
def g(_: Foo): ...
|
||||
def h(_: Bar): ...
|
||||
|
||||
|
||||
f(42)
|
||||
f(Foo(42))
|
||||
f(Bar(Foo(42)))
|
||||
@@ -60,11 +63,14 @@ h(Bar(Foo(42)))
|
||||
```py
|
||||
from typing_extensions import NewType
|
||||
|
||||
|
||||
class Foo:
|
||||
foo_member: str = "hello"
|
||||
|
||||
def foo_method(self) -> int:
|
||||
return 42
|
||||
|
||||
|
||||
Bar = NewType("Bar", Foo)
|
||||
Baz = NewType("Baz", Bar)
|
||||
baz = Baz(Bar(Foo()))
|
||||
@@ -88,16 +94,21 @@ from ty_extensions import CallableTypeOf
|
||||
|
||||
Foo = NewType("Foo", int)
|
||||
|
||||
|
||||
def _(obj: CallableTypeOf[Foo]):
|
||||
reveal_type(obj) # revealed: (int, /) -> Foo
|
||||
|
||||
|
||||
def f(_: Callable[[int], Foo]): ...
|
||||
|
||||
|
||||
f(Foo)
|
||||
map(Foo, [1, 2, 3])
|
||||
|
||||
|
||||
def g(_: Callable[[str], Foo]): ...
|
||||
|
||||
|
||||
g(Foo) # error: [invalid-argument-type]
|
||||
```
|
||||
|
||||
@@ -112,19 +123,23 @@ i = N(42)
|
||||
|
||||
y: Callable[..., Any] = i # error: [invalid-assignment] "Object of type `N` is not assignable to `(...) -> Any`"
|
||||
|
||||
|
||||
# error: [invalid-type-form] "Expected the first argument to `ty_extensions.CallableTypeOf` to be a callable object, but got an object of type `N`"
|
||||
def f(x: CallableTypeOf[i]):
|
||||
reveal_type(x) # revealed: Unknown
|
||||
|
||||
|
||||
class SomethingCallable:
|
||||
def __call__(self, a: str) -> bytes:
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
N2 = NewType("N2", SomethingCallable)
|
||||
j = N2(SomethingCallable())
|
||||
|
||||
z: Callable[[str], bytes] = j # fine
|
||||
|
||||
|
||||
def g(x: CallableTypeOf[j]):
|
||||
reveal_type(x) # revealed: (a: str) -> bytes
|
||||
```
|
||||
@@ -134,6 +149,7 @@ def g(x: CallableTypeOf[j]):
|
||||
```py
|
||||
from typing_extensions import NewType
|
||||
|
||||
|
||||
def _(name: str) -> None:
|
||||
_ = NewType(name, int) # error: [invalid-newtype] "The first argument to `NewType` must be a string literal"
|
||||
```
|
||||
@@ -205,6 +221,7 @@ class Bar:
|
||||
def __contains__(self, key: Foo) -> bool:
|
||||
return True
|
||||
|
||||
|
||||
reveal_type(Foo(42) + Bar()) # revealed: Foo
|
||||
reveal_type(Bar() + Foo(42)) # revealed: Foo
|
||||
reveal_type(Foo(42) < Bar()) # revealed: bool
|
||||
@@ -279,12 +296,16 @@ type:
|
||||
```py
|
||||
from collections.abc import Callable
|
||||
|
||||
|
||||
def f(_: Callable[[int | float], Foo]): ...
|
||||
|
||||
|
||||
f(Foo)
|
||||
|
||||
|
||||
def g(_: Callable[[int | float | complex], Bar]): ...
|
||||
|
||||
|
||||
g(Bar)
|
||||
```
|
||||
|
||||
@@ -321,6 +342,7 @@ class Bing:
|
||||
def __contains__(self, key: Foo) -> bool:
|
||||
return True
|
||||
|
||||
|
||||
reveal_type(Foo(3.14) + Bing()) # revealed: Foo
|
||||
reveal_type(Bing() + Foo(42)) # revealed: Foo
|
||||
reveal_type(Foo(3.14) < Bing()) # revealed: bool
|
||||
@@ -439,6 +461,7 @@ from typing import NewType
|
||||
|
||||
N = NewType("N", int)
|
||||
|
||||
|
||||
def f(x: N):
|
||||
reveal_type(isinstance(x, int)) # revealed: Literal[True]
|
||||
```
|
||||
@@ -470,6 +493,7 @@ from typing import NewType
|
||||
|
||||
X = NewType("X", int)
|
||||
|
||||
|
||||
class Foo(X): ... # error: [invalid-base]
|
||||
```
|
||||
|
||||
@@ -481,12 +505,15 @@ class Foo(X): ... # error: [invalid-base]
|
||||
from enum import Enum
|
||||
from typing import NewType
|
||||
|
||||
|
||||
class Foo(Enum):
|
||||
X = 0
|
||||
Y = 1
|
||||
|
||||
|
||||
N = NewType("N", Foo)
|
||||
|
||||
|
||||
def f(x: N):
|
||||
match x:
|
||||
case Foo.X:
|
||||
@@ -530,14 +557,18 @@ reveal_type(Bar(42)) # revealed: Bar
|
||||
```py
|
||||
from typing import NewType, Protocol, TypedDict
|
||||
|
||||
|
||||
class Id(Protocol):
|
||||
code: int
|
||||
|
||||
|
||||
UserId = NewType("UserId", Id) # error: [invalid-newtype]
|
||||
|
||||
|
||||
class Foo(TypedDict):
|
||||
a: int
|
||||
|
||||
|
||||
Bar = NewType("Bar", Foo) # error: [invalid-newtype]
|
||||
```
|
||||
|
||||
@@ -578,11 +609,15 @@ from stub import N, A
|
||||
|
||||
n = N(A()) # fine
|
||||
|
||||
|
||||
def f(x: A): ...
|
||||
|
||||
|
||||
f(n) # fine
|
||||
|
||||
|
||||
class Invalid: ...
|
||||
|
||||
|
||||
bad = N(Invalid()) # error: [invalid-argument-type]
|
||||
```
|
||||
|
||||
@@ -12,6 +12,7 @@ a1: Optional[bool]
|
||||
a2: Optional[Optional[bool]]
|
||||
a3: Optional[None]
|
||||
|
||||
|
||||
def f():
|
||||
# revealed: int | None
|
||||
reveal_type(a)
|
||||
@@ -41,6 +42,7 @@ from typing_extensions import Optional
|
||||
|
||||
a: Optional[int]
|
||||
|
||||
|
||||
def f():
|
||||
# revealed: int | None
|
||||
reveal_type(a)
|
||||
@@ -51,6 +53,7 @@ def f():
|
||||
```py
|
||||
from typing import Optional
|
||||
|
||||
|
||||
# error: [invalid-type-form] "`typing.Optional` requires exactly one argument when used in a type expression"
|
||||
def f(x: Optional) -> None:
|
||||
reveal_type(x) # revealed: Unknown
|
||||
|
||||
@@ -14,6 +14,7 @@ python-version = "3.13"
|
||||
```py
|
||||
from typing import Self
|
||||
|
||||
|
||||
class Shape:
|
||||
def set_scale(self: Self, scale: float) -> Self:
|
||||
reveal_type(self) # revealed: Self@set_scale
|
||||
@@ -26,21 +27,26 @@ class Shape:
|
||||
def inner() -> Self:
|
||||
reveal_type(self) # revealed: Self@nested_func
|
||||
return self
|
||||
|
||||
return inner()
|
||||
|
||||
def nested_func_without_enclosing_binding(self):
|
||||
def inner(x: Self):
|
||||
reveal_type(x) # revealed: Self@nested_func_without_enclosing_binding
|
||||
|
||||
inner(self)
|
||||
|
||||
|
||||
reveal_type(Shape().nested_type()) # revealed: list[Shape]
|
||||
reveal_type(Shape().nested_func()) # revealed: Shape
|
||||
|
||||
|
||||
class Circle(Shape):
|
||||
def set_scale(self: Self, scale: float) -> Self:
|
||||
reveal_type(self) # revealed: Self@set_scale
|
||||
return self
|
||||
|
||||
|
||||
class Outer:
|
||||
class Inner:
|
||||
def foo(self: Self) -> Self:
|
||||
@@ -62,6 +68,7 @@ python-version = "3.12"
|
||||
```py
|
||||
from typing import Self
|
||||
|
||||
|
||||
class A:
|
||||
def __init__(self):
|
||||
reveal_type(self) # revealed: Self@__init__
|
||||
@@ -103,6 +110,7 @@ class A:
|
||||
@staticmethod
|
||||
def a_staticmethod(x: int): ...
|
||||
|
||||
|
||||
a = A()
|
||||
|
||||
reveal_type(a.implicit_self()) # revealed: A
|
||||
@@ -123,9 +131,11 @@ Passing `self` implicitly also verifies the type:
|
||||
```py
|
||||
from typing import Never, Callable
|
||||
|
||||
|
||||
class Strange:
|
||||
def can_not_be_called(self: Never) -> None: ...
|
||||
|
||||
|
||||
# error: [invalid-argument-type] "Argument to bound method `can_not_be_called` is incorrect: Expected `Never`, found `Strange`"
|
||||
Strange().can_not_be_called()
|
||||
```
|
||||
@@ -147,6 +157,7 @@ The name `self` is not special in any way.
|
||||
def some_decorator[**P, R](f: Callable[P, R]) -> Callable[P, R]:
|
||||
return f
|
||||
|
||||
|
||||
class B:
|
||||
def name_does_not_matter(this) -> Self:
|
||||
reveal_type(this) # revealed: Self@name_does_not_matter
|
||||
@@ -184,12 +195,14 @@ class B:
|
||||
@some_decorator
|
||||
def decorated_static_method(self):
|
||||
reveal_type(self) # revealed: Unknown
|
||||
|
||||
# TODO: On Python <3.10, this should ideally be rejected, because `staticmethod` objects were not callable.
|
||||
@some_decorator
|
||||
@staticmethod
|
||||
def decorated_static_method_2(self):
|
||||
reveal_type(self) # revealed: Unknown
|
||||
|
||||
|
||||
reveal_type(B().name_does_not_matter()) # revealed: B
|
||||
reveal_type(B().positional_only(1)) # revealed: B
|
||||
reveal_type(B().keyword_only(x=1)) # revealed: B
|
||||
@@ -197,6 +210,7 @@ reveal_type(B().decorated_method()) # revealed: B
|
||||
|
||||
reveal_type(B().a_property) # revealed: B
|
||||
|
||||
|
||||
async def _():
|
||||
reveal_type(await B().async_method()) # revealed: B
|
||||
```
|
||||
@@ -208,12 +222,14 @@ from typing import Self, Generic, TypeVar
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
|
||||
class G(Generic[T]):
|
||||
def id(self) -> Self:
|
||||
reveal_type(self) # revealed: Self@id
|
||||
|
||||
return self
|
||||
|
||||
|
||||
reveal_type(G[int]().id()) # revealed: G[int]
|
||||
reveal_type(G[str]().id()) # revealed: G[str]
|
||||
```
|
||||
@@ -224,15 +240,18 @@ Free functions and nested functions do not use implicit `Self`:
|
||||
def not_a_method(self):
|
||||
reveal_type(self) # revealed: Unknown
|
||||
|
||||
|
||||
# error: [invalid-type-form]
|
||||
def does_not_return_self(self) -> Self:
|
||||
return self
|
||||
|
||||
|
||||
class C:
|
||||
def outer(self) -> None:
|
||||
def inner(self):
|
||||
reveal_type(self) # revealed: Unknown
|
||||
|
||||
|
||||
reveal_type(not_a_method) # revealed: def not_a_method(self) -> Unknown
|
||||
```
|
||||
|
||||
@@ -248,15 +267,18 @@ affect that subtitution. If we blindly substitute all occurrences of `Self`, we
|
||||
```py
|
||||
from typing import Self
|
||||
|
||||
|
||||
class Foo[T]:
|
||||
def foo(self: Self) -> T:
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class Bar:
|
||||
def bar(self: Self, x: Foo[Self]):
|
||||
# revealed: bound method Foo[Self@bar].foo() -> Self@bar
|
||||
reveal_type(x.foo)
|
||||
|
||||
|
||||
def f[U: Bar](x: Foo[U]):
|
||||
# revealed: bound method Foo[U@f].foo() -> U@f
|
||||
reveal_type(x.foo)
|
||||
@@ -272,10 +294,12 @@ python-version = "3.10"
|
||||
```py
|
||||
from typing_extensions import Self
|
||||
|
||||
|
||||
class C:
|
||||
def method(self: Self) -> Self:
|
||||
return self
|
||||
|
||||
|
||||
reveal_type(C().method()) # revealed: C
|
||||
```
|
||||
|
||||
@@ -286,6 +310,7 @@ reveal_type(C().method()) # revealed: C
|
||||
```py
|
||||
from typing import Self
|
||||
|
||||
|
||||
class Shape:
|
||||
def foo(self: Self) -> Self:
|
||||
return self
|
||||
@@ -295,8 +320,10 @@ class Shape:
|
||||
reveal_type(cls) # revealed: type[Self@bar]
|
||||
return cls()
|
||||
|
||||
|
||||
class Circle(Shape): ...
|
||||
|
||||
|
||||
reveal_type(Shape().foo()) # revealed: Shape
|
||||
reveal_type(Shape.bar()) # revealed: Shape
|
||||
|
||||
@@ -309,6 +336,7 @@ reveal_type(Circle.bar()) # revealed: Circle
|
||||
```py
|
||||
from typing import Self
|
||||
|
||||
|
||||
class Shape:
|
||||
def foo(self) -> Self:
|
||||
return self
|
||||
@@ -318,8 +346,10 @@ class Shape:
|
||||
reveal_type(cls) # revealed: type[Self@bar]
|
||||
return cls()
|
||||
|
||||
|
||||
class Circle(Shape): ...
|
||||
|
||||
|
||||
reveal_type(Shape().foo()) # revealed: Shape
|
||||
reveal_type(Shape.bar()) # revealed: Shape
|
||||
|
||||
@@ -332,6 +362,7 @@ reveal_type(Circle.bar()) # revealed: Circle
|
||||
```py
|
||||
from typing import Self
|
||||
|
||||
|
||||
class GenericShape[T]:
|
||||
def foo(self) -> Self:
|
||||
return self
|
||||
@@ -346,8 +377,10 @@ class GenericShape[T]:
|
||||
reveal_type(cls) # revealed: type[Self@baz]
|
||||
return cls()
|
||||
|
||||
|
||||
class GenericCircle[T](GenericShape[T]): ...
|
||||
|
||||
|
||||
reveal_type(GenericShape().foo()) # revealed: GenericShape[Unknown]
|
||||
reveal_type(GenericShape.bar()) # revealed: GenericShape[Unknown]
|
||||
reveal_type(GenericShape[int].bar()) # revealed: GenericShape[int]
|
||||
@@ -369,16 +402,19 @@ the return type should be the child's `Self` type variable, not the concrete chi
|
||||
```py
|
||||
from typing import Self
|
||||
|
||||
|
||||
class Parent:
|
||||
def copy(self) -> Self:
|
||||
return self
|
||||
|
||||
|
||||
class Child(Parent):
|
||||
def copy(self) -> Self:
|
||||
result = super().copy()
|
||||
reveal_type(result) # revealed: Self@copy
|
||||
return result
|
||||
|
||||
|
||||
# When called on concrete types, Self is substituted correctly.
|
||||
reveal_type(Child().copy()) # revealed: Child
|
||||
```
|
||||
@@ -388,11 +424,13 @@ The same applies to classmethods with `Self` return types:
|
||||
```py
|
||||
from typing import Self
|
||||
|
||||
|
||||
class Parent:
|
||||
@classmethod
|
||||
def create(cls) -> Self:
|
||||
return cls()
|
||||
|
||||
|
||||
class Child(Parent):
|
||||
@classmethod
|
||||
def create(cls) -> Self:
|
||||
@@ -400,6 +438,7 @@ class Child(Parent):
|
||||
reveal_type(result) # revealed: Self@create
|
||||
return result
|
||||
|
||||
|
||||
# When called on concrete types, Self is substituted correctly.
|
||||
reveal_type(Child.create()) # revealed: Child
|
||||
```
|
||||
@@ -412,6 +451,7 @@ TODO: The use of `Self` to annotate the `next_node` attribute should be
|
||||
```py
|
||||
from typing import Self
|
||||
|
||||
|
||||
class LinkedList:
|
||||
value: int
|
||||
next_node: Self
|
||||
@@ -422,6 +462,7 @@ class LinkedList:
|
||||
# error: [invalid-return-type]
|
||||
return self.next_node
|
||||
|
||||
|
||||
reveal_type(LinkedList().next()) # revealed: LinkedList
|
||||
```
|
||||
|
||||
@@ -432,8 +473,10 @@ from typing import Generic, TypeVar
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
|
||||
class C(Generic[T]):
|
||||
foo: T
|
||||
|
||||
def method(self) -> None:
|
||||
reveal_type(self) # revealed: Self@method
|
||||
reveal_type(self.foo) # revealed: T@C
|
||||
@@ -446,11 +489,14 @@ from typing import Self, Generic, TypeVar
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
|
||||
class Container(Generic[T]):
|
||||
value: T
|
||||
|
||||
def set_value(self: Self, value: T) -> Self:
|
||||
return self
|
||||
|
||||
|
||||
int_container: Container[int] = Container[int]()
|
||||
reveal_type(int_container) # revealed: Container[int]
|
||||
reveal_type(int_container.set_value(1)) # revealed: Container[int]
|
||||
@@ -466,26 +512,31 @@ a type that satisfies a bound.
|
||||
```py
|
||||
from typing import NewType
|
||||
|
||||
|
||||
class Base: ...
|
||||
|
||||
|
||||
class C[T: Base]:
|
||||
x: T
|
||||
|
||||
def g(self) -> None:
|
||||
pass
|
||||
|
||||
|
||||
# Calling a method on a specialized instance should not produce an error
|
||||
C[Base]().g()
|
||||
|
||||
# Test with a NewType bound
|
||||
K = NewType("K", int)
|
||||
|
||||
|
||||
class D[T: K]:
|
||||
x: T
|
||||
|
||||
def h(self) -> None:
|
||||
pass
|
||||
|
||||
|
||||
# Calling a method on a specialized instance should not produce an error
|
||||
D[K]().h()
|
||||
```
|
||||
@@ -499,6 +550,7 @@ TODO: <https://typing.python.org/en/latest/spec/generics.html#use-in-protocols>
|
||||
```py
|
||||
from typing import Self
|
||||
|
||||
|
||||
class Shape:
|
||||
def union(self: Self, other: Self | None):
|
||||
reveal_type(other) # revealed: Self@union | None
|
||||
@@ -512,10 +564,12 @@ This is a regression test for <https://github.com/astral-sh/ty/issues/1156>.
|
||||
```py
|
||||
from typing import Self
|
||||
|
||||
|
||||
class Container[T = bytes]:
|
||||
def __init__(self: Self, data: T | None = None) -> None:
|
||||
self.data = data
|
||||
|
||||
|
||||
reveal_type(Container()) # revealed: Container[bytes]
|
||||
reveal_type(Container(1)) # revealed: Container[int]
|
||||
reveal_type(Container("a")) # revealed: Container[str]
|
||||
@@ -527,20 +581,25 @@ reveal_type(Container(b"a")) # revealed: Container[bytes]
|
||||
```py
|
||||
from typing import Self, TypeVar, Generic
|
||||
|
||||
|
||||
class Container[T = bytes]:
|
||||
def method(self) -> Self:
|
||||
return self
|
||||
|
||||
|
||||
def _(c: Container[str], d: Container):
|
||||
reveal_type(c.method()) # revealed: Container[str]
|
||||
reveal_type(d.method()) # revealed: Container[bytes]
|
||||
|
||||
|
||||
T = TypeVar("T", default=bytes)
|
||||
|
||||
|
||||
class LegacyContainer(Generic[T]):
|
||||
def method(self) -> Self:
|
||||
return self
|
||||
|
||||
|
||||
def _(c: LegacyContainer[str], d: LegacyContainer):
|
||||
reveal_type(c.method()) # revealed: LegacyContainer[str]
|
||||
reveal_type(d.method()) # revealed: LegacyContainer[bytes]
|
||||
@@ -555,12 +614,15 @@ from typing import Self, Generic, TypeVar
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
|
||||
# error: [invalid-type-form]
|
||||
def x(s: Self): ...
|
||||
|
||||
|
||||
# error: [invalid-type-form]
|
||||
b: Self
|
||||
|
||||
|
||||
# TODO: "Self" cannot be used in a function with a `self` or `cls` parameter that has a type annotation other than "Self"
|
||||
class Foo:
|
||||
# TODO: This `self: T` annotation should be rejected because `T` is not `Self`
|
||||
@@ -578,11 +640,14 @@ class Foo:
|
||||
# error: [invalid-return-type]
|
||||
return Foo()
|
||||
|
||||
|
||||
class Bar(Generic[T]): ...
|
||||
|
||||
|
||||
# error: [invalid-type-form]
|
||||
class Baz(Bar[Self]): ...
|
||||
|
||||
|
||||
class MyMetaclass(type):
|
||||
# TODO: reject the Self usage. because self cannot be used within a metaclass.
|
||||
def __new__(cls) -> Self:
|
||||
@@ -604,9 +669,11 @@ from __future__ import annotations
|
||||
|
||||
from typing import final
|
||||
|
||||
|
||||
@final
|
||||
class Disjoint: ...
|
||||
|
||||
|
||||
class Explicit:
|
||||
# TODO: We could emit a warning if the annotated type of `self` is disjoint from `Explicit`
|
||||
def bad(self: Disjoint) -> None:
|
||||
@@ -615,15 +682,18 @@ class Explicit:
|
||||
def forward(self: Explicit) -> None:
|
||||
reveal_type(self) # revealed: Explicit
|
||||
|
||||
|
||||
# error: [invalid-argument-type] "Argument to bound method `bad` is incorrect: Expected `Disjoint`, found `Explicit`"
|
||||
Explicit().bad()
|
||||
|
||||
Explicit().forward()
|
||||
|
||||
|
||||
class ExplicitGeneric[T]:
|
||||
def special(self: ExplicitGeneric[int]) -> None:
|
||||
reveal_type(self) # revealed: ExplicitGeneric[int]
|
||||
|
||||
|
||||
ExplicitGeneric[int]().special()
|
||||
|
||||
# TODO: this should be an `invalid-argument-type` error
|
||||
@@ -638,6 +708,7 @@ specific type of the bound parameter.
|
||||
```py
|
||||
from typing import Self
|
||||
|
||||
|
||||
class C:
|
||||
def instance_method(self, other: Self) -> Self:
|
||||
return self
|
||||
@@ -646,13 +717,16 @@ class C:
|
||||
def class_method(cls) -> Self:
|
||||
return cls()
|
||||
|
||||
|
||||
# revealed: bound method C.instance_method(other: C) -> C
|
||||
reveal_type(C().instance_method)
|
||||
# revealed: bound method <class 'C'>.class_method() -> C
|
||||
reveal_type(C.class_method)
|
||||
|
||||
|
||||
class D(C): ...
|
||||
|
||||
|
||||
# revealed: bound method D.instance_method(other: D) -> D
|
||||
reveal_type(D().instance_method)
|
||||
# revealed: bound method <class 'D'>.class_method() -> D
|
||||
@@ -666,13 +740,16 @@ bound at `C.f`.
|
||||
from typing import Self
|
||||
from ty_extensions import generic_context
|
||||
|
||||
|
||||
class C[T]():
|
||||
def f(self: Self):
|
||||
def b(x: Self):
|
||||
reveal_type(x) # revealed: Self@f
|
||||
|
||||
# revealed: None
|
||||
reveal_type(generic_context(b))
|
||||
|
||||
|
||||
# revealed: ty_extensions.GenericContext[Self@f]
|
||||
reveal_type(generic_context(C.f))
|
||||
```
|
||||
@@ -684,13 +761,16 @@ Even if the `Self` annotation appears first in the nested function, it is the me
|
||||
from typing import Self
|
||||
from ty_extensions import generic_context
|
||||
|
||||
|
||||
class C:
|
||||
def f(self: "C"):
|
||||
def b(x: Self):
|
||||
reveal_type(x) # revealed: Self@f
|
||||
|
||||
# revealed: None
|
||||
reveal_type(generic_context(b))
|
||||
|
||||
|
||||
# revealed: None
|
||||
reveal_type(generic_context(C.f))
|
||||
```
|
||||
@@ -702,9 +782,11 @@ This makes sure that we don't bind `self` if it's not a positional parameter:
|
||||
```py
|
||||
from ty_extensions import CallableTypeOf
|
||||
|
||||
|
||||
class C:
|
||||
def method(*args, **kwargs) -> None: ...
|
||||
|
||||
|
||||
def _(c: CallableTypeOf[C().method]):
|
||||
reveal_type(c) # revealed: (...) -> None
|
||||
```
|
||||
|
||||
@@ -12,11 +12,13 @@ from typing_extensions import TypeVarTuple
|
||||
|
||||
Ts = TypeVarTuple("Ts")
|
||||
|
||||
|
||||
def append_int(*args: *Ts) -> tuple[*Ts, int]:
|
||||
reveal_type(args) # revealed: @Todo(PEP 646)
|
||||
|
||||
return (*args, 1)
|
||||
|
||||
|
||||
# TODO should be tuple[Literal[True], Literal["a"], int]
|
||||
reveal_type(append_int(True, "a")) # revealed: tuple[@Todo(PEP 646), ...]
|
||||
```
|
||||
|
||||
@@ -10,6 +10,7 @@ All of the following symbols can be mapped one-to-one with the actual type:
|
||||
```py
|
||||
import typing
|
||||
|
||||
|
||||
def f(
|
||||
list_bare: typing.List,
|
||||
list_parametrized: typing.List[int],
|
||||
@@ -65,6 +66,7 @@ In case the incorrect number of type arguments is passed, a diagnostic is given.
|
||||
```py
|
||||
import typing
|
||||
|
||||
|
||||
def f(
|
||||
# error: [invalid-type-form] "Legacy alias `typing.List` expected exactly 1 argument, got 2"
|
||||
incorrect_list: typing.List[int, int],
|
||||
@@ -120,23 +122,31 @@ from ty_extensions import reveal_mro
|
||||
### Built-ins
|
||||
####################
|
||||
|
||||
|
||||
class ListSubclass(typing.List): ...
|
||||
|
||||
|
||||
# revealed: (<class 'ListSubclass'>, <class 'list[Unknown]'>, <class 'MutableSequence[Unknown]'>, <class 'Sequence[Unknown]'>, <class 'Reversible[Unknown]'>, <class 'Collection[Unknown]'>, <class 'Iterable[Unknown]'>, <class 'Container[Unknown]'>, typing.Protocol, typing.Generic, <class 'object'>)
|
||||
reveal_mro(ListSubclass)
|
||||
|
||||
|
||||
class DictSubclass(typing.Dict): ...
|
||||
|
||||
|
||||
# revealed: (<class 'DictSubclass'>, <class 'dict[Unknown, Unknown]'>, <class 'MutableMapping[Unknown, Unknown]'>, <class 'Mapping[Unknown, Unknown]'>, <class 'Collection[Unknown]'>, <class 'Iterable[Unknown]'>, <class 'Container[Unknown]'>, typing.Protocol, typing.Generic, <class 'object'>)
|
||||
reveal_mro(DictSubclass)
|
||||
|
||||
|
||||
class SetSubclass(typing.Set): ...
|
||||
|
||||
|
||||
# revealed: (<class 'SetSubclass'>, <class 'set[Unknown]'>, <class 'MutableSet[Unknown]'>, <class 'AbstractSet[Unknown]'>, <class 'Collection[Unknown]'>, <class 'Iterable[Unknown]'>, <class 'Container[Unknown]'>, typing.Protocol, typing.Generic, <class 'object'>)
|
||||
reveal_mro(SetSubclass)
|
||||
|
||||
|
||||
class FrozenSetSubclass(typing.FrozenSet): ...
|
||||
|
||||
|
||||
# revealed: (<class 'FrozenSetSubclass'>, <class 'frozenset[Unknown]'>, <class 'AbstractSet[Unknown]'>, <class 'Collection[Unknown]'>, <class 'Iterable[Unknown]'>, <class 'Container[Unknown]'>, typing.Protocol, typing.Generic, <class 'object'>)
|
||||
reveal_mro(FrozenSetSubclass)
|
||||
|
||||
@@ -144,28 +154,38 @@ reveal_mro(FrozenSetSubclass)
|
||||
### `collections`
|
||||
####################
|
||||
|
||||
|
||||
class ChainMapSubclass(typing.ChainMap): ...
|
||||
|
||||
|
||||
# revealed: (<class 'ChainMapSubclass'>, <class 'ChainMap[Unknown, Unknown]'>, <class 'MutableMapping[Unknown, Unknown]'>, <class 'Mapping[Unknown, Unknown]'>, <class 'Collection[Unknown]'>, <class 'Iterable[Unknown]'>, <class 'Container[Unknown]'>, typing.Protocol, typing.Generic, <class 'object'>)
|
||||
reveal_mro(ChainMapSubclass)
|
||||
|
||||
|
||||
class CounterSubclass(typing.Counter): ...
|
||||
|
||||
|
||||
# revealed: (<class 'CounterSubclass'>, <class 'Counter[Unknown]'>, <class 'dict[Unknown, int]'>, <class 'MutableMapping[Unknown, int]'>, <class 'Mapping[Unknown, int]'>, <class 'Collection[Unknown]'>, <class 'Iterable[Unknown]'>, <class 'Container[Unknown]'>, typing.Protocol, typing.Generic, <class 'object'>)
|
||||
reveal_mro(CounterSubclass)
|
||||
|
||||
|
||||
class DefaultDictSubclass(typing.DefaultDict): ...
|
||||
|
||||
|
||||
# revealed: (<class 'DefaultDictSubclass'>, <class 'defaultdict[Unknown, Unknown]'>, <class 'dict[Unknown, Unknown]'>, <class 'MutableMapping[Unknown, Unknown]'>, <class 'Mapping[Unknown, Unknown]'>, <class 'Collection[Unknown]'>, <class 'Iterable[Unknown]'>, <class 'Container[Unknown]'>, typing.Protocol, typing.Generic, <class 'object'>)
|
||||
reveal_mro(DefaultDictSubclass)
|
||||
|
||||
|
||||
class DequeSubclass(typing.Deque): ...
|
||||
|
||||
|
||||
# revealed: (<class 'DequeSubclass'>, <class 'deque[Unknown]'>, <class 'MutableSequence[Unknown]'>, <class 'Sequence[Unknown]'>, <class 'Reversible[Unknown]'>, <class 'Collection[Unknown]'>, <class 'Iterable[Unknown]'>, <class 'Container[Unknown]'>, typing.Protocol, typing.Generic, <class 'object'>)
|
||||
reveal_mro(DequeSubclass)
|
||||
|
||||
|
||||
class OrderedDictSubclass(typing.OrderedDict): ...
|
||||
|
||||
|
||||
# revealed: (<class 'OrderedDictSubclass'>, <class 'OrderedDict[Unknown, Unknown]'>, <class 'dict[Unknown, Unknown]'>, <class 'MutableMapping[Unknown, Unknown]'>, <class 'Mapping[Unknown, Unknown]'>, <class 'Collection[Unknown]'>, <class 'Iterable[Unknown]'>, <class 'Container[Unknown]'>, typing.Protocol, typing.Generic, <class 'object'>)
|
||||
reveal_mro(OrderedDictSubclass)
|
||||
```
|
||||
|
||||
@@ -35,6 +35,7 @@ def f(v: tuple[int, "str"]):
|
||||
def f(v: "Foo"):
|
||||
reveal_type(v) # revealed: Foo
|
||||
|
||||
|
||||
class Foo: ...
|
||||
```
|
||||
|
||||
@@ -52,6 +53,7 @@ def f(v: "Foo"):
|
||||
def f(v: int | "Foo"):
|
||||
reveal_type(v) # revealed: int | Foo
|
||||
|
||||
|
||||
class Foo: ...
|
||||
```
|
||||
|
||||
@@ -60,10 +62,12 @@ class Foo: ...
|
||||
```py
|
||||
from typing import Literal
|
||||
|
||||
|
||||
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: ...
|
||||
```
|
||||
|
||||
@@ -104,6 +108,7 @@ def f1(
|
||||
```py
|
||||
from typing import Literal
|
||||
|
||||
|
||||
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", b"c"]
|
||||
```
|
||||
@@ -113,12 +118,14 @@ def f(v: Literal["a", r"b", b"c", "d" "e", "\N{LATIN SMALL LETTER F}", "\x67", "
|
||||
```py
|
||||
MyType = int
|
||||
|
||||
|
||||
class Aliases:
|
||||
MyType = str
|
||||
|
||||
forward: "MyType" = "value"
|
||||
not_forward: MyType = "value"
|
||||
|
||||
|
||||
reveal_type(Aliases.forward) # revealed: str
|
||||
reveal_type(Aliases.not_forward) # revealed: str
|
||||
```
|
||||
@@ -132,8 +139,10 @@ c: "Foo"
|
||||
# error: [invalid-assignment] "Object of type `Literal[1]` is not assignable to `Foo`"
|
||||
d: "Foo" = 1
|
||||
|
||||
|
||||
class Foo: ...
|
||||
|
||||
|
||||
c = Foo()
|
||||
|
||||
reveal_type(a) # revealed: Literal[1]
|
||||
@@ -211,6 +220,7 @@ def valid(
|
||||
reveal_type(a1) # revealed: int | str
|
||||
reveal_type(a2) # revealed: int | str
|
||||
|
||||
|
||||
def invalid(
|
||||
# error: [invalid-syntax-in-forward-annotation]
|
||||
a1: """
|
||||
|
||||
@@ -15,6 +15,7 @@ a4: Union[Union[bytes, str]]
|
||||
a5: Union[int]
|
||||
a6: Union[()]
|
||||
|
||||
|
||||
def f():
|
||||
# revealed: int | str
|
||||
reveal_type(a)
|
||||
@@ -55,6 +56,7 @@ from typing_extensions import Union
|
||||
|
||||
a: Union[int, str]
|
||||
|
||||
|
||||
def f():
|
||||
# revealed: int | str
|
||||
reveal_type(a)
|
||||
@@ -65,6 +67,7 @@ def f():
|
||||
```py
|
||||
from typing import Union
|
||||
|
||||
|
||||
# error: [invalid-type-form] "`typing.Union` requires at least one argument when used in a type expression"
|
||||
def f(x: Union) -> None:
|
||||
reveal_type(x) # revealed: Unknown
|
||||
@@ -80,6 +83,7 @@ python-version = "3.10"
|
||||
```py
|
||||
X = int | str
|
||||
|
||||
|
||||
def f(y: X):
|
||||
reveal_type(y) # revealed: int | str
|
||||
```
|
||||
|
||||
@@ -12,34 +12,44 @@ P = ParamSpec("P")
|
||||
Ts = TypeVarTuple("Ts")
|
||||
R_co = TypeVar("R_co", covariant=True)
|
||||
|
||||
|
||||
def f(*args: Unpack[Ts]) -> tuple[Unpack[Ts]]:
|
||||
reveal_type(args) # revealed: tuple[@Todo(`Unpack[]` special form), ...]
|
||||
return args
|
||||
|
||||
|
||||
def i(callback: Callable[Concatenate[int, P], R_co], *args: P.args, **kwargs: P.kwargs) -> R_co:
|
||||
reveal_type(args) # revealed: P@i.args
|
||||
reveal_type(kwargs) # revealed: P@i.kwargs
|
||||
return callback(42, *args, **kwargs)
|
||||
|
||||
|
||||
class Foo:
|
||||
def method(self, x: Self):
|
||||
reveal_type(x) # revealed: Self@method
|
||||
|
||||
|
||||
def ex2(msg: str):
|
||||
def wrapper(fn: Callable[P, R_co]) -> Callable[P, R_co]:
|
||||
def wrapped(*args: P.args, **kwargs: P.kwargs) -> R_co:
|
||||
print(msg)
|
||||
return fn(*args, **kwargs)
|
||||
|
||||
return wrapped
|
||||
|
||||
return wrapper
|
||||
|
||||
|
||||
def ex3(msg: str):
|
||||
P = ParamSpec("P")
|
||||
|
||||
def wrapper(fn: Callable[P, R_co]) -> Callable[P, R_co]:
|
||||
def wrapped(*args: P.args, **kwargs: P.kwargs) -> R_co:
|
||||
print(msg)
|
||||
return fn(*args, **kwargs)
|
||||
|
||||
return wrapped
|
||||
|
||||
return wrapper
|
||||
```
|
||||
|
||||
@@ -50,6 +60,7 @@ One thing that is supported is error messages for using special forms in type ex
|
||||
```py
|
||||
from typing_extensions import Unpack, TypeGuard, TypeIs, Concatenate, ParamSpec, Generic
|
||||
|
||||
|
||||
def _(
|
||||
a: Unpack, # error: [invalid-type-form] "`typing.Unpack` requires exactly one argument when used in a type expression"
|
||||
b: TypeGuard, # error: [invalid-type-form] "`typing.TypeGuard` requires exactly one argument when used in a type expression"
|
||||
@@ -77,14 +88,28 @@ from typing import Callable
|
||||
from typing_extensions import Self, Unpack, TypeGuard, TypeIs, Concatenate, Generic
|
||||
from ty_extensions import reveal_mro
|
||||
|
||||
|
||||
class A(Self): ... # error: [invalid-base]
|
||||
|
||||
|
||||
class B(Unpack): ... # error: [invalid-base]
|
||||
|
||||
|
||||
class C(TypeGuard): ... # error: [invalid-base]
|
||||
|
||||
|
||||
class D(TypeIs): ... # error: [invalid-base]
|
||||
|
||||
|
||||
class E(Concatenate): ... # error: [invalid-base]
|
||||
|
||||
|
||||
class F(Callable): ...
|
||||
|
||||
|
||||
class G(Generic): ... # error: [invalid-base] "Cannot inherit from plain `Generic`"
|
||||
|
||||
|
||||
reveal_mro(F) # revealed: (<class 'F'>, @Todo(Support for Callable as a base class), <class 'object'>)
|
||||
```
|
||||
|
||||
@@ -105,6 +130,7 @@ T = TypeVar("T")
|
||||
# error: [invalid-type-form] "Special form `typing.TypeAlias` expected no type parameter"
|
||||
X: TypeAlias[T] = int
|
||||
|
||||
|
||||
class Foo[T]:
|
||||
# error: [invalid-type-form] "Special form `typing.Self` expected no type parameter"
|
||||
# error: [invalid-type-form] "Special form `typing.Self` expected no type parameter"
|
||||
|
||||
@@ -14,5 +14,6 @@ MyTypedDict = typing.TypedDict("MyTypedDict", {"foo": int})
|
||||
MyNamedTuple1 = typing.NamedTuple("MyNamedTuple1", [("foo", int)])
|
||||
MyNamedTuple2 = collections.namedtuple("MyNamedTuple2", ["foo"])
|
||||
|
||||
|
||||
def f(a: MyEnum, b: MyTypedDict, c: MyNamedTuple1, d: MyNamedTuple2): ...
|
||||
```
|
||||
|
||||
@@ -11,6 +11,7 @@ from typing_extensions import Final, ReadOnly, TypedDict
|
||||
X: Final = 42
|
||||
Y: Final[int] = 42
|
||||
|
||||
|
||||
class Bar(TypedDict):
|
||||
z: ReadOnly[bytes]
|
||||
```
|
||||
@@ -22,6 +23,7 @@ One thing that is supported is error messages for using type qualifiers in type
|
||||
```py
|
||||
from typing_extensions import Final, ClassVar, Required, NotRequired, ReadOnly
|
||||
|
||||
|
||||
def _(
|
||||
# error: [invalid-type-form] "Type qualifier `typing.Final` is not allowed in type expressions (only in annotation expressions)"
|
||||
a: Final | int,
|
||||
@@ -42,7 +44,12 @@ You can't inherit from a type qualifier.
|
||||
```py
|
||||
from typing_extensions import Final, ClassVar, Required, NotRequired, ReadOnly
|
||||
|
||||
|
||||
class A(Final): ... # error: [invalid-base]
|
||||
|
||||
|
||||
class B(ClassVar): ... # error: [invalid-base]
|
||||
|
||||
|
||||
class C(ReadOnly): ... # error: [invalid-base]
|
||||
```
|
||||
|
||||
@@ -239,10 +239,12 @@ python-version = "3.12"
|
||||
```py
|
||||
from typing import Any
|
||||
|
||||
|
||||
class X[T]:
|
||||
def __init__(self, value: T):
|
||||
self.value = value
|
||||
|
||||
|
||||
x1: X[int] = X(1)
|
||||
reveal_type(x1) # revealed: X[int]
|
||||
|
||||
@@ -252,13 +254,16 @@ reveal_type(x2) # revealed: X[int | None]
|
||||
x3: X[int | None] | None = X(1)
|
||||
reveal_type(x3) # revealed: X[int | None]
|
||||
|
||||
|
||||
def _[T](x1: X[T]):
|
||||
x2: X[T | int] = X(x1.value)
|
||||
reveal_type(x2) # revealed: X[T@_ | int]
|
||||
|
||||
|
||||
x4: X[Any] = X(1)
|
||||
reveal_type(x4) # revealed: X[Any]
|
||||
|
||||
|
||||
def _(flag: bool):
|
||||
x5: X[int | None] = X(1) if flag else X(2)
|
||||
reveal_type(x5) # revealed: X[int | None]
|
||||
@@ -267,10 +272,12 @@ def _(flag: bool):
|
||||
```py
|
||||
from dataclasses import dataclass
|
||||
|
||||
|
||||
@dataclass
|
||||
class Y[T]:
|
||||
value: T
|
||||
|
||||
|
||||
y1 = Y(value=1)
|
||||
reveal_type(y1) # revealed: Y[int]
|
||||
|
||||
@@ -285,6 +292,7 @@ class Z[T]:
|
||||
def __new__(cls, value: T):
|
||||
return super().__new__(cls)
|
||||
|
||||
|
||||
z1 = Z(1)
|
||||
reveal_type(z1) # revealed: Z[int]
|
||||
|
||||
@@ -352,8 +360,10 @@ from __future__ import annotations
|
||||
|
||||
x: Foo
|
||||
|
||||
|
||||
class Foo: ...
|
||||
|
||||
|
||||
x = Foo()
|
||||
reveal_type(x) # revealed: Foo
|
||||
```
|
||||
@@ -379,8 +389,10 @@ python-version = "3.14"
|
||||
```py
|
||||
x: Foo
|
||||
|
||||
|
||||
class Foo: ...
|
||||
|
||||
|
||||
x = Foo()
|
||||
reveal_type(x) # revealed: Foo
|
||||
```
|
||||
@@ -404,9 +416,11 @@ python-version = "3.12"
|
||||
```py
|
||||
from typing import Literal, Sequence
|
||||
|
||||
|
||||
def f[T](x: T) -> list[T]:
|
||||
return [x]
|
||||
|
||||
|
||||
x1 = f("a")
|
||||
reveal_type(x1) # revealed: list[str]
|
||||
|
||||
@@ -428,9 +442,11 @@ x6: list[int] = f("a")
|
||||
# error: [invalid-assignment] "Object of type `list[str]` is not assignable to `tuple[int]`"
|
||||
x7: tuple[int] = f("a")
|
||||
|
||||
|
||||
def f2[T: int](x: T) -> T:
|
||||
return x
|
||||
|
||||
|
||||
x8: int = f2(True)
|
||||
reveal_type(x8) # revealed: Literal[True]
|
||||
|
||||
@@ -453,12 +469,15 @@ A function's arguments are also inferred using the type context:
|
||||
```py
|
||||
from typing import TypedDict
|
||||
|
||||
|
||||
class TD(TypedDict):
|
||||
x: int
|
||||
|
||||
|
||||
def f[T](x: list[T]) -> T:
|
||||
return x[0]
|
||||
|
||||
|
||||
a: TD = f([{"x": 0}, {"x": 1}])
|
||||
reveal_type(a) # revealed: TD
|
||||
|
||||
@@ -483,12 +502,15 @@ But not in a way that leads to assignability errors:
|
||||
```py
|
||||
from typing import TypedDict, Any
|
||||
|
||||
|
||||
class TD(TypedDict, total=False):
|
||||
x: str
|
||||
|
||||
|
||||
class TD2(TypedDict):
|
||||
x: str
|
||||
|
||||
|
||||
def f(self, dt: dict[str, Any], key: str):
|
||||
x1: TD = dt.get(key, {})
|
||||
reveal_type(x1) # revealed: Any
|
||||
@@ -525,15 +547,19 @@ python-version = "3.14"
|
||||
```py
|
||||
from typing import Any
|
||||
|
||||
|
||||
def f[T](x: T) -> list[T]:
|
||||
return [x]
|
||||
|
||||
|
||||
def f2[T](x: T) -> list[T] | None:
|
||||
return [x]
|
||||
|
||||
|
||||
def f3[T](x: T) -> list[T] | dict[T, T]:
|
||||
return [x]
|
||||
|
||||
|
||||
a = f(1)
|
||||
reveal_type(a) # revealed: list[int]
|
||||
|
||||
@@ -564,29 +590,37 @@ We only prefer the declared type if it is in non-covariant position.
|
||||
class Bivariant[T]:
|
||||
pass
|
||||
|
||||
|
||||
class Covariant[T]:
|
||||
def pop(self) -> T:
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class Contravariant[T]:
|
||||
def push(self, value: T) -> None:
|
||||
pass
|
||||
|
||||
|
||||
class Invariant[T]:
|
||||
x: T
|
||||
|
||||
|
||||
def bivariant[T](x: T) -> Bivariant[T]:
|
||||
return Bivariant()
|
||||
|
||||
|
||||
def covariant[T](x: T) -> Covariant[T]:
|
||||
return Covariant()
|
||||
|
||||
|
||||
def contravariant[T](x: T) -> Contravariant[T]:
|
||||
return Contravariant()
|
||||
|
||||
|
||||
def invariant[T](x: T) -> Invariant[T]:
|
||||
return Invariant()
|
||||
|
||||
|
||||
x1 = bivariant(1)
|
||||
x2 = covariant(1)
|
||||
x3 = contravariant(1)
|
||||
@@ -614,6 +648,7 @@ class X[T]:
|
||||
def pop(self) -> T:
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
x1: X[int | None] = X()
|
||||
reveal_type(x1) # revealed: X[None]
|
||||
```
|
||||
@@ -650,16 +685,20 @@ reveal_type(x5) # revealed: list[Iterable[Any]]
|
||||
x6: Iterable[list[Any]] = [[1, 2, 3]]
|
||||
reveal_type(x6) # revealed: list[list[Any]]
|
||||
|
||||
|
||||
class X[T]:
|
||||
value: T
|
||||
|
||||
def __init__(self, value: T): ...
|
||||
|
||||
|
||||
class A[T](X[T]): ...
|
||||
|
||||
|
||||
def a[T](value: T) -> A[T]:
|
||||
return A(value)
|
||||
|
||||
|
||||
x7: A[object] = A(1)
|
||||
reveal_type(x7) # revealed: A[object]
|
||||
|
||||
@@ -672,9 +711,11 @@ reveal_type(x9) # revealed: A[object]
|
||||
x10: X[object] | None = a(1)
|
||||
reveal_type(x10) # revealed: A[object]
|
||||
|
||||
|
||||
def f[T](x: T) -> list[list[T]]:
|
||||
return [[x]]
|
||||
|
||||
|
||||
x11: Sequence[Sequence[Any]] = f(1)
|
||||
reveal_type(x11) # revealed: list[list[int]]
|
||||
|
||||
@@ -695,28 +736,35 @@ python-version = "3.12"
|
||||
```py
|
||||
from typing import reveal_type, TypedDict
|
||||
|
||||
|
||||
def identity[T](x: T) -> T:
|
||||
return x
|
||||
|
||||
|
||||
def _(narrow: dict[str, str], target: list[str] | dict[str, str] | None):
|
||||
target = identity(narrow)
|
||||
reveal_type(target) # revealed: dict[str, str]
|
||||
|
||||
|
||||
def _(narrow: list[str], target: list[str] | dict[str, str] | None):
|
||||
target = identity(narrow)
|
||||
reveal_type(target) # revealed: list[str]
|
||||
|
||||
|
||||
def _(narrow: list[str] | dict[str, str], target: list[str] | dict[str, str] | None):
|
||||
target = identity(narrow)
|
||||
reveal_type(target) # revealed: list[str] | dict[str, str]
|
||||
|
||||
|
||||
class TD(TypedDict):
|
||||
x: int
|
||||
|
||||
|
||||
def _(target: list[TD] | dict[str, TD] | None):
|
||||
target = identity([{"x": 1}])
|
||||
reveal_type(target) # revealed: list[TD]
|
||||
|
||||
|
||||
def _(target: list[TD] | dict[str, TD] | None):
|
||||
target = identity({"x": {"x": 1}})
|
||||
reveal_type(target) # revealed: dict[str, TD]
|
||||
@@ -733,9 +781,11 @@ python-version = "3.12"
|
||||
def identity[T](x: T) -> T:
|
||||
return x
|
||||
|
||||
|
||||
def lst[T](x: T) -> list[T]:
|
||||
return [x]
|
||||
|
||||
|
||||
def _(i: int):
|
||||
a: int | None = i
|
||||
b: int | None = identity(i)
|
||||
@@ -765,9 +815,11 @@ def _(i: int):
|
||||
reveal_type(b) # revealed: list[Unknown]
|
||||
reveal_type(c) # revealed: list[Unknown]
|
||||
|
||||
|
||||
def f[T](x: list[T]) -> T:
|
||||
return x[0]
|
||||
|
||||
|
||||
def _(a: int, b: str, c: int | str):
|
||||
x1: int = f(lst(a))
|
||||
reveal_type(x1) # revealed: int
|
||||
|
||||
@@ -23,14 +23,17 @@ class C:
|
||||
def __isub__(self, other: int) -> str:
|
||||
return "Hello, world!"
|
||||
|
||||
|
||||
x = C()
|
||||
x -= 1
|
||||
reveal_type(x) # revealed: str
|
||||
|
||||
|
||||
class C:
|
||||
def __iadd__(self, other: str) -> int:
|
||||
return 1
|
||||
|
||||
|
||||
x = C()
|
||||
x += "Hello"
|
||||
reveal_type(x) # revealed: int
|
||||
@@ -45,6 +48,7 @@ class C:
|
||||
def __isub__(self, other: str) -> int:
|
||||
return 42
|
||||
|
||||
|
||||
x = C()
|
||||
# error: [unsupported-operator] "Operator `-=` is not supported between objects of type `C` and `Literal[1]`"
|
||||
x -= 1
|
||||
@@ -58,9 +62,12 @@ reveal_type(x) # revealed: int
|
||||
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
|
||||
|
||||
@@ -76,6 +83,7 @@ def _(flag: bool):
|
||||
def _(flag: bool):
|
||||
class Foo:
|
||||
if flag:
|
||||
|
||||
def __iadd__(self, other: str) -> int:
|
||||
return 42
|
||||
|
||||
@@ -94,7 +102,9 @@ def _(flag: bool):
|
||||
class Foo:
|
||||
def __add__(self, other: str) -> str:
|
||||
return "Hello, world!"
|
||||
|
||||
if flag:
|
||||
|
||||
def __iadd__(self, other: str) -> int:
|
||||
return 42
|
||||
|
||||
@@ -111,7 +121,9 @@ 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
|
||||
|
||||
@@ -148,7 +160,9 @@ 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
|
||||
|
||||
@@ -175,8 +189,10 @@ class Meta(type):
|
||||
def __iadd__(cls, other: int) -> str:
|
||||
return ""
|
||||
|
||||
|
||||
class C(metaclass=Meta): ...
|
||||
|
||||
|
||||
cls = C
|
||||
cls += 1
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
async def retrieve() -> int:
|
||||
return 42
|
||||
|
||||
|
||||
async def main():
|
||||
result = await retrieve()
|
||||
|
||||
@@ -19,9 +20,11 @@ from typing import TypeVar
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
|
||||
async def persist(x: T) -> T:
|
||||
return x
|
||||
|
||||
|
||||
async def f(x: int):
|
||||
result = await persist(x)
|
||||
|
||||
@@ -36,9 +39,11 @@ async def f(x: int):
|
||||
import asyncio
|
||||
import concurrent.futures
|
||||
|
||||
|
||||
def blocking_function() -> int:
|
||||
return 42
|
||||
|
||||
|
||||
async def main():
|
||||
loop = asyncio.get_event_loop()
|
||||
with concurrent.futures.ThreadPoolExecutor() as pool:
|
||||
@@ -51,9 +56,11 @@ async def main():
|
||||
```py
|
||||
import asyncio
|
||||
|
||||
|
||||
async def f() -> int:
|
||||
return 1
|
||||
|
||||
|
||||
async def main():
|
||||
task = asyncio.create_task(f())
|
||||
|
||||
@@ -67,9 +74,11 @@ async def main():
|
||||
```py
|
||||
import asyncio
|
||||
|
||||
|
||||
async def task(name: str) -> int:
|
||||
return len(name)
|
||||
|
||||
|
||||
async def main():
|
||||
(a, b) = await asyncio.gather(
|
||||
task("A"),
|
||||
@@ -113,6 +122,7 @@ final type of the `await` expression, we retrieve that third argument of the `Ge
|
||||
```py
|
||||
from typing import Generator
|
||||
|
||||
|
||||
def _():
|
||||
result = yield from retrieve().__await__()
|
||||
reveal_type(result) # revealed: int
|
||||
@@ -127,5 +137,6 @@ not just `Unknown`:
|
||||
async def f():
|
||||
pass
|
||||
|
||||
|
||||
reveal_type(f()) # revealed: CoroutineType[Any, Any, Unknown]
|
||||
```
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -18,9 +18,11 @@ python-version = "3.12"
|
||||
```py
|
||||
from typing import Literal
|
||||
|
||||
|
||||
def list1[T](x: T) -> list[T]:
|
||||
return [x]
|
||||
|
||||
|
||||
l1: list[Literal[1]] = list1(1)
|
||||
reveal_type(l1) # revealed: list[Literal[1]]
|
||||
|
||||
@@ -30,6 +32,7 @@ reveal_type(l2) # revealed: list[int]
|
||||
l3: list[int | str] | None = list1(1)
|
||||
reveal_type(l3) # revealed: list[int | str]
|
||||
|
||||
|
||||
def _(l: list[int] | None = None):
|
||||
l1 = l or list()
|
||||
reveal_type(l1) # revealed: (list[int] & ~AlwaysFalsy) | list[Unknown]
|
||||
@@ -38,9 +41,11 @@ def _(l: list[int] | None = None):
|
||||
# it would be better if this were `list[int]`? (https://github.com/astral-sh/ty/issues/136)
|
||||
reveal_type(l2) # revealed: (list[int] & ~AlwaysFalsy) | list[Unknown]
|
||||
|
||||
|
||||
def f[T](x: T, cond: bool) -> T | list[T]:
|
||||
return x if cond else [x]
|
||||
|
||||
|
||||
l5: int | list[int] = f(1, True)
|
||||
|
||||
a: list[int] = [1, 2, *(3, 4, 5)]
|
||||
@@ -55,9 +60,11 @@ reveal_type(b) # revealed: list[list[int]]
|
||||
```py
|
||||
from typing import TypedDict
|
||||
|
||||
|
||||
class TD(TypedDict):
|
||||
x: int
|
||||
|
||||
|
||||
d1 = {"x": 1}
|
||||
d2: TD = {"x": 1}
|
||||
d3: dict[str, int] = {"x": 1}
|
||||
@@ -69,9 +76,11 @@ reveal_type(d2) # revealed: TD
|
||||
reveal_type(d3) # revealed: dict[str, int]
|
||||
reveal_type(d4) # revealed: TD
|
||||
|
||||
|
||||
def _() -> TD:
|
||||
return {"x": 1}
|
||||
|
||||
|
||||
def _() -> TD:
|
||||
# error: [missing-typed-dict-key] "Missing required key 'x' in TypedDict `TD` constructor"
|
||||
# error: [invalid-return-type]
|
||||
@@ -88,12 +97,15 @@ python-version = "3.12"
|
||||
```py
|
||||
from typing import overload, Callable
|
||||
|
||||
|
||||
def list1[T](x: T) -> list[T]:
|
||||
return [x]
|
||||
|
||||
|
||||
def get_data() -> dict | None:
|
||||
return {}
|
||||
|
||||
|
||||
def wrap_data() -> list[dict]:
|
||||
if not (res := get_data()):
|
||||
return list1({})
|
||||
@@ -103,15 +115,18 @@ def wrap_data() -> list[dict]:
|
||||
# by bidirectional type inference using the annotated return type, and the type of `res` is not used.
|
||||
return list1(res)
|
||||
|
||||
|
||||
def wrap_data2() -> list[dict] | None:
|
||||
if not (res := get_data()):
|
||||
return None
|
||||
reveal_type(list1(res)) # revealed: list[dict[Unknown, Unknown] & ~AlwaysFalsy]
|
||||
return list1(res)
|
||||
|
||||
|
||||
def deco[T](func: Callable[[], T]) -> Callable[[], T]:
|
||||
return func
|
||||
|
||||
|
||||
def outer() -> Callable[[], list[dict]]:
|
||||
@deco
|
||||
def inner() -> list[dict]:
|
||||
@@ -119,8 +134,10 @@ def outer() -> Callable[[], list[dict]]:
|
||||
return list1({})
|
||||
reveal_type(list1(res)) # revealed: list[dict[Unknown, Unknown] & ~AlwaysFalsy]
|
||||
return list1(res)
|
||||
|
||||
return inner
|
||||
|
||||
|
||||
@overload
|
||||
def f(x: int) -> list[int]: ...
|
||||
@overload
|
||||
@@ -132,15 +149,19 @@ def f(x: int | str) -> list[int] | list[str]:
|
||||
else:
|
||||
return list1(x)
|
||||
|
||||
|
||||
reveal_type(f(1)) # revealed: list[int]
|
||||
reveal_type(f("a")) # revealed: list[str]
|
||||
|
||||
|
||||
async def g() -> list[int | str]:
|
||||
return list1(1)
|
||||
|
||||
|
||||
def h[T](x: T, cond: bool) -> T | list[T]:
|
||||
return i(x, cond)
|
||||
|
||||
|
||||
def i[T](x: T, cond: bool) -> T | list[T]:
|
||||
return x if cond else [x]
|
||||
```
|
||||
@@ -160,6 +181,7 @@ Function parameter annotations:
|
||||
```py
|
||||
def b(x: list[Literal[1]]): ...
|
||||
|
||||
|
||||
b([1])
|
||||
```
|
||||
|
||||
@@ -170,6 +192,7 @@ class C:
|
||||
def __init__(self, x: list[Literal[1]]): ...
|
||||
def foo(self, x: list[Literal[1]]): ...
|
||||
|
||||
|
||||
C([1]).foo([1])
|
||||
```
|
||||
|
||||
@@ -187,6 +210,7 @@ class E:
|
||||
a: list[Literal[1]]
|
||||
b: list[Literal[1]]
|
||||
|
||||
|
||||
def _(e: E):
|
||||
e.a = [1]
|
||||
E.b = [1]
|
||||
@@ -211,6 +235,7 @@ Both meta and class/instance attribute annotations are used as type context:
|
||||
```py
|
||||
from typing import Literal, Any
|
||||
|
||||
|
||||
class DataDescriptor:
|
||||
def __get__(self, instance: object, owner: type | None = None) -> list[Literal[1]]:
|
||||
return []
|
||||
@@ -218,9 +243,11 @@ class DataDescriptor:
|
||||
def __set__(self, instance: object, value: list[Literal[1]]) -> None:
|
||||
pass
|
||||
|
||||
|
||||
def lst[T](x: T) -> list[T]:
|
||||
return [x]
|
||||
|
||||
|
||||
def _(flag: bool):
|
||||
class Meta(type):
|
||||
if flag:
|
||||
@@ -242,15 +269,19 @@ For union targets, each element of the union is considered as a separate type co
|
||||
```py
|
||||
from typing import Literal
|
||||
|
||||
|
||||
class X:
|
||||
x: list[int | str]
|
||||
|
||||
|
||||
class Y:
|
||||
x: list[int | None]
|
||||
|
||||
|
||||
def lst[T](x: T) -> list[T]:
|
||||
return [x]
|
||||
|
||||
|
||||
def _(xy: X | Y):
|
||||
xy.x = lst(1)
|
||||
```
|
||||
@@ -269,12 +300,14 @@ calls:
|
||||
def f[T](x: T) -> list[T]:
|
||||
return [x]
|
||||
|
||||
|
||||
class A:
|
||||
def __new__(cls, value: list[int | str]):
|
||||
return super().__new__(cls, value)
|
||||
|
||||
def __init__(self, value: list[int | None]): ...
|
||||
|
||||
|
||||
A(f(1))
|
||||
|
||||
# error: [invalid-argument-type] "Argument to function `__new__` is incorrect: Expected `list[int | str]`, found `list[list[Unknown]]`"
|
||||
@@ -295,6 +328,7 @@ The type context is propagated through both branches of conditional expressions:
|
||||
def f[T](x: T) -> list[T]:
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
def _(flag: bool):
|
||||
x1 = f(1) if flag else f(2)
|
||||
reveal_type(x1) # revealed: list[int]
|
||||
@@ -310,19 +344,24 @@ The key and value parameters types are used as type context for `__setitem__` du
|
||||
```py
|
||||
from typing import TypedDict
|
||||
|
||||
|
||||
class Bar(TypedDict):
|
||||
baz: float
|
||||
|
||||
|
||||
def _(x: dict[str, Bar]):
|
||||
x["foo"] = reveal_type({"baz": 2}) # revealed: Bar
|
||||
|
||||
|
||||
class X:
|
||||
def __setitem__(self, key: Bar, value: Bar): ...
|
||||
|
||||
|
||||
def _(x: X):
|
||||
# revealed: Bar
|
||||
x[reveal_type({"baz": 1})] = reveal_type({"baz": 2}) # revealed: Bar
|
||||
|
||||
|
||||
# TODO: Support type context with union subscripting.
|
||||
def _(x: X | dict[Bar, Bar]):
|
||||
# error: [invalid-assignment]
|
||||
@@ -345,6 +384,7 @@ Diagnostics unrelated to the type-context are only reported once:
|
||||
def f[T](x: T) -> list[T]:
|
||||
return [x]
|
||||
|
||||
|
||||
def a(x: list[bool], y: list[bool]): ...
|
||||
def b(x: list[int], y: list[int]): ...
|
||||
def c(x: list[int], y: list[int]): ...
|
||||
@@ -385,12 +425,15 @@ def _(a: object, b: object, flag: bool):
|
||||
```py
|
||||
from typing import TypedDict
|
||||
|
||||
|
||||
class TD(TypedDict):
|
||||
y: int
|
||||
|
||||
|
||||
class X:
|
||||
td: TD
|
||||
|
||||
|
||||
def _(x: X, flag: bool):
|
||||
if flag:
|
||||
y = 1
|
||||
|
||||
@@ -109,6 +109,7 @@ def _(a: bool):
|
||||
```py
|
||||
import random
|
||||
|
||||
|
||||
def _(a: bool):
|
||||
def lhs_is_int(x: int):
|
||||
reveal_type(x | a) # revealed: int
|
||||
|
||||
@@ -11,8 +11,11 @@ python-version = "3.10"
|
||||
|
||||
```py
|
||||
class A: ...
|
||||
|
||||
|
||||
class B: ...
|
||||
|
||||
|
||||
reveal_type(A | B) # revealed: <types.UnionType special-form 'A | B'>
|
||||
```
|
||||
|
||||
@@ -25,8 +28,11 @@ python-version = "3.9"
|
||||
|
||||
```py
|
||||
class A: ...
|
||||
|
||||
|
||||
class B: ...
|
||||
|
||||
|
||||
# error: "Operator `|` is not supported between objects of type `<class 'A'>` and `<class 'B'>`"
|
||||
reveal_type(A | B) # revealed: Unknown
|
||||
```
|
||||
@@ -40,16 +46,23 @@ python-version = "3.12"
|
||||
|
||||
```py
|
||||
class A: ...
|
||||
|
||||
|
||||
class B: ...
|
||||
|
||||
|
||||
def _(sub_a: type[A], sub_b: type[B]):
|
||||
reveal_type(A | sub_b) # revealed: <types.UnionType special-form>
|
||||
reveal_type(sub_a | B) # revealed: <types.UnionType special-form>
|
||||
reveal_type(sub_a | sub_b) # revealed: <types.UnionType special-form>
|
||||
|
||||
|
||||
class C[T]: ...
|
||||
|
||||
|
||||
class D[T]: ...
|
||||
|
||||
|
||||
reveal_type(C | D) # revealed: <types.UnionType special-form 'C[Unknown] | D[Unknown]'>
|
||||
|
||||
reveal_type(C[int] | D[str]) # revealed: <types.UnionType special-form 'C[int] | D[str]'>
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
```py
|
||||
from typing import Literal
|
||||
|
||||
|
||||
class Yes:
|
||||
def __add__(self, other) -> Literal["+"]:
|
||||
return "+"
|
||||
@@ -45,9 +46,13 @@ class Yes:
|
||||
def __floordiv__(self, other) -> Literal["//"]:
|
||||
return "//"
|
||||
|
||||
|
||||
class Sub(Yes): ...
|
||||
|
||||
|
||||
class No: ...
|
||||
|
||||
|
||||
# Yes implements all of the dunder methods.
|
||||
reveal_type(Yes() + Yes()) # revealed: Literal["+"]
|
||||
reveal_type(Yes() - Yes()) # revealed: Literal["-"]
|
||||
@@ -140,6 +145,7 @@ reveal_type(No() // Yes()) # revealed: Unknown
|
||||
```py
|
||||
from typing import Literal
|
||||
|
||||
|
||||
class Yes:
|
||||
def __add__(self, other) -> Literal["+"]:
|
||||
return "+"
|
||||
@@ -180,6 +186,7 @@ class Yes:
|
||||
def __floordiv__(self, other) -> Literal["//"]:
|
||||
return "//"
|
||||
|
||||
|
||||
class Sub(Yes):
|
||||
def __radd__(self, other) -> Literal["r+"]:
|
||||
return "r+"
|
||||
@@ -220,6 +227,7 @@ class Sub(Yes):
|
||||
def __rfloordiv__(self, other) -> Literal["r//"]:
|
||||
return "r//"
|
||||
|
||||
|
||||
class No:
|
||||
def __radd__(self, other) -> Literal["r+"]:
|
||||
return "r+"
|
||||
@@ -260,6 +268,7 @@ class No:
|
||||
def __rfloordiv__(self, other) -> Literal["r//"]:
|
||||
return "r//"
|
||||
|
||||
|
||||
# Subclass reflected dunder methods take precedence over the superclass's regular dunders.
|
||||
reveal_type(Yes() + Sub()) # revealed: Literal["r+"]
|
||||
reveal_type(Yes() - Sub()) # revealed: Literal["r-"]
|
||||
@@ -302,13 +311,18 @@ class's type, i.e. `type`.)
|
||||
```py
|
||||
from typing import Literal
|
||||
|
||||
|
||||
class Yes:
|
||||
def __add__(self, other) -> Literal["+"]:
|
||||
return "+"
|
||||
|
||||
|
||||
class Sub(Yes): ...
|
||||
|
||||
|
||||
class No: ...
|
||||
|
||||
|
||||
# error: [unsupported-operator] "Operator `+` is not supported between two objects of type `<class 'Yes'>`"
|
||||
reveal_type(Yes + Yes) # revealed: Unknown
|
||||
# error: [unsupported-operator] "Operator `+` is not supported between two objects of type `<class 'Sub'>`"
|
||||
@@ -322,22 +336,30 @@ reveal_type(No + No) # revealed: Unknown
|
||||
```py
|
||||
from typing import Literal
|
||||
|
||||
|
||||
class Yes:
|
||||
def __add__(self, other) -> Literal["+"]:
|
||||
return "+"
|
||||
|
||||
|
||||
class Sub(Yes): ...
|
||||
|
||||
|
||||
class No: ...
|
||||
|
||||
|
||||
def yes() -> type[Yes]:
|
||||
return Yes
|
||||
|
||||
|
||||
def sub() -> type[Sub]:
|
||||
return Sub
|
||||
|
||||
|
||||
def no() -> type[No]:
|
||||
return No
|
||||
|
||||
|
||||
# error: [unsupported-operator] "Operator `+` is not supported between two objects of type `type[Yes]`"
|
||||
reveal_type(yes() + yes()) # revealed: Unknown
|
||||
# error: [unsupported-operator] "Operator `+` is not supported between two objects of type `type[Sub]`"
|
||||
@@ -352,6 +374,7 @@ reveal_type(no() + no()) # revealed: Unknown
|
||||
def f():
|
||||
pass
|
||||
|
||||
|
||||
# error: [unsupported-operator] "Operator `+` is not supported between two objects of type `def f() -> Unknown`"
|
||||
reveal_type(f + f) # revealed: Unknown
|
||||
# error: [unsupported-operator] "Operator `-` is not supported between two objects of type `def f() -> Unknown`"
|
||||
@@ -398,8 +421,10 @@ class A: ...
|
||||
```py
|
||||
import mod1
|
||||
|
||||
|
||||
class A: ...
|
||||
|
||||
|
||||
# error: [unsupported-operator] "Operator `+` is not supported between objects of type `mod2.A` and `mod1.A`"
|
||||
A() + mod1.A()
|
||||
```
|
||||
|
||||
@@ -53,8 +53,10 @@ class A:
|
||||
def __or__(self, other) -> "A":
|
||||
return self
|
||||
|
||||
|
||||
class B: ...
|
||||
|
||||
|
||||
reveal_type(A() + B()) # revealed: A
|
||||
reveal_type(A() - B()) # revealed: A
|
||||
reveal_type(A() * B()) # revealed: A
|
||||
@@ -115,8 +117,10 @@ class A:
|
||||
def __ror__(self, other) -> "A":
|
||||
return self
|
||||
|
||||
|
||||
class B: ...
|
||||
|
||||
|
||||
reveal_type(B() + A()) # revealed: A
|
||||
reveal_type(B() - A()) # revealed: A
|
||||
reveal_type(B() * A()) # revealed: A
|
||||
@@ -144,8 +148,10 @@ class A:
|
||||
def __rsub__(self, other) -> int:
|
||||
return 1
|
||||
|
||||
|
||||
class B: ...
|
||||
|
||||
|
||||
reveal_type(A() + B()) # revealed: int
|
||||
reveal_type(B() - A()) # revealed: int
|
||||
```
|
||||
@@ -160,12 +166,15 @@ class A:
|
||||
def __add__(self, other: "B") -> int:
|
||||
return 42
|
||||
|
||||
|
||||
class B:
|
||||
def __radd__(self, other: "A") -> str:
|
||||
return "foo"
|
||||
|
||||
|
||||
reveal_type(A() + B()) # revealed: int
|
||||
|
||||
|
||||
# Edge case: C is a subtype of C, *but* if the two sides are of *equal* types,
|
||||
# the lhs *still* takes precedence
|
||||
class C:
|
||||
@@ -175,6 +184,7 @@ class C:
|
||||
def __radd__(self, other: "C") -> str:
|
||||
return "foo"
|
||||
|
||||
|
||||
reveal_type(C() + C()) # revealed: int
|
||||
```
|
||||
|
||||
@@ -191,17 +201,22 @@ class A:
|
||||
def __radd__(self, other) -> str:
|
||||
return "foo"
|
||||
|
||||
|
||||
class MyString(str): ...
|
||||
|
||||
|
||||
class B(A):
|
||||
def __radd__(self, other) -> MyString:
|
||||
return MyString()
|
||||
|
||||
|
||||
reveal_type(A() + B()) # revealed: MyString
|
||||
|
||||
|
||||
# N.B. Still a subtype of `A`, even though `A` does not appear directly in the class's `__bases__`
|
||||
class C(B): ...
|
||||
|
||||
|
||||
reveal_type(A() + C()) # revealed: MyString
|
||||
```
|
||||
|
||||
@@ -218,8 +233,10 @@ class A:
|
||||
def __radd__(self, other) -> int:
|
||||
return 42
|
||||
|
||||
|
||||
class B(A): ...
|
||||
|
||||
|
||||
reveal_type(A() + B()) # revealed: str
|
||||
```
|
||||
|
||||
@@ -240,10 +257,12 @@ class A:
|
||||
def __sub__(self, other: "A") -> "A":
|
||||
return A()
|
||||
|
||||
|
||||
class B:
|
||||
def __rsub__(self, other: A) -> "B":
|
||||
return B()
|
||||
|
||||
|
||||
reveal_type(A() - B()) # revealed: B
|
||||
```
|
||||
|
||||
@@ -256,9 +275,11 @@ class A:
|
||||
def __call__(self, other) -> int:
|
||||
return 42
|
||||
|
||||
|
||||
class B:
|
||||
__add__ = A()
|
||||
|
||||
|
||||
reveal_type(B() + B()) # revealed: Unknown | int
|
||||
```
|
||||
|
||||
@@ -269,6 +290,7 @@ the callable is declared:
|
||||
class B2:
|
||||
__add__: A = A()
|
||||
|
||||
|
||||
reveal_type(B2() + B2()) # revealed: int
|
||||
```
|
||||
|
||||
@@ -287,6 +309,7 @@ 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
|
||||
@@ -306,6 +329,7 @@ class A:
|
||||
def __radd__(self, other) -> "A":
|
||||
return self
|
||||
|
||||
|
||||
reveal_type(A() + 1) # revealed: A
|
||||
reveal_type(1 + A()) # revealed: A
|
||||
|
||||
@@ -341,12 +365,15 @@ from does_not_exist import Foo # error: [unresolved-import]
|
||||
|
||||
reveal_type(Foo) # revealed: Unknown
|
||||
|
||||
|
||||
class X:
|
||||
def __add__(self, other: object) -> int:
|
||||
return 42
|
||||
|
||||
|
||||
class Y(Foo): ...
|
||||
|
||||
|
||||
# TODO: Should be `int | Unknown`; see above discussion.
|
||||
reveal_type(X() + Y()) # revealed: int
|
||||
```
|
||||
@@ -359,6 +386,7 @@ reveal_type(X() + Y()) # revealed: int
|
||||
class NotBoolable:
|
||||
__bool__: int = 3
|
||||
|
||||
|
||||
a = NotBoolable()
|
||||
|
||||
# error: [unsupported-bool-conversion]
|
||||
@@ -372,6 +400,7 @@ When operating on class objects, the corresponding dunder methods are looked up
|
||||
```py
|
||||
from __future__ import annotations
|
||||
|
||||
|
||||
class Meta(type):
|
||||
def __add__(self, other: Meta) -> int:
|
||||
return 1
|
||||
@@ -382,9 +411,13 @@ class Meta(type):
|
||||
def __getitem__(self, key: int) -> str:
|
||||
return "a"
|
||||
|
||||
|
||||
class A(metaclass=Meta): ...
|
||||
|
||||
|
||||
class B(metaclass=Meta): ...
|
||||
|
||||
|
||||
reveal_type(A + B) # revealed: int
|
||||
# error: [unsupported-operator] "Operator `-` is not supported between objects of type `<class 'A'>` and `<class 'B'>`"
|
||||
reveal_type(A - B) # revealed: Unknown
|
||||
@@ -408,10 +441,12 @@ The magic method must exist on the class, not just on the instance:
|
||||
def add_impl(self, other) -> int:
|
||||
return 1
|
||||
|
||||
|
||||
class A:
|
||||
def __init__(self):
|
||||
self.__add__ = add_impl
|
||||
|
||||
|
||||
# error: [unsupported-operator] "Operator `+` is not supported between two objects of type `A`"
|
||||
# revealed: Unknown
|
||||
reveal_type(A() + A())
|
||||
@@ -422,6 +457,7 @@ reveal_type(A() + A())
|
||||
```py
|
||||
class A: ...
|
||||
|
||||
|
||||
# error: [unsupported-operator]
|
||||
# revealed: Unknown
|
||||
reveal_type(A() + A())
|
||||
@@ -436,12 +472,15 @@ class A:
|
||||
def __add__(self, other) -> int:
|
||||
return 1
|
||||
|
||||
|
||||
class B:
|
||||
def __radd__(self, other) -> int:
|
||||
return 1
|
||||
|
||||
|
||||
class C: ...
|
||||
|
||||
|
||||
# error: [unsupported-operator]
|
||||
# revealed: Unknown
|
||||
reveal_type(C() + A())
|
||||
@@ -463,6 +502,7 @@ class Foo:
|
||||
def __radd__(self, other: "Foo") -> "Foo":
|
||||
return self
|
||||
|
||||
|
||||
# error: [unsupported-operator]
|
||||
# revealed: Unknown
|
||||
reveal_type(Foo() + Foo())
|
||||
|
||||
@@ -16,6 +16,7 @@ reveal_type(7 ^ 2) # revealed: Literal[5]
|
||||
# error: [unsupported-operator] "Operator `+` is not supported between objects of type `Literal[2]` and `Literal["f"]`"
|
||||
reveal_type(2 + "f") # revealed: Unknown
|
||||
|
||||
|
||||
def lhs(x: int):
|
||||
reveal_type(x + 1) # revealed: int
|
||||
reveal_type(x - 4) # revealed: int
|
||||
@@ -24,6 +25,7 @@ def lhs(x: int):
|
||||
reveal_type(x / 3) # revealed: int | float
|
||||
reveal_type(x % 3) # revealed: int
|
||||
|
||||
|
||||
def rhs(x: int):
|
||||
reveal_type(2 + x) # revealed: int
|
||||
reveal_type(3 - x) # revealed: int
|
||||
@@ -32,6 +34,7 @@ def rhs(x: int):
|
||||
reveal_type(-3 / x) # revealed: int | float
|
||||
reveal_type(5 % x) # revealed: int
|
||||
|
||||
|
||||
def both(x: int):
|
||||
reveal_type(x + x) # revealed: int
|
||||
reveal_type(x - x) # revealed: int
|
||||
@@ -52,6 +55,7 @@ reveal_type(2**2) # revealed: Literal[4]
|
||||
reveal_type(1 ** (largest_u32 + 1)) # revealed: int
|
||||
reveal_type(2**largest_u32) # revealed: int
|
||||
|
||||
|
||||
def variable(x: int):
|
||||
reveal_type(x**2) # revealed: int
|
||||
reveal_type(2**x) # revealed: Any
|
||||
@@ -134,8 +138,10 @@ bool(1) / False
|
||||
# error: "Cannot divide object of type `float` by zero"
|
||||
reveal_type(1.0 / 0) # revealed: int | float
|
||||
|
||||
|
||||
class MyInt(int): ...
|
||||
|
||||
|
||||
# No error for a subclass of int
|
||||
reveal_type(MyInt(3) / 0) # revealed: int | float
|
||||
```
|
||||
|
||||
@@ -8,6 +8,7 @@ reveal_type(() + (1, 2)) # revealed: tuple[Literal[1, 2], ...]
|
||||
reveal_type((1, 2) + ()) # revealed: tuple[Literal[1, 2], ...]
|
||||
reveal_type(() + ()) # revealed: tuple[()]
|
||||
|
||||
|
||||
def _(x: tuple[int, str], y: tuple[None, tuple[int]]):
|
||||
reveal_type(x + y) # revealed: tuple[int | str | None | tuple[int], ...]
|
||||
reveal_type(y + x) # revealed: tuple[None | tuple[int] | int | str, ...]
|
||||
@@ -38,6 +39,7 @@ ThreeFour = tuple[Literal[3], Literal[4]]
|
||||
IntTuple = tuple[int, ...]
|
||||
StrTuple = tuple[str, ...]
|
||||
|
||||
|
||||
def _(one_two: OneTwo, x: IntTuple, y: StrTuple, three_four: ThreeFour):
|
||||
reveal_type(x + x) # revealed: tuple[int, ...]
|
||||
reveal_type(x + y) # revealed: tuple[int | str, ...]
|
||||
|
||||
@@ -28,6 +28,7 @@ the possible outcomes:
|
||||
```py
|
||||
from typing import Literal
|
||||
|
||||
|
||||
def f3(two_or_three: Literal[2, 3], a_or_b: Literal["a", "b"]):
|
||||
reveal_type(two_or_three + two_or_three) # revealed: Literal[4, 5, 6]
|
||||
reveal_type(two_or_three**two_or_three) # revealed: Literal[4, 8, 9, 27]
|
||||
|
||||
@@ -39,14 +39,17 @@ If a symbol has a declared type (`int`), we use that even if there is a more pre
|
||||
```py
|
||||
from typing import Any
|
||||
|
||||
|
||||
def any() -> Any: ...
|
||||
|
||||
|
||||
class Public:
|
||||
a: int = 1
|
||||
b: str = 2 # error: [invalid-assignment]
|
||||
c: Any = 3
|
||||
d: int = any()
|
||||
|
||||
|
||||
reveal_type(Public.a) # revealed: int
|
||||
reveal_type(Public.b) # revealed: str
|
||||
reveal_type(Public.c) # revealed: Any
|
||||
@@ -60,10 +63,12 @@ If a symbol is declared and *possibly* unbound, we trust the declared type witho
|
||||
```py
|
||||
from typing import Any
|
||||
|
||||
|
||||
def any() -> Any: ...
|
||||
def flag() -> bool:
|
||||
return True
|
||||
|
||||
|
||||
class Public:
|
||||
a: int
|
||||
b: str
|
||||
@@ -76,6 +81,7 @@ class Public:
|
||||
c = 3
|
||||
d = any()
|
||||
|
||||
|
||||
reveal_type(Public.a) # revealed: int
|
||||
reveal_type(Public.b) # revealed: str
|
||||
reveal_type(Public.c) # revealed: Any
|
||||
@@ -90,10 +96,12 @@ is available somehow and simply use the declared type.
|
||||
```py
|
||||
from typing import Any
|
||||
|
||||
|
||||
class Public:
|
||||
a: int
|
||||
b: Any
|
||||
|
||||
|
||||
reveal_type(Public.a) # revealed: int
|
||||
reveal_type(Public.b) # revealed: Any
|
||||
```
|
||||
@@ -108,10 +116,12 @@ inferred types:
|
||||
```py
|
||||
from typing import Any
|
||||
|
||||
|
||||
def any() -> Any: ...
|
||||
def flag() -> bool:
|
||||
return True
|
||||
|
||||
|
||||
class Public:
|
||||
a = 1
|
||||
b = 2
|
||||
@@ -123,6 +133,7 @@ class Public:
|
||||
c: str # error: [invalid-declaration]
|
||||
d: int
|
||||
|
||||
|
||||
reveal_type(Public.a) # revealed: int
|
||||
reveal_type(Public.b) # revealed: Literal[2] | Any
|
||||
reveal_type(Public.c) # revealed: Literal[3] | Unknown
|
||||
@@ -143,9 +154,11 @@ error for both `a` and `b`:
|
||||
```py
|
||||
from typing import Any
|
||||
|
||||
|
||||
def flag() -> bool:
|
||||
return True
|
||||
|
||||
|
||||
class Public:
|
||||
if flag():
|
||||
a: Any = 1
|
||||
@@ -153,6 +166,7 @@ class Public:
|
||||
else:
|
||||
b: str
|
||||
|
||||
|
||||
# error: [possibly-missing-attribute]
|
||||
reveal_type(Public.a) # revealed: Literal[1] | Any
|
||||
# error: [possibly-missing-attribute]
|
||||
@@ -173,10 +187,12 @@ seems inconsistent when compared to the case just above.
|
||||
def flag() -> bool:
|
||||
return True
|
||||
|
||||
|
||||
class Public:
|
||||
if flag():
|
||||
a: int
|
||||
|
||||
|
||||
# TODO: this should raise an error. Once we fix this, update the section description and the table
|
||||
# on top of this document.
|
||||
reveal_type(Public.a) # revealed: int
|
||||
@@ -202,6 +218,7 @@ class Public:
|
||||
# Implicitly declared with `Unknown`, due to the usage of an unknown name in the annotation:
|
||||
b: SomeUnknownName = 1 # error: [unresolved-reference]
|
||||
|
||||
|
||||
reveal_type(Public.a) # revealed: Unknown | Literal[1]
|
||||
reveal_type(Public.b) # revealed: Unknown
|
||||
|
||||
@@ -218,11 +235,13 @@ inconsistent when compared to the "possibly-undeclared-and-possibly-unbound" cas
|
||||
def flag() -> bool:
|
||||
return True
|
||||
|
||||
|
||||
class Public:
|
||||
if flag:
|
||||
a = 1
|
||||
b: SomeUnknownName = 1 # error: [unresolved-reference]
|
||||
|
||||
|
||||
# TODO: these should raise an error. Once we fix this, update the section description and the table
|
||||
# on top of this document.
|
||||
reveal_type(Public.a) # revealed: Unknown | Literal[1]
|
||||
@@ -241,6 +260,7 @@ class Public:
|
||||
if False:
|
||||
a: int = 1
|
||||
|
||||
|
||||
# error: [unresolved-attribute]
|
||||
reveal_type(Public.a) # revealed: Unknown
|
||||
|
||||
|
||||
@@ -3,9 +3,11 @@
|
||||
```py
|
||||
from typing import Callable
|
||||
|
||||
|
||||
def _(c: Callable[[], int]):
|
||||
reveal_type(c()) # revealed: int
|
||||
|
||||
|
||||
def _(c: Callable[[int, str], int]):
|
||||
reveal_type(c(1, "a")) # revealed: int
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
class NotBool:
|
||||
__bool__ = None
|
||||
|
||||
|
||||
# error: [too-many-positional-arguments] "Too many positional arguments to class `bool`: expected 1, got 2"
|
||||
bool(1, 2)
|
||||
|
||||
@@ -68,10 +69,12 @@ from enum import Enum
|
||||
from types import FunctionType
|
||||
from typing import TypeVar
|
||||
|
||||
|
||||
class Answer(Enum):
|
||||
NO = 0
|
||||
YES = 1
|
||||
|
||||
|
||||
reveal_type(isinstance(True, bool)) # revealed: Literal[True]
|
||||
reveal_type(isinstance(True, int)) # revealed: Literal[True]
|
||||
reveal_type(isinstance(True, object)) # revealed: Literal[True]
|
||||
@@ -82,17 +85,27 @@ reveal_type(isinstance(Answer.NO, Answer)) # revealed: Literal[True]
|
||||
|
||||
reveal_type(isinstance((1, 2), tuple)) # revealed: Literal[True]
|
||||
|
||||
|
||||
def f(): ...
|
||||
|
||||
|
||||
reveal_type(isinstance(f, FunctionType)) # revealed: Literal[True]
|
||||
|
||||
reveal_type(isinstance("", int)) # revealed: bool
|
||||
|
||||
|
||||
class A: ...
|
||||
|
||||
|
||||
class SubclassOfA(A): ...
|
||||
|
||||
|
||||
class OtherSubclassOfA(A): ...
|
||||
|
||||
|
||||
class B: ...
|
||||
|
||||
|
||||
reveal_type(isinstance(A, type)) # revealed: Literal[True]
|
||||
|
||||
a = A()
|
||||
@@ -106,6 +119,7 @@ s = SubclassOfA()
|
||||
reveal_type(isinstance(s, SubclassOfA)) # revealed: Literal[True]
|
||||
reveal_type(isinstance(s, A)) # revealed: Literal[True]
|
||||
|
||||
|
||||
def _(x: A | B, y: list[int]):
|
||||
reveal_type(isinstance(y, list)) # revealed: Literal[True]
|
||||
reveal_type(isinstance(x, A)) # revealed: bool
|
||||
@@ -116,10 +130,12 @@ def _(x: A | B, y: list[int]):
|
||||
reveal_type(x) # revealed: B & ~A
|
||||
reveal_type(isinstance(x, B)) # revealed: Literal[True]
|
||||
|
||||
|
||||
T = TypeVar("T")
|
||||
T_bound_A = TypeVar("T_bound_A", bound=A)
|
||||
T_constrained = TypeVar("T_constrained", SubclassOfA, OtherSubclassOfA)
|
||||
|
||||
|
||||
def _(
|
||||
x: T,
|
||||
x_bound_a: T_bound_A,
|
||||
|
||||
@@ -10,11 +10,14 @@ class Multiplier:
|
||||
def __call__(self, number: int) -> int:
|
||||
return number * self.factor
|
||||
|
||||
|
||||
a = Multiplier(2)(3)
|
||||
reveal_type(a) # revealed: int
|
||||
|
||||
|
||||
class Unit: ...
|
||||
|
||||
|
||||
b = Unit()(3.0) # error: "Object of type `Unit` is not callable"
|
||||
reveal_type(b) # revealed: Unknown
|
||||
```
|
||||
@@ -25,6 +28,7 @@ reveal_type(b) # revealed: Unknown
|
||||
def _(flag: bool):
|
||||
class PossiblyNotCallable:
|
||||
if flag:
|
||||
|
||||
def __call__(self) -> int:
|
||||
return 1
|
||||
|
||||
@@ -38,6 +42,7 @@ def _(flag: bool):
|
||||
```py
|
||||
def _(flag: bool):
|
||||
if flag:
|
||||
|
||||
class PossiblyUnbound:
|
||||
def __call__(self) -> int:
|
||||
return 1
|
||||
@@ -53,6 +58,7 @@ def _(flag: bool):
|
||||
class NonCallable:
|
||||
__call__ = 1
|
||||
|
||||
|
||||
a = NonCallable()
|
||||
# error: [call-non-callable] "Object of type `Literal[1]` is not callable"
|
||||
reveal_type(a()) # revealed: Unknown
|
||||
@@ -66,6 +72,7 @@ def _(flag: bool):
|
||||
if flag:
|
||||
__call__ = 1
|
||||
else:
|
||||
|
||||
def __call__(self) -> int:
|
||||
return 1
|
||||
|
||||
@@ -83,6 +90,7 @@ class C:
|
||||
def __call__(self, x: int) -> int:
|
||||
return 1
|
||||
|
||||
|
||||
c = C()
|
||||
|
||||
# error: 15 [invalid-argument-type] "Argument to bound method `__call__` is incorrect: Expected `int`, found `Literal["foo"]`"
|
||||
@@ -97,6 +105,7 @@ class C:
|
||||
def __call__(self: int) -> int:
|
||||
return 1
|
||||
|
||||
|
||||
c = C()
|
||||
|
||||
# error: 13 [invalid-argument-type] "Argument to bound method `__call__` is incorrect: Expected `int`, found `C`"
|
||||
@@ -111,6 +120,7 @@ reveal_type(c()) # revealed: int
|
||||
def outer(cond1: bool):
|
||||
class Test:
|
||||
if cond1:
|
||||
|
||||
def __call__(self): ...
|
||||
|
||||
class Other:
|
||||
|
||||
@@ -16,10 +16,12 @@ the first argument:
|
||||
from ty_extensions import CallableTypeOf
|
||||
from typing import Callable
|
||||
|
||||
|
||||
class C1:
|
||||
def method(self: C1, x: int) -> str:
|
||||
return str(x)
|
||||
|
||||
|
||||
def _(
|
||||
accessed_on_class: CallableTypeOf[C1.method],
|
||||
accessed_on_instance: CallableTypeOf[C1().method],
|
||||
@@ -37,9 +39,11 @@ class NonDescriptorCallable2:
|
||||
def __call__(self, c2: C2, x: int) -> str:
|
||||
return str(x)
|
||||
|
||||
|
||||
class C2:
|
||||
non_descriptor_callable: NonDescriptorCallable2 = NonDescriptorCallable2()
|
||||
|
||||
|
||||
def _(
|
||||
accessed_on_class: CallableTypeOf[C2.non_descriptor_callable],
|
||||
accessed_on_instance: CallableTypeOf[C2().non_descriptor_callable],
|
||||
@@ -55,9 +59,11 @@ class NonDescriptorCallable3:
|
||||
def __call__(self, c3: C3, x: int) -> str:
|
||||
return str(x)
|
||||
|
||||
|
||||
class C3:
|
||||
def method(self: C3, x: int) -> str:
|
||||
return str(x)
|
||||
|
||||
non_descriptor_callable: NonDescriptorCallable3 = NonDescriptorCallable3()
|
||||
|
||||
callable_m: Callable[[C3, int], str] = method
|
||||
@@ -113,9 +119,11 @@ intention that it shouldn't influence the method's descriptor behavior. For exam
|
||||
```py
|
||||
from typing import Callable
|
||||
|
||||
|
||||
def memoize[**P, R](f: Callable[P, R]) -> Callable[P, R]:
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class C1:
|
||||
def method(self, x: int) -> str:
|
||||
return str(x)
|
||||
@@ -124,6 +132,7 @@ class C1:
|
||||
def method_decorated(self, x: int) -> str:
|
||||
return str(x)
|
||||
|
||||
|
||||
C1().method(1)
|
||||
|
||||
C1().method_decorated(1)
|
||||
@@ -135,11 +144,13 @@ This also works with an argumentless `Callable` annotation:
|
||||
def memoize2(f: Callable) -> Callable:
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class C2:
|
||||
@memoize2
|
||||
def method_decorated(self, x: int) -> str:
|
||||
return str(x)
|
||||
|
||||
|
||||
C2().method_decorated(1)
|
||||
```
|
||||
|
||||
@@ -148,14 +159,17 @@ And with unions of `Callable` types:
|
||||
```py
|
||||
from typing import Callable
|
||||
|
||||
|
||||
def expand(f: Callable[[C3, int], int]) -> Callable[[C3, int], int] | Callable[[C3, int], str]:
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class C3:
|
||||
@expand
|
||||
def method_decorated(self, x: int) -> int:
|
||||
return x
|
||||
|
||||
|
||||
reveal_type(C3().method_decorated(1)) # revealed: int | str
|
||||
```
|
||||
|
||||
@@ -167,11 +181,14 @@ but here we emit errors:
|
||||
def memoize3(f: Callable[[C4, int], str]) -> Callable[[C4, int], str]:
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class C4:
|
||||
def method(self, x: int) -> str:
|
||||
return str(x)
|
||||
|
||||
method_decorated = memoize3(method)
|
||||
|
||||
|
||||
# error: [missing-argument]
|
||||
# error: [invalid-argument-type]
|
||||
C4().method_decorated(1)
|
||||
@@ -194,12 +211,15 @@ class SquareCalculator:
|
||||
def __call__(self, x: float) -> int:
|
||||
return self.post_process(x * x)
|
||||
|
||||
|
||||
def square_then(c: Callable[[float], int]) -> Callable[[float], int]:
|
||||
return SquareCalculator(c)
|
||||
|
||||
|
||||
class Calculator:
|
||||
square_then_round = square_then(round)
|
||||
|
||||
|
||||
reveal_type(Calculator().square_then_round(3.14)) # revealed: Unknown | int
|
||||
```
|
||||
|
||||
@@ -212,12 +232,15 @@ example. We generally treat dunder attributes as bound-method descriptors since
|
||||
```py
|
||||
from typing import Callable
|
||||
|
||||
|
||||
def pow_impl(tensor: Tensor, exponent: int) -> Tensor:
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class Tensor:
|
||||
__pow__: Callable[[Tensor, int], Tensor] = pow_impl
|
||||
|
||||
|
||||
Tensor() ** 2
|
||||
```
|
||||
|
||||
@@ -229,9 +252,11 @@ treat it as a bound-method descriptor:
|
||||
def make_comparison_operator(name: str) -> Callable[[Matrix, Matrix], bool]:
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class Matrix:
|
||||
__lt__ = make_comparison_operator("lt")
|
||||
|
||||
|
||||
Matrix() < Matrix()
|
||||
```
|
||||
|
||||
@@ -245,14 +270,17 @@ function-like:
|
||||
```py
|
||||
from typing import Callable
|
||||
|
||||
|
||||
def my_lossy_decorator(fn: Callable[..., int]) -> Callable[..., int]:
|
||||
return fn
|
||||
|
||||
|
||||
class MyClass:
|
||||
@my_lossy_decorator
|
||||
def method(self) -> int:
|
||||
return 42
|
||||
|
||||
|
||||
reveal_type(MyClass().method) # revealed: (...) -> int
|
||||
reveal_type(MyClass().method.__name__) # revealed: str
|
||||
```
|
||||
@@ -267,9 +295,11 @@ behavior.
|
||||
```py
|
||||
from typing import Callable
|
||||
|
||||
|
||||
def callable_identity[**P, R](func: Callable[P, R]) -> Callable[P, R]:
|
||||
return func
|
||||
|
||||
|
||||
class C:
|
||||
@callable_identity
|
||||
@classmethod
|
||||
@@ -281,6 +311,7 @@ class C:
|
||||
def f2(cls, x: int) -> str:
|
||||
return "a"
|
||||
|
||||
|
||||
# error: [too-many-positional-arguments]
|
||||
# error: [invalid-argument-type]
|
||||
C.f1(C, 1)
|
||||
@@ -303,18 +334,22 @@ The callable type of a type object is not function-like.
|
||||
from typing import ClassVar
|
||||
from ty_extensions import CallableTypeOf
|
||||
|
||||
|
||||
class WithNew:
|
||||
def __new__(self, x: int) -> WithNew:
|
||||
return super().__new__(WithNew)
|
||||
|
||||
|
||||
class WithInit:
|
||||
def __init__(self, x: int) -> None:
|
||||
pass
|
||||
|
||||
|
||||
class C:
|
||||
with_new: ClassVar[CallableTypeOf[WithNew]]
|
||||
with_init: ClassVar[CallableTypeOf[WithInit]]
|
||||
|
||||
|
||||
C.with_new(1)
|
||||
C().with_new(1)
|
||||
C.with_init(1)
|
||||
|
||||
@@ -45,6 +45,7 @@ reveal_type(object(1)) # revealed: object
|
||||
```py
|
||||
class Foo: ...
|
||||
|
||||
|
||||
reveal_type(Foo()) # revealed: Foo
|
||||
|
||||
# error: [too-many-positional-arguments] "Too many positional arguments to bound method `__init__`: expected 1, got 2"
|
||||
@@ -58,6 +59,7 @@ class Foo:
|
||||
def __new__(cls, x: int) -> "Foo":
|
||||
return object.__new__(cls)
|
||||
|
||||
|
||||
reveal_type(Foo(1)) # revealed: Foo
|
||||
|
||||
# error: [missing-argument] "No argument provided for required parameter `x` of function `__new__`"
|
||||
@@ -74,12 +76,15 @@ constructor from it.
|
||||
```py
|
||||
from typing_extensions import Self
|
||||
|
||||
|
||||
class Base:
|
||||
def __new__(cls, x: int) -> Self:
|
||||
return cls()
|
||||
|
||||
|
||||
class Foo(Base): ...
|
||||
|
||||
|
||||
reveal_type(Foo(1)) # revealed: Foo
|
||||
|
||||
# error: [missing-argument] "No argument provided for required parameter `x` of function `__new__`"
|
||||
@@ -94,8 +99,11 @@ reveal_type(Foo(1, 2)) # revealed: Foo
|
||||
def _(flag: bool) -> None:
|
||||
class Foo:
|
||||
if flag:
|
||||
|
||||
def __new__(cls, x: int): ...
|
||||
|
||||
else:
|
||||
|
||||
def __new__(cls, x: int, y: int = 1): ...
|
||||
|
||||
reveal_type(Foo(1)) # revealed: Foo
|
||||
@@ -118,13 +126,16 @@ class SomeCallable:
|
||||
obj.x = x
|
||||
return obj
|
||||
|
||||
|
||||
class Descriptor:
|
||||
def __get__(self, instance, owner) -> SomeCallable:
|
||||
return SomeCallable()
|
||||
|
||||
|
||||
class Foo:
|
||||
__new__: Descriptor = Descriptor()
|
||||
|
||||
|
||||
reveal_type(Foo(1)) # revealed: Foo
|
||||
# error: [missing-argument] "No argument provided for required parameter `x` of bound method `__call__`"
|
||||
reveal_type(Foo()) # revealed: Foo
|
||||
@@ -139,9 +150,11 @@ class Callable:
|
||||
def __call__(self, cls, x: int) -> "Foo":
|
||||
return object.__new__(cls)
|
||||
|
||||
|
||||
class Foo:
|
||||
__new__ = Callable()
|
||||
|
||||
|
||||
reveal_type(Foo(1)) # revealed: Foo
|
||||
# error: [missing-argument] "No argument provided for required parameter `x` of bound method `__call__`"
|
||||
reveal_type(Foo()) # revealed: Foo
|
||||
@@ -155,6 +168,7 @@ reveal_type(Foo()) # revealed: Foo
|
||||
def _(flag: bool) -> None:
|
||||
class Foo:
|
||||
if flag:
|
||||
|
||||
def __new__(cls):
|
||||
return object.__new__(cls)
|
||||
|
||||
@@ -172,6 +186,7 @@ def _(flag: bool) -> None:
|
||||
def _(flag: bool) -> None:
|
||||
class Callable:
|
||||
if flag:
|
||||
|
||||
def __call__(self, cls, x: int) -> "Foo":
|
||||
return object.__new__(cls)
|
||||
|
||||
@@ -194,6 +209,7 @@ If the class has an `__init__` method, we can infer the signature of the constru
|
||||
class Foo:
|
||||
def __init__(self, x: int): ...
|
||||
|
||||
|
||||
reveal_type(Foo(1)) # revealed: Foo
|
||||
|
||||
# error: [missing-argument] "No argument provided for required parameter `x` of bound method `__init__`"
|
||||
@@ -211,8 +227,10 @@ constructor from it.
|
||||
class Base:
|
||||
def __init__(self, x: int): ...
|
||||
|
||||
|
||||
class Foo(Base): ...
|
||||
|
||||
|
||||
reveal_type(Foo(1)) # revealed: Foo
|
||||
|
||||
# error: [missing-argument] "No argument provided for required parameter `x` of bound method `__init__`"
|
||||
@@ -227,8 +245,11 @@ reveal_type(Foo(1, 2)) # revealed: Foo
|
||||
def _(flag: bool) -> None:
|
||||
class Foo:
|
||||
if flag:
|
||||
|
||||
def __init__(self, x: int): ...
|
||||
|
||||
else:
|
||||
|
||||
def __init__(self, x: int, y: int = 1): ...
|
||||
|
||||
reveal_type(Foo(1)) # revealed: Foo
|
||||
@@ -253,13 +274,16 @@ class SomeCallable:
|
||||
def __call__(self, x: int) -> str:
|
||||
return "a"
|
||||
|
||||
|
||||
class Descriptor:
|
||||
def __get__(self, instance, owner) -> SomeCallable:
|
||||
return SomeCallable()
|
||||
|
||||
|
||||
class Foo:
|
||||
__init__: Descriptor = Descriptor()
|
||||
|
||||
|
||||
reveal_type(Foo(1)) # revealed: Foo
|
||||
# error: [missing-argument] "No argument provided for required parameter `x` of bound method `__call__`"
|
||||
reveal_type(Foo()) # revealed: Foo
|
||||
@@ -274,9 +298,11 @@ class Callable:
|
||||
def __call__(self, x: int) -> None:
|
||||
pass
|
||||
|
||||
|
||||
class Foo:
|
||||
__init__ = Callable()
|
||||
|
||||
|
||||
reveal_type(Foo(1)) # revealed: Foo
|
||||
# error: [missing-argument] "No argument provided for required parameter `x` of bound method `__call__`"
|
||||
reveal_type(Foo()) # revealed: Foo
|
||||
@@ -288,6 +314,7 @@ reveal_type(Foo()) # revealed: Foo
|
||||
def _(flag: bool) -> None:
|
||||
class Callable:
|
||||
if flag:
|
||||
|
||||
def __call__(self, x: int) -> None:
|
||||
pass
|
||||
|
||||
@@ -320,6 +347,7 @@ class Foo:
|
||||
|
||||
def __init__(self, x: int): ...
|
||||
|
||||
|
||||
# error: [missing-argument] "No argument provided for required parameter `x` of function `__new__`"
|
||||
# error: [missing-argument] "No argument provided for required parameter `x` of bound method `__init__`"
|
||||
reveal_type(Foo()) # revealed: Foo
|
||||
@@ -340,6 +368,7 @@ class Foo:
|
||||
def __init__(self, x: int) -> None:
|
||||
self.x = x
|
||||
|
||||
|
||||
# error: [missing-argument] "No argument provided for required parameter `x` of bound method `__init__`"
|
||||
reveal_type(Foo()) # revealed: Foo
|
||||
reveal_type(Foo(1)) # revealed: Foo
|
||||
@@ -353,6 +382,7 @@ reveal_type(Foo(1, 2)) # revealed: Foo
|
||||
```py
|
||||
import abc
|
||||
|
||||
|
||||
class Foo:
|
||||
def __new__(cls) -> "Foo":
|
||||
return object.__new__(cls)
|
||||
@@ -360,12 +390,14 @@ class Foo:
|
||||
def __init__(self, x):
|
||||
self.x = 42
|
||||
|
||||
|
||||
# error: [missing-argument] "No argument provided for required parameter `x` of bound method `__init__`"
|
||||
reveal_type(Foo()) # revealed: Foo
|
||||
|
||||
# error: [too-many-positional-arguments] "Too many positional arguments to function `__new__`: expected 1, got 2"
|
||||
reveal_type(Foo(42)) # revealed: Foo
|
||||
|
||||
|
||||
class Foo2:
|
||||
def __new__(cls, x) -> "Foo2":
|
||||
return object.__new__(cls)
|
||||
@@ -373,12 +405,14 @@ class Foo2:
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
|
||||
# error: [missing-argument] "No argument provided for required parameter `x` of function `__new__`"
|
||||
reveal_type(Foo2()) # revealed: Foo2
|
||||
|
||||
# error: [too-many-positional-arguments] "Too many positional arguments to bound method `__init__`: expected 1, got 2"
|
||||
reveal_type(Foo2(42)) # revealed: Foo2
|
||||
|
||||
|
||||
class Foo3(metaclass=abc.ABCMeta):
|
||||
def __new__(cls) -> "Foo3":
|
||||
return object.__new__(cls)
|
||||
@@ -386,12 +420,14 @@ class Foo3(metaclass=abc.ABCMeta):
|
||||
def __init__(self, x):
|
||||
self.x = 42
|
||||
|
||||
|
||||
# error: [missing-argument] "No argument provided for required parameter `x` of bound method `__init__`"
|
||||
reveal_type(Foo3()) # revealed: Foo3
|
||||
|
||||
# error: [too-many-positional-arguments] "Too many positional arguments to function `__new__`: expected 1, got 2"
|
||||
reveal_type(Foo3(42)) # revealed: Foo3
|
||||
|
||||
|
||||
class Foo4(metaclass=abc.ABCMeta):
|
||||
def __new__(cls, x) -> "Foo4":
|
||||
return object.__new__(cls)
|
||||
@@ -399,6 +435,7 @@ class Foo4(metaclass=abc.ABCMeta):
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
|
||||
# error: [missing-argument] "No argument provided for required parameter `x` of function `__new__`"
|
||||
reveal_type(Foo4()) # revealed: Foo4
|
||||
|
||||
@@ -415,6 +452,7 @@ meta-type, never on the type itself).
|
||||
```py
|
||||
from typing_extensions import Literal
|
||||
|
||||
|
||||
class Meta(type):
|
||||
def __new__(mcls, name, bases, namespace, /, **kwargs):
|
||||
return super().__new__(mcls, name, bases, namespace)
|
||||
@@ -422,8 +460,10 @@ class Meta(type):
|
||||
def __lt__(cls, other) -> Literal[True]:
|
||||
return True
|
||||
|
||||
|
||||
class C(metaclass=Meta): ...
|
||||
|
||||
|
||||
# No error is raised here, since we don't implicitly call `Meta.__new__`
|
||||
reveal_type(C()) # revealed: C
|
||||
|
||||
|
||||
@@ -16,10 +16,12 @@ as the `instance` argument to `__get__`. A desugared version of `obj[key]` is ro
|
||||
```py
|
||||
from typing import Any
|
||||
|
||||
|
||||
def find_name_in_mro(typ: type, name: str) -> Any:
|
||||
# See implementation in https://docs.python.org/3/howto/descriptor.html#invocation-from-an-instance
|
||||
pass
|
||||
|
||||
|
||||
def getitem_desugared(obj: object, key: object) -> object:
|
||||
getitem_callable = find_name_in_mro(type(obj), "__getitem__")
|
||||
if hasattr(getitem_callable, "__get__"):
|
||||
@@ -40,9 +42,11 @@ class Meta(type):
|
||||
def __getitem__(cls, key: int) -> str:
|
||||
return str(key)
|
||||
|
||||
|
||||
class DunderOnMetaclass(metaclass=Meta):
|
||||
pass
|
||||
|
||||
|
||||
reveal_type(DunderOnMetaclass[0]) # revealed: str
|
||||
```
|
||||
|
||||
@@ -53,6 +57,7 @@ class ClassWithNormalDunder:
|
||||
def __getitem__(self, key: int) -> str:
|
||||
return str(key)
|
||||
|
||||
|
||||
# error: [not-subscriptable]
|
||||
ClassWithNormalDunder[0]
|
||||
```
|
||||
@@ -68,6 +73,7 @@ class ClassWithNormalDunder:
|
||||
def __getitem__(self, key: int) -> str:
|
||||
return str(key)
|
||||
|
||||
|
||||
class_with_normal_dunder = ClassWithNormalDunder()
|
||||
|
||||
reveal_type(class_with_normal_dunder[0]) # revealed: str
|
||||
@@ -79,10 +85,12 @@ Which can be demonstrated by trying to attach a dunder method to an instance, wh
|
||||
def external_getitem(instance, key: int) -> str:
|
||||
return str(key)
|
||||
|
||||
|
||||
class ThisFails:
|
||||
def __init__(self):
|
||||
self.__getitem__ = external_getitem
|
||||
|
||||
|
||||
this_fails = ThisFails()
|
||||
|
||||
# error: [not-subscriptable] "Cannot subscript object of type `ThisFails` with no `__getitem__` method"
|
||||
@@ -101,9 +109,11 @@ The instance-level method is also not called when the class-level method is pres
|
||||
def external_getitem1(instance, key) -> str:
|
||||
return "a"
|
||||
|
||||
|
||||
def external_getitem2(key) -> int:
|
||||
return 1
|
||||
|
||||
|
||||
def _(flag: bool):
|
||||
class ThisFails:
|
||||
if flag:
|
||||
@@ -129,9 +139,11 @@ Class-level annotations with no value assigned are considered to be accessible o
|
||||
```py
|
||||
from typing import Callable
|
||||
|
||||
|
||||
class C:
|
||||
__call__: Callable[..., None]
|
||||
|
||||
|
||||
C()()
|
||||
|
||||
_: Callable[..., None] = C()
|
||||
@@ -142,10 +154,12 @@ And of course the same is true if we have only an implicit assignment inside a m
|
||||
```py
|
||||
from typing import Callable
|
||||
|
||||
|
||||
class C:
|
||||
def __init__(self):
|
||||
self.__call__ = lambda *a, **kw: None
|
||||
|
||||
|
||||
# error: [call-non-callable]
|
||||
C()()
|
||||
|
||||
@@ -162,9 +176,11 @@ class SomeCallable:
|
||||
def __call__(self, key: int) -> str:
|
||||
return str(key)
|
||||
|
||||
|
||||
class ClassWithNonMethodDunder:
|
||||
__getitem__: SomeCallable = SomeCallable()
|
||||
|
||||
|
||||
class_with_callable_dunder = ClassWithNonMethodDunder()
|
||||
|
||||
reveal_type(class_with_callable_dunder[0]) # revealed: str
|
||||
@@ -178,17 +194,21 @@ that the `instance` argument is on object of type `ClassWithDescriptorDunder`:
|
||||
```py
|
||||
from __future__ import annotations
|
||||
|
||||
|
||||
class SomeCallable:
|
||||
def __call__(self, key: int) -> str:
|
||||
return str(key)
|
||||
|
||||
|
||||
class Descriptor:
|
||||
def __get__(self, instance: ClassWithDescriptorDunder, owner: type[ClassWithDescriptorDunder]) -> SomeCallable:
|
||||
return SomeCallable()
|
||||
|
||||
|
||||
class ClassWithDescriptorDunder:
|
||||
__getitem__: Descriptor = Descriptor()
|
||||
|
||||
|
||||
class_with_descriptor_dunder = ClassWithDescriptorDunder()
|
||||
|
||||
reveal_type(class_with_descriptor_dunder[0]) # revealed: str
|
||||
@@ -208,6 +228,7 @@ class C:
|
||||
# error: [invalid-assignment]
|
||||
self.__getitem__ = None
|
||||
|
||||
|
||||
# This is still fine, and simply calls the `__getitem__` method on the class
|
||||
reveal_type(C()[0]) # revealed: str
|
||||
```
|
||||
@@ -218,9 +239,12 @@ reveal_type(C()[0]) # revealed: str
|
||||
def _(flag: bool):
|
||||
class C:
|
||||
if flag:
|
||||
|
||||
def __getitem__(self, key: int) -> str:
|
||||
return str(key)
|
||||
|
||||
else:
|
||||
|
||||
def __getitem__(self, key: int) -> bytes:
|
||||
return bytes()
|
||||
|
||||
@@ -228,11 +252,13 @@ def _(flag: bool):
|
||||
reveal_type(c[0]) # revealed: str | bytes
|
||||
|
||||
if flag:
|
||||
|
||||
class D:
|
||||
def __getitem__(self, key: int) -> str:
|
||||
return str(key)
|
||||
|
||||
else:
|
||||
|
||||
class D:
|
||||
def __getitem__(self, key: int) -> bytes:
|
||||
return bytes()
|
||||
@@ -250,14 +276,17 @@ regular method calls.
|
||||
def external_getitem(instance, key: int) -> str:
|
||||
return str(key)
|
||||
|
||||
|
||||
class NotSubscriptable1:
|
||||
def __init__(self, value: int):
|
||||
self.__getitem__ = external_getitem
|
||||
|
||||
|
||||
class NotSubscriptable2:
|
||||
def __init__(self, value: int):
|
||||
self.__getitem__ = external_getitem
|
||||
|
||||
|
||||
def _(union: NotSubscriptable1 | NotSubscriptable2):
|
||||
# error: [not-subscriptable] "Cannot subscript object of type `NotSubscriptable2` with no `__getitem__` method"
|
||||
# error: [not-subscriptable] "Cannot subscript object of type `NotSubscriptable1` with no `__getitem__` method"
|
||||
@@ -270,6 +299,7 @@ def _(union: NotSubscriptable1 | NotSubscriptable2):
|
||||
def _(flag: bool):
|
||||
class C:
|
||||
if flag:
|
||||
|
||||
def __getitem__(self, key: int) -> str:
|
||||
return str(key)
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
def get_int() -> int:
|
||||
return 42
|
||||
|
||||
|
||||
reveal_type(get_int()) # revealed: int
|
||||
```
|
||||
|
||||
@@ -15,6 +16,7 @@ reveal_type(get_int()) # revealed: int
|
||||
async def get_int_async() -> int:
|
||||
return 42
|
||||
|
||||
|
||||
reveal_type(get_int_async()) # revealed: CoroutineType[Any, Any, int]
|
||||
```
|
||||
|
||||
@@ -29,6 +31,7 @@ python-version = "3.12"
|
||||
def get_int[T]() -> int:
|
||||
return 42
|
||||
|
||||
|
||||
reveal_type(get_int()) # revealed: int
|
||||
```
|
||||
|
||||
@@ -37,16 +40,20 @@ reveal_type(get_int()) # revealed: int
|
||||
```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
|
||||
```
|
||||
|
||||
@@ -62,8 +69,10 @@ x = nonsense() # error: "Object of type `Literal[123]` is not callable"
|
||||
```py
|
||||
def _(flag: bool):
|
||||
if flag:
|
||||
|
||||
def foo() -> int:
|
||||
return 42
|
||||
|
||||
# error: [possibly-unresolved-reference]
|
||||
reveal_type(foo()) # revealed: int
|
||||
```
|
||||
@@ -89,6 +98,7 @@ still continue to use the old convention, so it is supported by ty as well.
|
||||
```py
|
||||
def f(__x: int): ...
|
||||
|
||||
|
||||
f(1)
|
||||
# error: [positional-only-parameter-as-kwarg]
|
||||
f(__x=1)
|
||||
@@ -99,6 +109,7 @@ But not if they follow a non-positional-only parameter:
|
||||
```py
|
||||
def g(x: int, __y: str): ...
|
||||
|
||||
|
||||
g(x=1, __y="foo")
|
||||
```
|
||||
|
||||
@@ -107,6 +118,7 @@ And also not if they both start and end with `__`:
|
||||
```py
|
||||
def h(__x__: str): ...
|
||||
|
||||
|
||||
h(__x__="foo")
|
||||
```
|
||||
|
||||
@@ -115,6 +127,7 @@ And if *any* parameters use the new PEP-570 convention, the old convention does
|
||||
```py
|
||||
def i(x: str, /, __y: int): ...
|
||||
|
||||
|
||||
i("foo", __y=42) # fine
|
||||
```
|
||||
|
||||
@@ -125,11 +138,13 @@ class C:
|
||||
def method(self, __x: int): ...
|
||||
@classmethod
|
||||
def class_method(cls, __x: str): ...
|
||||
|
||||
# (the name of the first parameter is irrelevant;
|
||||
# a staticmethod works the same as a free function in the global scope)
|
||||
@staticmethod
|
||||
def static_method(self, __x: int): ...
|
||||
|
||||
|
||||
# error: [positional-only-parameter-as-kwarg]
|
||||
C().method(__x=1)
|
||||
# error: [positional-only-parameter-as-kwarg]
|
||||
@@ -153,8 +168,10 @@ 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)
|
||||
@@ -169,6 +186,7 @@ def _(args: list[int]) -> None:
|
||||
takes_at_least_two(*args)
|
||||
takes_at_least_two_positional_only(*args)
|
||||
|
||||
|
||||
def _(args: tuple[int, ...]) -> None:
|
||||
takes_zero(*args)
|
||||
takes_one(*args)
|
||||
@@ -196,8 +214,10 @@ 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)
|
||||
@@ -210,6 +230,7 @@ def _(args: tuple[int]) -> None:
|
||||
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]
|
||||
@@ -222,6 +243,7 @@ def _(args: tuple[int, int]) -> None:
|
||||
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]
|
||||
@@ -249,10 +271,13 @@ 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
|
||||
|
||||
|
||||
class SingleElementTuple(tuple[int]): ...
|
||||
|
||||
|
||||
def _(args: SingleElementTuple) -> None:
|
||||
takes_zero(*args) # error: [too-many-positional-arguments]
|
||||
|
||||
@@ -270,8 +295,10 @@ def _(args: SingleElementTuple) -> None:
|
||||
takes_at_least_two(*args) # error: [missing-argument]
|
||||
takes_at_least_two_positional_only(*args) # error: [missing-argument]
|
||||
|
||||
|
||||
class TwoElementIntTuple(tuple[int, int]): ...
|
||||
|
||||
|
||||
def _(args: TwoElementIntTuple) -> None:
|
||||
takes_zero(*args) # error: [too-many-positional-arguments]
|
||||
takes_one(*args) # error: [too-many-positional-arguments]
|
||||
@@ -284,8 +311,10 @@ def _(args: TwoElementIntTuple) -> None:
|
||||
takes_at_least_two(*args)
|
||||
takes_at_least_two_positional_only(*args)
|
||||
|
||||
|
||||
class IntStrTuple(tuple[int, str]): ...
|
||||
|
||||
|
||||
def _(args: IntStrTuple) -> None:
|
||||
takes_zero(*args) # error: [too-many-positional-arguments]
|
||||
|
||||
@@ -326,8 +355,10 @@ 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)
|
||||
@@ -340,6 +371,7 @@ def _(args: tuple[int, *tuple[int, ...]]) -> None:
|
||||
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)
|
||||
@@ -352,6 +384,7 @@ def _(args: tuple[int, *tuple[str, ...]]) -> None:
|
||||
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]
|
||||
@@ -364,6 +397,7 @@ def _(args: tuple[int, int, *tuple[int, ...]]) -> None:
|
||||
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]
|
||||
@@ -376,6 +410,7 @@ def _(args: tuple[int, int, *tuple[str, ...]]) -> None:
|
||||
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]
|
||||
@@ -388,6 +423,7 @@ def _(args: tuple[int, *tuple[int, ...], int]) -> None:
|
||||
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]
|
||||
@@ -420,10 +456,13 @@ 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
|
||||
|
||||
|
||||
class IntStarInt(tuple[int, *tuple[int, ...]]): ...
|
||||
|
||||
|
||||
def _(args: IntStarInt) -> None:
|
||||
takes_zero(*args) # error: [too-many-positional-arguments]
|
||||
takes_one(*args)
|
||||
@@ -436,8 +475,10 @@ def _(args: IntStarInt) -> None:
|
||||
takes_at_least_two(*args)
|
||||
takes_at_least_two_positional_only(*args)
|
||||
|
||||
|
||||
class IntStarStr(tuple[int, *tuple[str, ...]]): ...
|
||||
|
||||
|
||||
def _(args: IntStarStr) -> None:
|
||||
takes_zero(*args) # error: [too-many-positional-arguments]
|
||||
|
||||
@@ -460,8 +501,10 @@ def _(args: IntStarStr) -> None:
|
||||
# error: [invalid-argument-type]
|
||||
takes_at_least_two_positional_only(*args)
|
||||
|
||||
|
||||
class IntIntStarInt(tuple[int, int, *tuple[int, ...]]): ...
|
||||
|
||||
|
||||
def _(args: IntIntStarInt) -> None:
|
||||
takes_zero(*args) # error: [too-many-positional-arguments]
|
||||
takes_one(*args) # error: [too-many-positional-arguments]
|
||||
@@ -474,8 +517,10 @@ def _(args: IntIntStarInt) -> None:
|
||||
takes_at_least_two(*args)
|
||||
takes_at_least_two_positional_only(*args)
|
||||
|
||||
|
||||
class IntIntStarStr(tuple[int, int, *tuple[str, ...]]): ...
|
||||
|
||||
|
||||
def _(args: IntIntStarStr) -> None:
|
||||
takes_zero(*args) # error: [too-many-positional-arguments]
|
||||
|
||||
@@ -497,8 +542,10 @@ def _(args: IntIntStarStr) -> None:
|
||||
|
||||
takes_at_least_two_positional_only(*args)
|
||||
|
||||
|
||||
class IntStarIntInt(tuple[int, *tuple[int, ...], int]): ...
|
||||
|
||||
|
||||
def _(args: IntStarIntInt) -> None:
|
||||
takes_zero(*args) # error: [too-many-positional-arguments]
|
||||
takes_one(*args) # error: [too-many-positional-arguments]
|
||||
@@ -511,8 +558,10 @@ def _(args: IntStarIntInt) -> None:
|
||||
takes_at_least_two(*args)
|
||||
takes_at_least_two_positional_only(*args)
|
||||
|
||||
|
||||
class IntStarStrInt(tuple[int, *tuple[str, ...], int]): ...
|
||||
|
||||
|
||||
def _(args: IntStarStrInt) -> None:
|
||||
takes_zero(*args) # error: [too-many-positional-arguments]
|
||||
|
||||
@@ -542,6 +591,7 @@ def _(args: IntStarStrInt) -> None:
|
||||
```py
|
||||
from typing import Literal
|
||||
|
||||
|
||||
def takes_zero() -> None: ...
|
||||
def takes_one(x: str) -> None: ...
|
||||
def takes_two(x: str, y: str) -> None: ...
|
||||
@@ -553,8 +603,10 @@ 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)
|
||||
@@ -571,6 +623,7 @@ def _(args: Literal["a"]) -> None:
|
||||
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]
|
||||
@@ -583,6 +636,7 @@ def _(args: Literal["ab"]) -> None:
|
||||
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]
|
||||
@@ -599,6 +653,7 @@ def _(args: Literal["abc"]) -> None:
|
||||
takes_at_least_two(*args)
|
||||
takes_at_least_two_positional_only(*args)
|
||||
|
||||
|
||||
def _(args: str) -> None:
|
||||
takes_zero(*args)
|
||||
takes_one(*args)
|
||||
@@ -620,6 +675,7 @@ def _(args: str) -> None:
|
||||
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
|
||||
```
|
||||
@@ -630,6 +686,7 @@ reveal_type(f("foo")) # revealed: int
|
||||
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
|
||||
```
|
||||
@@ -640,6 +697,7 @@ reveal_type(f("foo")) # revealed: int
|
||||
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
|
||||
```
|
||||
@@ -655,6 +713,7 @@ python-version = "3.11"
|
||||
def f(*args: int) -> int:
|
||||
return 1
|
||||
|
||||
|
||||
def _(args: list[str]) -> None:
|
||||
# error: [invalid-argument-type] "Argument to function `f` is incorrect: Expected `int`, found `str`"
|
||||
reveal_type(f(*args)) # revealed: int
|
||||
@@ -701,6 +760,7 @@ A union of heterogeneous tuples provided to a variadic parameter:
|
||||
# - <https://github.com/home-assistant/core/blob/bde4eb50111a72f9717fe73ee5929e50eb06911b/homeassistant/components/lovelace/websocket.py#L50-L59>
|
||||
# - <https://github.com/pydata/xarray/blob/3572f4e70f2b12ef9935c1f8c3c1b74045d2a092/xarray/tests/test_groupby.py#L3058-L3059>
|
||||
|
||||
|
||||
def f2(a: str, b: bool): ...
|
||||
def f3(coinflip: bool):
|
||||
if coinflip:
|
||||
@@ -722,8 +782,10 @@ def f3(coinflip: bool):
|
||||
# error: [invalid-argument-type] "Argument to function `f2` is incorrect: Expected `bool`, found `Literal[True] | tuple[Literal[True]]`"
|
||||
f2(*other_args)
|
||||
|
||||
|
||||
def f4(a=None, b=None, c=None, d=None, e=None): ...
|
||||
|
||||
|
||||
my_args = ((1, 2), (3, 4), (5, 6))
|
||||
|
||||
for tup in my_args:
|
||||
@@ -750,6 +812,7 @@ python-version = "3.11"
|
||||
def f(x: int, *args: str) -> int:
|
||||
return 1
|
||||
|
||||
|
||||
def _(
|
||||
args1: list[int],
|
||||
args2: tuple[int],
|
||||
@@ -785,6 +848,7 @@ def _(
|
||||
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
|
||||
```
|
||||
@@ -795,6 +859,7 @@ reveal_type(f(x="foo")) # revealed: int
|
||||
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
|
||||
```
|
||||
@@ -805,6 +870,7 @@ reveal_type(f(x="foo")) # revealed: int
|
||||
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
|
||||
```
|
||||
@@ -815,6 +881,7 @@ reveal_type(f(x="foo")) # revealed: int
|
||||
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
|
||||
@@ -827,10 +894,16 @@ reveal_type(f(y=2, x="bar")) # revealed: int
|
||||
```py
|
||||
from typing import Sized
|
||||
|
||||
|
||||
class Foo: ...
|
||||
|
||||
|
||||
class Bar: ...
|
||||
|
||||
|
||||
class Baz: ...
|
||||
|
||||
|
||||
def f(x: Sized): ...
|
||||
def g(
|
||||
a: str | Foo,
|
||||
@@ -852,6 +925,7 @@ def g(
|
||||
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
|
||||
```
|
||||
@@ -862,6 +936,7 @@ reveal_type(f("foo")) # revealed: int
|
||||
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
|
||||
```
|
||||
@@ -872,6 +947,7 @@ reveal_type(f("foo", "bar")) # revealed: int
|
||||
def f(*args: int) -> int:
|
||||
return 1
|
||||
|
||||
|
||||
reveal_type(f(1, 2, 3)) # revealed: int
|
||||
```
|
||||
|
||||
@@ -881,6 +957,7 @@ reveal_type(f(1, 2, 3)) # revealed: int
|
||||
def f(**kwargs: int) -> int:
|
||||
return 1
|
||||
|
||||
|
||||
reveal_type(f(foo=1, bar=2)) # revealed: int
|
||||
```
|
||||
|
||||
@@ -892,6 +969,7 @@ reveal_type(f(foo=1, bar=2)) # revealed: int
|
||||
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
|
||||
```
|
||||
@@ -902,6 +980,7 @@ reveal_type(f()) # revealed: int
|
||||
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
|
||||
```
|
||||
@@ -912,6 +991,7 @@ reveal_type(f()) # revealed: int
|
||||
def f(x: int = 1) -> int:
|
||||
return 1
|
||||
|
||||
|
||||
reveal_type(f()) # revealed: int
|
||||
```
|
||||
|
||||
@@ -921,6 +1001,7 @@ reveal_type(f()) # revealed: int
|
||||
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
|
||||
```
|
||||
@@ -931,6 +1012,7 @@ reveal_type(f()) # revealed: int
|
||||
def f(*args: int) -> int:
|
||||
return 1
|
||||
|
||||
|
||||
reveal_type(f()) # revealed: int
|
||||
```
|
||||
|
||||
@@ -940,6 +1022,7 @@ reveal_type(f()) # revealed: int
|
||||
def f(**kwargs: int) -> int:
|
||||
return 1
|
||||
|
||||
|
||||
reveal_type(f()) # revealed: int
|
||||
```
|
||||
|
||||
@@ -949,6 +1032,7 @@ reveal_type(f()) # revealed: int
|
||||
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
|
||||
```
|
||||
@@ -959,6 +1043,7 @@ reveal_type(f()) # revealed: int
|
||||
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
|
||||
```
|
||||
@@ -969,6 +1054,7 @@ reveal_type(f(x=1, y=2)) # revealed: int
|
||||
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
|
||||
```
|
||||
@@ -1044,6 +1130,7 @@ def empty() -> None: ...
|
||||
def _(kwargs: dict[str, int]) -> None:
|
||||
empty(**kwargs)
|
||||
|
||||
|
||||
empty(**{})
|
||||
empty(**dict())
|
||||
```
|
||||
@@ -1053,15 +1140,19 @@ empty(**dict())
|
||||
```py
|
||||
from typing_extensions import TypedDict
|
||||
|
||||
|
||||
def f(**kwargs: int) -> None: ...
|
||||
|
||||
|
||||
class Foo(TypedDict):
|
||||
a: int
|
||||
b: int
|
||||
|
||||
|
||||
def _(kwargs: dict[str, int]) -> None:
|
||||
f(**kwargs)
|
||||
|
||||
|
||||
f(**{"foo": 1})
|
||||
f(**dict(foo=1))
|
||||
f(**Foo(a=1, b=2))
|
||||
@@ -1085,14 +1176,17 @@ def _(kwargs: dict[str, int]) -> None:
|
||||
```py
|
||||
from typing_extensions import TypedDict
|
||||
|
||||
|
||||
class Foo(TypedDict):
|
||||
a: int
|
||||
b: int
|
||||
|
||||
|
||||
def f(a: int, b: int) -> None: ...
|
||||
def _(kwargs: dict[str, int]) -> None:
|
||||
f(**kwargs)
|
||||
|
||||
|
||||
f(**{"a": 1, "b": 2})
|
||||
f(**dict(a=1, b=2))
|
||||
f(**Foo(a=1, b=2))
|
||||
@@ -1103,14 +1197,17 @@ f(**Foo(a=1, b=2))
|
||||
```py
|
||||
from typing_extensions import TypedDict
|
||||
|
||||
|
||||
class Foo(TypedDict):
|
||||
a: int
|
||||
b: int
|
||||
|
||||
|
||||
def f(*, a: int, b: int) -> None: ...
|
||||
def _(kwargs: dict[str, int]) -> None:
|
||||
f(**kwargs)
|
||||
|
||||
|
||||
f(**{"a": 1, "b": 2})
|
||||
f(**dict(a=1, b=2))
|
||||
f(**Foo(a=1, b=2))
|
||||
@@ -1135,6 +1232,7 @@ def _(kwargs1: dict[str, int], kwargs2: dict[str, int], kwargs3: dict[str, str],
|
||||
```py
|
||||
class B: ...
|
||||
|
||||
|
||||
def f(*, a: int, b: B, **kwargs: int) -> None: ...
|
||||
def _(kwargs: dict[str, int]):
|
||||
# Make sure that the `b` argument is not being matched against `kwargs` by passing an integer
|
||||
@@ -1160,16 +1258,20 @@ def _(kwargs1: dict[str, int], kwargs2: dict[str, str]):
|
||||
```py
|
||||
from typing_extensions import NotRequired, TypedDict
|
||||
|
||||
|
||||
class Foo1(TypedDict):
|
||||
a: int
|
||||
b: str
|
||||
|
||||
|
||||
class Foo2(TypedDict):
|
||||
a: int
|
||||
b: NotRequired[str]
|
||||
|
||||
|
||||
def f(**kwargs: int) -> None: ...
|
||||
|
||||
|
||||
# error: [invalid-argument-type] "Argument to function `f` is incorrect: Expected `int`, found `str`"
|
||||
f(**Foo1(a=1, b="b"))
|
||||
# error: [invalid-argument-type] "Argument to function `f` is incorrect: Expected `int`, found `str`"
|
||||
@@ -1183,11 +1285,16 @@ The keys of the mapping passed to a double-starred argument must be strings.
|
||||
```py
|
||||
from collections.abc import Mapping
|
||||
|
||||
|
||||
def f(**kwargs: int) -> None: ...
|
||||
|
||||
|
||||
class DictSubclass(dict[int, int]): ...
|
||||
|
||||
|
||||
class MappingSubclass(Mapping[int, int]): ...
|
||||
|
||||
|
||||
class MappingProtocol:
|
||||
def keys(self) -> list[int]:
|
||||
return [1]
|
||||
@@ -1195,10 +1302,12 @@ class MappingProtocol:
|
||||
def __getitem__(self, key: int) -> int:
|
||||
return 1
|
||||
|
||||
|
||||
def _(kwargs: dict[int, int]) -> None:
|
||||
# error: [invalid-argument-type] "Argument expression after ** must be a mapping with `str` key type: Found `int`"
|
||||
f(**kwargs)
|
||||
|
||||
|
||||
# error: [invalid-argument-type] "Argument expression after ** must be a mapping with `str` key type: Found `int`"
|
||||
f(**DictSubclass())
|
||||
# error: [invalid-argument-type] "Argument expression after ** must be a mapping with `str` key type: Found `int`"
|
||||
@@ -1211,8 +1320,11 @@ The key can also be a custom type that inherits from `str`.
|
||||
|
||||
```py
|
||||
class SubStr(str): ...
|
||||
|
||||
|
||||
class SubInt(int): ...
|
||||
|
||||
|
||||
def _(kwargs1: dict[SubStr, int], kwargs2: dict[SubInt, int]) -> None:
|
||||
f(**kwargs1)
|
||||
# error: [invalid-argument-type] "Argument expression after ** must be a mapping with `str` key type: Found `SubInt`"
|
||||
@@ -1225,6 +1337,7 @@ Or, it can be a type that is assignable to `str`.
|
||||
from typing import Any
|
||||
from ty_extensions import Unknown
|
||||
|
||||
|
||||
def _(kwargs1: dict[Any, int], kwargs2: dict[Unknown, int]) -> None:
|
||||
f(**kwargs1)
|
||||
f(**kwargs2)
|
||||
@@ -1235,11 +1348,16 @@ def _(kwargs1: dict[Any, int], kwargs2: dict[Unknown, int]) -> None:
|
||||
```py
|
||||
from collections.abc import Mapping
|
||||
|
||||
|
||||
def f(**kwargs: str) -> None: ...
|
||||
|
||||
|
||||
class DictSubclass(dict[str, int]): ...
|
||||
|
||||
|
||||
class MappingSubclass(Mapping[str, int]): ...
|
||||
|
||||
|
||||
class MappingProtocol:
|
||||
def keys(self) -> list[str]:
|
||||
return ["foo"]
|
||||
@@ -1247,6 +1365,7 @@ class MappingProtocol:
|
||||
def __getitem__(self, key: str) -> int:
|
||||
return 1
|
||||
|
||||
|
||||
def _(kwargs: dict[str, int]) -> None:
|
||||
# error: [invalid-argument-type] "Argument to function `f` is incorrect: Expected `str`, found `int`"
|
||||
f(**kwargs)
|
||||
@@ -1263,6 +1382,7 @@ def _(kwargs: dict[str, int]) -> None:
|
||||
```py
|
||||
from ty_extensions import Unknown
|
||||
|
||||
|
||||
def f(**kwargs: int) -> None: ...
|
||||
def _(kwargs: Unknown):
|
||||
f(**kwargs)
|
||||
@@ -1273,8 +1393,10 @@ def _(kwargs: Unknown):
|
||||
```py
|
||||
def f(**kwargs: int) -> None: ...
|
||||
|
||||
|
||||
class A: ...
|
||||
|
||||
|
||||
class InvalidMapping:
|
||||
def keys(self) -> A:
|
||||
return A()
|
||||
@@ -1282,6 +1404,7 @@ class InvalidMapping:
|
||||
def __getitem__(self, key: str) -> int:
|
||||
return 1
|
||||
|
||||
|
||||
def _(kwargs: dict[str, int] | int):
|
||||
# error: [invalid-argument-type] "Argument expression after ** must be a mapping type: Found `dict[str, int] | int`"
|
||||
f(**kwargs)
|
||||
@@ -1299,9 +1422,11 @@ from typing import TypeVar
|
||||
|
||||
_T = TypeVar("_T")
|
||||
|
||||
|
||||
def f(**kwargs: _T) -> _T:
|
||||
return kwargs["a"]
|
||||
|
||||
|
||||
def _(kwargs: dict[str, int]) -> None:
|
||||
reveal_type(f(**kwargs)) # revealed: int
|
||||
```
|
||||
@@ -1314,12 +1439,15 @@ from typing_extensions import TypedDict
|
||||
|
||||
_T = TypeVar("_T")
|
||||
|
||||
|
||||
class Foo(TypedDict):
|
||||
a: int
|
||||
b: str
|
||||
|
||||
|
||||
def f(**kwargs: _T) -> _T:
|
||||
return kwargs["a"]
|
||||
|
||||
|
||||
reveal_type(f(**Foo(a=1, b="b"))) # revealed: int | str
|
||||
```
|
||||
|
||||
@@ -10,10 +10,12 @@ Consider the following example:
|
||||
```py
|
||||
import inspect
|
||||
|
||||
|
||||
class Descriptor:
|
||||
def __get__(self, instance, owner) -> str:
|
||||
return "a"
|
||||
|
||||
|
||||
class C:
|
||||
normal: int = 1
|
||||
descriptor: Descriptor = Descriptor()
|
||||
@@ -70,6 +72,7 @@ class D:
|
||||
def __init__(self) -> None:
|
||||
self.instance_attr: int = 1
|
||||
|
||||
|
||||
reveal_type(inspect.getattr_static(D(), "instance_attr")) # revealed: int
|
||||
```
|
||||
|
||||
@@ -79,8 +82,10 @@ And attributes on metaclasses can be accessed when probing the class:
|
||||
class Meta(type):
|
||||
attr: int = 1
|
||||
|
||||
|
||||
class E(metaclass=Meta): ...
|
||||
|
||||
|
||||
reveal_type(inspect.getattr_static(E, "attr")) # revealed: int
|
||||
```
|
||||
|
||||
@@ -98,9 +103,11 @@ back to `Any`:
|
||||
```py
|
||||
import inspect
|
||||
|
||||
|
||||
class C:
|
||||
x: int = 1
|
||||
|
||||
|
||||
def _(attr_name: str):
|
||||
reveal_type(inspect.getattr_static(C(), attr_name)) # revealed: Any
|
||||
reveal_type(inspect.getattr_static(C(), attr_name, 1)) # revealed: Any
|
||||
@@ -127,6 +134,7 @@ inspect.getattr_static(C(), "x", "default-arg", "one too many")
|
||||
```py
|
||||
import inspect
|
||||
|
||||
|
||||
def _(flag: bool):
|
||||
class C:
|
||||
if flag:
|
||||
@@ -141,6 +149,7 @@ def _(flag: bool):
|
||||
import inspect
|
||||
from typing import Any
|
||||
|
||||
|
||||
def _(a: Any, tuple_of_any: tuple[Any]):
|
||||
reveal_type(inspect.getattr_static(a, "x", "default")) # revealed: Any | Literal["default"]
|
||||
|
||||
|
||||
@@ -83,6 +83,7 @@ When we access methods from derived classes, they will be bound to instances of
|
||||
class D(C):
|
||||
pass
|
||||
|
||||
|
||||
reveal_type(D().f) # revealed: bound method D.f(x: int) -> str
|
||||
```
|
||||
|
||||
@@ -107,10 +108,12 @@ class Base:
|
||||
def method_on_base(self, x: int | None) -> str:
|
||||
return "a"
|
||||
|
||||
|
||||
class Derived(Base):
|
||||
def method_on_derived(self, x: bytes) -> tuple[int, str]:
|
||||
return (1, "a")
|
||||
|
||||
|
||||
reveal_type(Base().method_on_base(1)) # revealed: str
|
||||
reveal_type(Base.method_on_base(Base(), 1)) # revealed: str
|
||||
|
||||
@@ -159,6 +162,7 @@ reveal_type(b"abcde".startswith(b"abc")) # revealed: bool
|
||||
```py
|
||||
from typing_extensions import LiteralString
|
||||
|
||||
|
||||
def f(s: LiteralString) -> None:
|
||||
reveal_type(s.find("a")) # revealed: int
|
||||
```
|
||||
@@ -175,14 +179,17 @@ def f(t: tuple[int, str]) -> None:
|
||||
```py
|
||||
from typing import Any
|
||||
|
||||
|
||||
class A:
|
||||
def f(self) -> int:
|
||||
return 1
|
||||
|
||||
|
||||
class B:
|
||||
def f(self) -> str:
|
||||
return "a"
|
||||
|
||||
|
||||
def f(a_or_b: A | B, any_or_a: Any | A):
|
||||
reveal_type(a_or_b.f) # revealed: (bound method A.f() -> int) | (bound method B.f() -> str)
|
||||
reveal_type(a_or_b.f()) # revealed: int | str
|
||||
@@ -216,14 +223,18 @@ class:
|
||||
from typing import Protocol, Literal
|
||||
from ty_extensions import AlwaysFalsy
|
||||
|
||||
|
||||
class Foo: ...
|
||||
|
||||
|
||||
class SupportsStr(Protocol):
|
||||
def __str__(self) -> str: ...
|
||||
|
||||
|
||||
class Falsy(Protocol):
|
||||
def __bool__(self) -> Literal[False]: ...
|
||||
|
||||
|
||||
def _(a: object, b: SupportsStr, c: Falsy, d: AlwaysFalsy, e: None, f: Foo | None):
|
||||
a.__str__()
|
||||
b.__str__()
|
||||
@@ -253,10 +264,12 @@ Here, we test that this signature is enforced correctly:
|
||||
```py
|
||||
from inspect import getattr_static
|
||||
|
||||
|
||||
class C:
|
||||
def f(self, x: int) -> str:
|
||||
return "a"
|
||||
|
||||
|
||||
method_wrapper = getattr_static(C, "f").__get__
|
||||
|
||||
reveal_type(method_wrapper) # revealed: <method-wrapper '__get__' of function 'f'>
|
||||
@@ -298,13 +311,16 @@ the class itself. This also creates a bound method that is bound to the class ob
|
||||
```py
|
||||
from __future__ import annotations
|
||||
|
||||
|
||||
class Meta(type):
|
||||
def f(cls, arg: int) -> str:
|
||||
return "a"
|
||||
|
||||
|
||||
class C(metaclass=Meta):
|
||||
pass
|
||||
|
||||
|
||||
reveal_type(C.f) # revealed: bound method <class 'C'>.f(arg: int) -> str
|
||||
reveal_type(C.f(1)) # revealed: str
|
||||
```
|
||||
@@ -321,10 +337,12 @@ A metaclass function can be shadowed by a method on the class:
|
||||
```py
|
||||
from typing import Any, Literal
|
||||
|
||||
|
||||
class D(metaclass=Meta):
|
||||
def f(arg: int) -> Literal["a"]:
|
||||
return "a"
|
||||
|
||||
|
||||
reveal_type(D.f(1)) # revealed: Literal["a"]
|
||||
```
|
||||
|
||||
@@ -334,11 +352,14 @@ If the class method is possibly missing, we union the return types:
|
||||
def flag() -> bool:
|
||||
return True
|
||||
|
||||
|
||||
class E(metaclass=Meta):
|
||||
if flag():
|
||||
|
||||
def f(arg: int) -> Any:
|
||||
return "a"
|
||||
|
||||
|
||||
reveal_type(E.f(1)) # revealed: str | Any
|
||||
```
|
||||
|
||||
@@ -352,11 +373,13 @@ the class object itself:
|
||||
```py
|
||||
from __future__ import annotations
|
||||
|
||||
|
||||
class C:
|
||||
@classmethod
|
||||
def f(cls: type[C], x: int) -> str:
|
||||
return "a"
|
||||
|
||||
|
||||
reveal_type(C.f) # revealed: bound method <class 'C'>.f(x: int) -> str
|
||||
reveal_type(C().f) # revealed: bound method type[C].f(x: int) -> str
|
||||
```
|
||||
@@ -385,6 +408,7 @@ class D:
|
||||
# This function is wrongly annotated, it should be `type[D]` instead of `D`
|
||||
pass
|
||||
|
||||
|
||||
# error: [invalid-argument-type] "Argument to bound method `f` is incorrect: Expected `D`, found `<class 'D'>`"
|
||||
D.f()
|
||||
```
|
||||
@@ -395,6 +419,7 @@ When a class method is accessed on a derived class, it is bound to that derived
|
||||
class Derived(C):
|
||||
pass
|
||||
|
||||
|
||||
reveal_type(Derived.f) # revealed: bound method <class 'Derived'>.f(x: int) -> str
|
||||
reveal_type(Derived().f) # revealed: bound method type[Derived].f(x: int) -> str
|
||||
|
||||
@@ -410,10 +435,12 @@ currently don't model this explicitly:
|
||||
```py
|
||||
from inspect import getattr_static
|
||||
|
||||
|
||||
class C:
|
||||
@classmethod
|
||||
def f(cls): ...
|
||||
|
||||
|
||||
reveal_type(getattr_static(C, "f")) # revealed: def f(cls) -> Unknown
|
||||
# revealed: <method-wrapper '__get__' of function 'f'>
|
||||
reveal_type(getattr_static(C, "f").__get__)
|
||||
@@ -447,6 +474,7 @@ class method:
|
||||
def does_nothing[T](f: T) -> T:
|
||||
return f
|
||||
|
||||
|
||||
class C:
|
||||
@classmethod
|
||||
@does_nothing
|
||||
@@ -458,6 +486,7 @@ class C:
|
||||
def f2(cls, x: int) -> str:
|
||||
return "a"
|
||||
|
||||
|
||||
reveal_type(C.f1(1)) # revealed: str
|
||||
reveal_type(C().f1(1)) # revealed: str
|
||||
reveal_type(C.f2(1)) # revealed: str
|
||||
@@ -475,14 +504,17 @@ from contextlib import contextmanager
|
||||
from typing import Iterator
|
||||
from typing_extensions import Self
|
||||
|
||||
|
||||
class Base:
|
||||
@classmethod
|
||||
@contextmanager
|
||||
def create(cls) -> Iterator[Self]:
|
||||
yield cls()
|
||||
|
||||
|
||||
class Child(Base): ...
|
||||
|
||||
|
||||
reveal_type(Base.create()) # revealed: _GeneratorContextManager[Base, None, None]
|
||||
with Base.create() as base:
|
||||
reveal_type(base) # revealed: Base
|
||||
@@ -515,9 +547,11 @@ class Base:
|
||||
super().__init_subclass__(**kwargs)
|
||||
cls.custom_attribute: int = 0
|
||||
|
||||
|
||||
class Derived(Base):
|
||||
pass
|
||||
|
||||
|
||||
reveal_type(Derived.custom_attribute) # revealed: int
|
||||
```
|
||||
|
||||
@@ -527,17 +561,25 @@ Subclasses must be constructed with arguments matching the required arguments of
|
||||
```py
|
||||
class Empty: ...
|
||||
|
||||
|
||||
class RequiresArg:
|
||||
def __init_subclass__(cls, arg: int): ...
|
||||
|
||||
|
||||
class NoArg:
|
||||
def __init_subclass__(cls): ...
|
||||
|
||||
|
||||
# Single-base definitions
|
||||
class MissingArg(RequiresArg): ... # error: [missing-argument]
|
||||
|
||||
|
||||
class InvalidType(RequiresArg, arg="foo"): ... # error: [invalid-argument-type]
|
||||
|
||||
|
||||
class Valid(RequiresArg, arg=1): ...
|
||||
|
||||
|
||||
# error: [missing-argument]
|
||||
# error: [unknown-argument]
|
||||
class IncorrectArg(RequiresArg, not_arg="foo"): ...
|
||||
@@ -548,28 +590,53 @@ For multiple inheritance, the first resolved `__init_subclass__` method is used.
|
||||
```py
|
||||
class Empty: ...
|
||||
|
||||
|
||||
class RequiresArg:
|
||||
def __init_subclass__(cls, arg: int): ...
|
||||
|
||||
|
||||
class NoArg:
|
||||
def __init_subclass__(cls): ...
|
||||
|
||||
|
||||
class Valid(NoArg, RequiresArg): ...
|
||||
|
||||
|
||||
class MissingArg(RequiresArg, NoArg): ... # error: [missing-argument]
|
||||
|
||||
|
||||
class InvalidType(RequiresArg, NoArg, arg="foo"): ... # error: [invalid-argument-type]
|
||||
|
||||
|
||||
class Valid(RequiresArg, NoArg, arg=1): ...
|
||||
|
||||
|
||||
# Ensure base class without __init_subclass__ is ignored
|
||||
class Valid(Empty, NoArg): ...
|
||||
|
||||
|
||||
class Valid(Empty, RequiresArg, NoArg, arg=1): ...
|
||||
|
||||
|
||||
class MissingArg(Empty, RequiresArg): ... # error: [missing-argument]
|
||||
|
||||
|
||||
class MissingArg(Empty, RequiresArg, NoArg): ... # error: [missing-argument]
|
||||
|
||||
|
||||
class InvalidType(Empty, RequiresArg, NoArg, arg="foo"): ... # error: [invalid-argument-type]
|
||||
|
||||
|
||||
# Multiple inheritance with args
|
||||
class Base(Empty, RequiresArg, NoArg, arg=1): ...
|
||||
|
||||
|
||||
class Valid(Base, arg=1): ...
|
||||
|
||||
|
||||
class MissingArg(Base): ... # error: [missing-argument]
|
||||
|
||||
|
||||
class InvalidType(Base, arg="foo"): ... # error: [invalid-argument-type]
|
||||
```
|
||||
|
||||
@@ -578,23 +645,30 @@ Keyword splats are allowed if their type can be determined:
|
||||
```py
|
||||
from typing import TypedDict
|
||||
|
||||
|
||||
class RequiresKwarg:
|
||||
def __init_subclass__(cls, arg: int): ...
|
||||
|
||||
|
||||
class WrongArg(TypedDict):
|
||||
kwarg: int
|
||||
|
||||
|
||||
class InvalidType(TypedDict):
|
||||
arg: str
|
||||
|
||||
|
||||
wrong_arg: WrongArg = {"kwarg": 5}
|
||||
|
||||
|
||||
# error: [missing-argument]
|
||||
# error: [unknown-argument]
|
||||
class MissingArg(RequiresKwarg, **wrong_arg): ...
|
||||
|
||||
|
||||
invalid_type: InvalidType = {"arg": "foo"}
|
||||
|
||||
|
||||
# error: [invalid-argument-type]
|
||||
class InvalidType(RequiresKwarg, **invalid_type): ...
|
||||
```
|
||||
@@ -604,19 +678,28 @@ So are generics:
|
||||
```py
|
||||
from typing import Generic, TypeVar, Literal, overload
|
||||
|
||||
|
||||
class Base[T]:
|
||||
def __init_subclass__(cls, arg: T): ...
|
||||
|
||||
|
||||
class Valid(Base[int], arg=1): ...
|
||||
|
||||
|
||||
class InvalidType(Base[int], arg="x"): ... # error: [invalid-argument-type]
|
||||
|
||||
|
||||
# Old generic syntax
|
||||
T = TypeVar("T")
|
||||
|
||||
|
||||
class Base(Generic[T]):
|
||||
def __init_subclass__(cls, arg: T) -> None: ...
|
||||
|
||||
|
||||
class Valid(Base[int], arg=1): ...
|
||||
|
||||
|
||||
class InvalidType(Base[int], arg="x"): ... # error: [invalid-argument-type]
|
||||
```
|
||||
|
||||
@@ -630,8 +713,13 @@ class Base:
|
||||
def __init_subclass__(cls, mode: Literal["b"], arg: str) -> None: ...
|
||||
def __init_subclass__(cls, mode: str, arg: int | str) -> None: ...
|
||||
|
||||
|
||||
class Valid(Base, mode="a", arg=5): ...
|
||||
|
||||
|
||||
class Valid(Base, mode="b", arg="foo"): ...
|
||||
|
||||
|
||||
class InvalidType(Base, mode="b", arg=5): ... # error: [no-matching-overload]
|
||||
```
|
||||
|
||||
@@ -642,6 +730,7 @@ The `metaclass` keyword is ignored, as it has special meaning and is not passed
|
||||
class Base:
|
||||
def __init_subclass__(cls, arg: int): ...
|
||||
|
||||
|
||||
class Valid(Base, arg=5, metaclass=object): ...
|
||||
```
|
||||
|
||||
@@ -655,11 +744,13 @@ true whether it's accessed on the class or on an instance of the class.
|
||||
```py
|
||||
from __future__ import annotations
|
||||
|
||||
|
||||
class C:
|
||||
@staticmethod
|
||||
def f(x: int) -> str:
|
||||
return "a"
|
||||
|
||||
|
||||
reveal_type(C.f) # revealed: def f(x: int) -> str
|
||||
reveal_type(C().f) # revealed: def f(x: int) -> str
|
||||
```
|
||||
@@ -686,6 +777,7 @@ When a static method is accessed on a derived class, it behaves identically:
|
||||
class Derived(C):
|
||||
pass
|
||||
|
||||
|
||||
reveal_type(Derived.f) # revealed: def f(x: int) -> str
|
||||
reveal_type(Derived().f) # revealed: def f(x: int) -> str
|
||||
|
||||
@@ -698,6 +790,7 @@ reveal_type(Derived().f(1)) # revealed: str
|
||||
```py
|
||||
from inspect import getattr_static
|
||||
|
||||
|
||||
class C:
|
||||
@staticmethod
|
||||
def f(): ...
|
||||
@@ -733,9 +826,11 @@ static method:
|
||||
```py
|
||||
from __future__ import annotations
|
||||
|
||||
|
||||
def does_nothing[T](f: T) -> T:
|
||||
return f
|
||||
|
||||
|
||||
class C:
|
||||
@staticmethod
|
||||
@does_nothing
|
||||
@@ -747,6 +842,7 @@ class C:
|
||||
def f2(x: int) -> str:
|
||||
return "a"
|
||||
|
||||
|
||||
reveal_type(C.f1(1)) # revealed: str
|
||||
reveal_type(C().f1(1)) # revealed: str
|
||||
reveal_type(C.f2(1)) # revealed: str
|
||||
@@ -760,6 +856,7 @@ bind `self`:
|
||||
from contextlib import contextmanager
|
||||
from collections.abc import Iterator
|
||||
|
||||
|
||||
class D:
|
||||
@staticmethod
|
||||
@contextmanager
|
||||
@@ -771,6 +868,7 @@ class D:
|
||||
with self.ctx(10) as x:
|
||||
reveal_type(x) # revealed: int
|
||||
|
||||
|
||||
# Accessing via class works
|
||||
reveal_type(D.ctx(5)) # revealed: _GeneratorContextManager[int, None, None]
|
||||
|
||||
@@ -793,6 +891,7 @@ reveal_type(int.__new__)
|
||||
# revealed: Overload[[Self](cls, x: str | Buffer | SupportsInt | SupportsIndex | SupportsTrunc = 0, /) -> Self, [Self](cls, x: str | bytes | bytearray, /, base: SupportsIndex) -> Self]
|
||||
reveal_type((42).__new__)
|
||||
|
||||
|
||||
class X:
|
||||
def __init__(self, val: int): ...
|
||||
def make_another(self) -> Self:
|
||||
@@ -810,8 +909,10 @@ import types
|
||||
from typing import Callable
|
||||
from ty_extensions import static_assert, CallableTypeOf, is_assignable_to, TypeOf
|
||||
|
||||
|
||||
def f(obj: type) -> None: ...
|
||||
|
||||
|
||||
class MyClass:
|
||||
@property
|
||||
def my_property(self) -> int:
|
||||
@@ -820,6 +921,7 @@ class MyClass:
|
||||
@my_property.setter
|
||||
def my_property(self, value: int | str) -> None: ...
|
||||
|
||||
|
||||
static_assert(is_assignable_to(types.FunctionType, Callable))
|
||||
|
||||
# revealed: <wrapper-descriptor '__get__' of 'function' objects>
|
||||
@@ -871,6 +973,7 @@ static_assert(is_assignable_to(TypeOf[str.startswith], Callable))
|
||||
reveal_type("foo".startswith)
|
||||
static_assert(is_assignable_to(TypeOf["foo".startswith], Callable))
|
||||
|
||||
|
||||
def _(
|
||||
a: CallableTypeOf[types.FunctionType.__get__],
|
||||
b: CallableTypeOf[f],
|
||||
|
||||
@@ -5,6 +5,7 @@ The type `Never` is callable with an arbitrary set of arguments. The result is a
|
||||
```py
|
||||
from typing_extensions import Never
|
||||
|
||||
|
||||
def f(never: Never):
|
||||
reveal_type(never()) # revealed: Never
|
||||
reveal_type(never(1)) # revealed: Never
|
||||
|
||||
@@ -15,6 +15,7 @@ reveal_type(open("", "rb")) # revealed: BufferedReader[_BufferedReaderStream]
|
||||
with open("foo.pickle", "rb") as f:
|
||||
x = pickle.load(f) # fine
|
||||
|
||||
|
||||
def _(mode: str):
|
||||
reveal_type(open("", mode)) # revealed: IO[Any]
|
||||
```
|
||||
|
||||
@@ -174,6 +174,7 @@ def f(x: C) -> C: ...
|
||||
```py
|
||||
from overloaded import A, B, C, f
|
||||
|
||||
|
||||
def _(ab: A | B, ac: A | C, bc: B | C):
|
||||
reveal_type(f(ab)) # revealed: A | B
|
||||
reveal_type(f(*(ab,))) # revealed: A | B
|
||||
@@ -213,6 +214,7 @@ def f(x: B, y: D) -> D: ...
|
||||
```py
|
||||
from overloaded import A, B, C, D, f
|
||||
|
||||
|
||||
def _(a_b: A | B):
|
||||
reveal_type(f(a_b, C())) # revealed: A | C
|
||||
reveal_type(f(*(a_b, C()))) # revealed: A | C
|
||||
@@ -220,6 +222,7 @@ def _(a_b: A | B):
|
||||
reveal_type(f(a_b, D())) # revealed: B | D
|
||||
reveal_type(f(*(a_b, D()))) # revealed: B | D
|
||||
|
||||
|
||||
# But, if it doesn't, it should expand the second argument and try again:
|
||||
def _(a_b: A | B, c_d: C | D):
|
||||
reveal_type(f(a_b, c_d)) # revealed: A | B | C | D
|
||||
@@ -252,6 +255,7 @@ def f(x: B, y: D) -> D: ...
|
||||
```py
|
||||
from overloaded import A, B, C, D, f
|
||||
|
||||
|
||||
def _(a: A, bc: B | C, cd: C | D):
|
||||
# This also tests that partial matching works correctly as the argument type expansion results
|
||||
# in matching the first and second overloads, but not the third one.
|
||||
@@ -285,6 +289,7 @@ def f(x: _T) -> _T: ...
|
||||
```py
|
||||
from overloaded import A, f
|
||||
|
||||
|
||||
def _(x: int, y: A | int):
|
||||
reveal_type(f(x)) # revealed: int
|
||||
reveal_type(f(*(x,))) # revealed: int
|
||||
@@ -317,6 +322,7 @@ def f[T](x: T) -> T: ...
|
||||
```py
|
||||
from overloaded import B, f
|
||||
|
||||
|
||||
def _(x: int, y: B | int):
|
||||
reveal_type(f(x)) # revealed: int
|
||||
reveal_type(f(*(x,))) # revealed: int
|
||||
@@ -344,6 +350,7 @@ def f(x: Literal[False]) -> F: ...
|
||||
```py
|
||||
from overloaded import f
|
||||
|
||||
|
||||
def _(flag: bool):
|
||||
reveal_type(f(True)) # revealed: T
|
||||
reveal_type(f(*(True,))) # revealed: T
|
||||
@@ -380,6 +387,7 @@ def f(x: tuple[B, int], y: tuple[int, Literal[False]]) -> D: ...
|
||||
```py
|
||||
from overloaded import A, B, f
|
||||
|
||||
|
||||
def _(x: tuple[A | B, int], y: tuple[int, bool]):
|
||||
reveal_type(f(x, y)) # revealed: A | B | C | D
|
||||
reveal_type(f(*(x, y))) # revealed: A | B | C | D
|
||||
@@ -407,6 +415,7 @@ def f(x: type[B]) -> B: ...
|
||||
```py
|
||||
from overloaded import A, B, f
|
||||
|
||||
|
||||
def _(x: type[A | B]):
|
||||
reveal_type(x) # revealed: type[A] | type[B]
|
||||
reveal_type(f(x)) # revealed: A | B
|
||||
@@ -445,6 +454,7 @@ def f(x: Literal[SomeEnum.C]) -> C: ...
|
||||
from typing import Literal
|
||||
from overloaded import SomeEnum, A, B, C, f
|
||||
|
||||
|
||||
def _(x: SomeEnum, y: Literal[SomeEnum.A, SomeEnum.C]):
|
||||
reveal_type(f(SomeEnum.A)) # revealed: A
|
||||
reveal_type(f(*(SomeEnum.A,))) # revealed: A
|
||||
@@ -498,6 +508,7 @@ reveal_type(f(b=0)) # revealed: OnlyBSpecified
|
||||
|
||||
f(a=0, b=0) # error: [no-matching-overload]
|
||||
|
||||
|
||||
def _(missing: Literal[Missing.Value], missing_or_present: Literal[Missing.Value] | int):
|
||||
reveal_type(f(a=missing, b=missing)) # revealed: BothMissing
|
||||
reveal_type(f(a=missing)) # revealed: BothMissing
|
||||
@@ -546,6 +557,7 @@ def f(x: MyEnumSubclass) -> MyEnumSubclass: ...
|
||||
```py
|
||||
from overloaded import MyEnumSubclass, ActualEnum, f
|
||||
|
||||
|
||||
def _(actual_enum: ActualEnum, my_enum_instance: MyEnumSubclass):
|
||||
reveal_type(f(actual_enum)) # revealed: Both
|
||||
reveal_type(f(*(actual_enum,))) # revealed: Both
|
||||
@@ -584,6 +596,7 @@ def f(x: B) -> B: ...
|
||||
```py
|
||||
from overloaded import A, B, C, D, f
|
||||
|
||||
|
||||
def _(ab: A | B, ac: A | C, cd: C | D):
|
||||
reveal_type(f(ab)) # revealed: A | B
|
||||
reveal_type(f(*(ab,))) # revealed: A | B
|
||||
@@ -644,6 +657,7 @@ class Foo:
|
||||
from overloaded import A, B, C, Foo, f
|
||||
from typing_extensions import Any, reveal_type
|
||||
|
||||
|
||||
def _(ab: A | B, a: int | Any):
|
||||
reveal_type(f(a1=a, a2=a, a3=a)) # revealed: C
|
||||
reveal_type(f(A(), a1=a, a2=a, a3=a)) # revealed: A
|
||||
@@ -732,6 +746,7 @@ def _(ab: A | B, a: int | Any):
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def _(foo: Foo, ab: A | B, a: int | Any):
|
||||
reveal_type(foo.f(a1=a, a2=a, a3=a)) # revealed: C
|
||||
reveal_type(foo.f(A(), a1=a, a2=a, a3=a)) # revealed: A
|
||||
@@ -843,6 +858,7 @@ def f(x: B, /, **kwargs: int) -> B: ...
|
||||
from overloaded import A, B, f
|
||||
from typing_extensions import reveal_type
|
||||
|
||||
|
||||
def _(a: int | None):
|
||||
reveal_type(
|
||||
# error: [no-matching-overload]
|
||||
@@ -906,17 +922,20 @@ from overloaded import f
|
||||
|
||||
# Test all of the above with a number of different splatted argument types
|
||||
|
||||
|
||||
def _(t: tuple[int, str]) -> None:
|
||||
# This correctly produces an error because the first element of the union has a precise arity of
|
||||
# 2, which matches the first overload, but the second element of the tuple doesn't match the
|
||||
# second parameter type, yielding an `invalid-argument-type` error.
|
||||
f(*t) # error: [invalid-argument-type]
|
||||
|
||||
|
||||
def _(t: tuple[int, str, int]) -> None:
|
||||
# This correctly produces no error because the first element of the union has a precise arity of
|
||||
# 3, which matches the second overload.
|
||||
f(*t)
|
||||
|
||||
|
||||
def _(t: tuple[int, str] | tuple[int, str, int]) -> None:
|
||||
# This produces an error because the expansion produces two argument lists: `[*tuple[int, str]]`
|
||||
# and `[*tuple[int, str, int]]`. The first list produces produces a type checking error as
|
||||
@@ -954,6 +973,7 @@ def f(*args: int) -> int: ...
|
||||
```py
|
||||
from overloaded import f
|
||||
|
||||
|
||||
def _(x1: int, x2: int, args: list[int]):
|
||||
reveal_type(f(x1)) # revealed: tuple[int]
|
||||
reveal_type(f(x1, x2)) # revealed: tuple[int, int]
|
||||
@@ -986,6 +1006,7 @@ def f(x1: int, *args: int) -> tuple[int, ...]: ...
|
||||
```py
|
||||
from overloaded import f
|
||||
|
||||
|
||||
def _(x1: int, x2: int, args1: list[int], args2: tuple[int, *tuple[int, ...]]):
|
||||
reveal_type(f(x1, x2)) # revealed: tuple[int, int]
|
||||
reveal_type(f(*(x1, x2))) # revealed: tuple[int, int]
|
||||
@@ -1013,6 +1034,7 @@ def f(**kwargs: int) -> int: ...
|
||||
```py
|
||||
from overloaded import f
|
||||
|
||||
|
||||
def _(x1: int, x2: int, kwargs: dict[str, int]):
|
||||
reveal_type(f(x1=x1)) # revealed: int
|
||||
reveal_type(f(x1=x1, x2=x2)) # revealed: tuple[int, int]
|
||||
@@ -1044,10 +1066,12 @@ def f(**kwargs: int) -> tuple[int, ...]: ...
|
||||
from typing import TypedDict
|
||||
from overloaded import f
|
||||
|
||||
|
||||
class Foo(TypedDict):
|
||||
x: int
|
||||
y: int
|
||||
|
||||
|
||||
def _(foo: Foo, kwargs: dict[str, int]):
|
||||
reveal_type(f(**foo)) # revealed: tuple[int, int]
|
||||
reveal_type(f(**kwargs)) # revealed: tuple[int, ...]
|
||||
@@ -1089,6 +1113,7 @@ from overloaded import f
|
||||
reveal_type(f(1)) # revealed: str
|
||||
reveal_type(f(*(1,))) # revealed: str
|
||||
|
||||
|
||||
def _(list_int: list[int], list_any: list[Any]):
|
||||
reveal_type(f(list_int)) # revealed: int
|
||||
reveal_type(f(*(list_int,))) # revealed: int
|
||||
@@ -1124,6 +1149,7 @@ from overloaded import f
|
||||
reveal_type(f(1)) # revealed: str
|
||||
reveal_type(f(*(1,))) # revealed: str
|
||||
|
||||
|
||||
def _(list_int: list[int], list_any: list[Any]):
|
||||
# All materializations of `list[int]` are assignable to `list[int]`, so it matches the first
|
||||
# overload.
|
||||
@@ -1166,6 +1192,7 @@ reveal_type(f(*((1, "b"),))) # revealed: int
|
||||
reveal_type(f((1, 2))) # revealed: int
|
||||
reveal_type(f(*((1, 2),))) # revealed: int
|
||||
|
||||
|
||||
def _(int_str: tuple[int, str], int_any: tuple[int, Any], any_any: tuple[Any, Any]):
|
||||
# All materializations are assignable to first overload, so second and third overloads are
|
||||
# eliminated
|
||||
@@ -1206,6 +1233,7 @@ class Foo:
|
||||
from module import Foo
|
||||
from typing_extensions import LiteralString
|
||||
|
||||
|
||||
def f(a: Foo, b: list[str], c: list[LiteralString], e):
|
||||
reveal_type(e) # revealed: Unknown
|
||||
reveal_type(a.join(b)) # revealed: str
|
||||
@@ -1245,6 +1273,7 @@ from typing import Any
|
||||
|
||||
from overloaded import A, f
|
||||
|
||||
|
||||
def _(list_int: list[int], list_any: list[Any], int_str: tuple[int, str], int_any: tuple[int, Any], any_any: tuple[Any, Any]):
|
||||
# All materializations of both argument types are assignable to the first overload, so the
|
||||
# second and third overloads are filtered out
|
||||
@@ -1293,6 +1322,7 @@ from typing_extensions import LiteralString
|
||||
|
||||
from overloaded import f
|
||||
|
||||
|
||||
def _(literal: LiteralString, string: str, any: Any):
|
||||
reveal_type(f(literal)) # revealed: LiteralString
|
||||
reveal_type(f(*(literal,))) # revealed: LiteralString
|
||||
@@ -1331,6 +1361,7 @@ from typing import Any
|
||||
|
||||
from overloaded import f
|
||||
|
||||
|
||||
def _(list_int: list[int], list_str: list[str], list_any: list[Any], any: Any):
|
||||
reveal_type(f(list_int)) # revealed: A
|
||||
reveal_type(f(*(list_int,))) # revealed: A
|
||||
@@ -1365,6 +1396,7 @@ from typing import Any
|
||||
|
||||
from overloaded import f
|
||||
|
||||
|
||||
def _(integer: int, string: str, any: Any, list_any: list[Any]):
|
||||
reveal_type(f(integer, string)) # revealed: int
|
||||
reveal_type(f(*(integer, string))) # revealed: int
|
||||
@@ -1407,11 +1439,13 @@ from typing import Any
|
||||
|
||||
from overloaded import A, B
|
||||
|
||||
|
||||
def _(a_int: A[int], a_str: A[str], a_any: A[Any]):
|
||||
reveal_type(a_int.method()) # revealed: int
|
||||
reveal_type(a_str.method()) # revealed: int
|
||||
reveal_type(a_any.method()) # revealed: int
|
||||
|
||||
|
||||
def _(b_int: B[int], b_str: B[str], b_any: B[Any]):
|
||||
reveal_type(b_int.method()) # revealed: int
|
||||
reveal_type(b_str.method()) # revealed: str
|
||||
@@ -1458,6 +1492,7 @@ from typing import Any
|
||||
|
||||
from overloaded import f1, f2, f3, f4
|
||||
|
||||
|
||||
def _(arg: list[Any]):
|
||||
# Matches both overload and the return types are equivalent
|
||||
reveal_type(f1(*arg)) # revealed: A
|
||||
@@ -1520,10 +1555,12 @@ reveal_type(f1(1)) # revealed: tuple[Literal[1]]
|
||||
reveal_type(f1(1, 2)) # revealed: tuple[Literal[1], Literal[2]]
|
||||
reveal_type(f1(1, 2, 3)) # revealed: tuple[Literal[1], Literal[2], Literal[3]]
|
||||
|
||||
|
||||
def _(args1: list[int], args2: list[Any]):
|
||||
reveal_type(f1(*args1)) # revealed: tuple[Any, ...]
|
||||
reveal_type(f1(*args2)) # revealed: tuple[Any, ...]
|
||||
|
||||
|
||||
reveal_type(f2()) # revealed: tuple[Any, ...]
|
||||
reveal_type(f2(1, 2)) # revealed: tuple[Literal[1], Literal[2]]
|
||||
# TODO: Should be `tuple[Literal[1], Literal[2]]`
|
||||
@@ -1574,6 +1611,7 @@ from typing import Any
|
||||
|
||||
from overloaded import f
|
||||
|
||||
|
||||
def _(any: Any):
|
||||
reveal_type(f(any, flag=True)) # revealed: int
|
||||
reveal_type(f(*(any,), flag=True)) # revealed: int
|
||||
@@ -1602,6 +1640,7 @@ from typing import Any, Literal
|
||||
|
||||
from overloaded import f
|
||||
|
||||
|
||||
def _(any: Any):
|
||||
reveal_type(f(any, flag=True)) # revealed: int
|
||||
reveal_type(f(*(any,), flag=True)) # revealed: int
|
||||
@@ -1609,9 +1648,11 @@ def _(any: Any):
|
||||
reveal_type(f(any, flag=False)) # revealed: str
|
||||
reveal_type(f(*(any,), flag=False)) # revealed: str
|
||||
|
||||
|
||||
def _(args: tuple[Any, Literal[True]]):
|
||||
reveal_type(f(*args)) # revealed: int
|
||||
|
||||
|
||||
def _(args: tuple[Any, Literal[False]]):
|
||||
reveal_type(f(*args)) # revealed: str
|
||||
```
|
||||
@@ -1656,6 +1697,7 @@ from typing import Any
|
||||
|
||||
from overloaded import A, B, f
|
||||
|
||||
|
||||
def _(arg: tuple[A | B, Any]):
|
||||
reveal_type(f(arg)) # revealed: A | B
|
||||
reveal_type(f(*(arg,))) # revealed: A | B
|
||||
@@ -1691,6 +1733,7 @@ from typing import Any
|
||||
|
||||
from overloaded import A, B, C, f
|
||||
|
||||
|
||||
def _(arg: tuple[A | B, Any]):
|
||||
reveal_type(f(arg)) # revealed: A | Unknown
|
||||
reveal_type(f(*(arg,))) # revealed: A | Unknown
|
||||
@@ -1725,6 +1768,7 @@ from typing import Any
|
||||
|
||||
from overloaded import A, B, C, f
|
||||
|
||||
|
||||
def _(arg: tuple[A | B, Any]):
|
||||
reveal_type(f(arg)) # revealed: Unknown
|
||||
reveal_type(f(*(arg,))) # revealed: Unknown
|
||||
@@ -1742,9 +1786,11 @@ Type inference accounts for parameter type annotations across all overloads.
|
||||
```py
|
||||
from typing import TypedDict, overload
|
||||
|
||||
|
||||
class T(TypedDict):
|
||||
x: int
|
||||
|
||||
|
||||
@overload
|
||||
def f(a: list[T], b: int) -> int: ...
|
||||
@overload
|
||||
@@ -1752,9 +1798,11 @@ def f(a: list[dict[str, int]], b: str) -> str: ...
|
||||
def f(a: list[dict[str, int]] | list[T], b: int | str) -> int | str:
|
||||
return 1
|
||||
|
||||
|
||||
def int_or_str() -> int | str:
|
||||
return 1
|
||||
|
||||
|
||||
x = f([{"x": 1}], int_or_str())
|
||||
reveal_type(x) # revealed: int | str
|
||||
|
||||
@@ -1767,9 +1815,11 @@ Non-matching overloads do not produce diagnostics:
|
||||
```py
|
||||
from typing import TypedDict, overload
|
||||
|
||||
|
||||
class T(TypedDict):
|
||||
x: int
|
||||
|
||||
|
||||
@overload
|
||||
def f(a: T, b: int) -> int: ...
|
||||
@overload
|
||||
@@ -1777,6 +1827,7 @@ def f(a: dict[str, int], b: str) -> str: ...
|
||||
def f(a: T | dict[str, int], b: int | str) -> int | str:
|
||||
return 1
|
||||
|
||||
|
||||
x = f({"y": 1}, "a")
|
||||
reveal_type(x) # revealed: str
|
||||
```
|
||||
@@ -1784,11 +1835,13 @@ reveal_type(x) # revealed: str
|
||||
```py
|
||||
from typing import SupportsRound, overload
|
||||
|
||||
|
||||
@overload
|
||||
def takes_str_or_float(x: str): ...
|
||||
@overload
|
||||
def takes_str_or_float(x: float): ...
|
||||
def takes_str_or_float(x: float | str): ...
|
||||
|
||||
|
||||
takes_str_or_float(round(1.0))
|
||||
```
|
||||
|
||||
@@ -31,11 +31,13 @@ Dataclasses support the `__replace__` protocol:
|
||||
from dataclasses import dataclass
|
||||
from copy import replace
|
||||
|
||||
|
||||
@dataclass
|
||||
class Point:
|
||||
x: int
|
||||
y: int
|
||||
|
||||
|
||||
reveal_type(Point.__replace__) # revealed: (self: Point, *, x: int = ..., y: int = ...) -> Point
|
||||
```
|
||||
|
||||
@@ -80,10 +82,12 @@ NamedTuples also support the `__replace__` protocol:
|
||||
from typing import NamedTuple
|
||||
from copy import replace
|
||||
|
||||
|
||||
class Point(NamedTuple):
|
||||
x: int
|
||||
y: int
|
||||
|
||||
|
||||
reveal_type(Point.__replace__) # revealed: (self: Self, *, x: int = ..., y: int = ...) -> Self
|
||||
```
|
||||
|
||||
|
||||
@@ -39,6 +39,7 @@ And similiarly, we should still infer `bool` if the instance or the prefix are n
|
||||
```py
|
||||
from typing_extensions import LiteralString
|
||||
|
||||
|
||||
def _(string_instance: str, literalstring: LiteralString):
|
||||
reveal_type(string_instance.startswith("a")) # revealed: bool
|
||||
reveal_type(literalstring.startswith("a")) # revealed: bool
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
```py
|
||||
class C: ...
|
||||
|
||||
|
||||
def _(subclass_of_c: type[C]):
|
||||
reveal_type(subclass_of_c()) # revealed: C
|
||||
```
|
||||
@@ -17,6 +18,7 @@ def _(subclass_of_c: type[C]):
|
||||
class C:
|
||||
def __init__(self, x: int): ...
|
||||
|
||||
|
||||
def _(subclass_of_c: type[C]):
|
||||
reveal_type(subclass_of_c(1)) # revealed: C
|
||||
|
||||
@@ -34,6 +36,7 @@ def _(subclass_of_c: type[C]):
|
||||
from typing import Any
|
||||
from ty_extensions import Unknown
|
||||
|
||||
|
||||
def _(subclass_of_any: type[Any], subclass_of_unknown: type[Unknown]):
|
||||
reveal_type(subclass_of_any()) # revealed: Any
|
||||
reveal_type(subclass_of_any("any", "args", 1, 2)) # revealed: Any
|
||||
@@ -45,8 +48,11 @@ def _(subclass_of_any: type[Any], subclass_of_unknown: type[Unknown]):
|
||||
|
||||
```py
|
||||
class A: ...
|
||||
|
||||
|
||||
class B: ...
|
||||
|
||||
|
||||
def _(subclass_of_ab: type[A | B]):
|
||||
reveal_type(subclass_of_ab()) # revealed: A | B
|
||||
```
|
||||
|
||||
@@ -17,8 +17,11 @@ from the first argument:
|
||||
|
||||
```py
|
||||
class Base: ...
|
||||
|
||||
|
||||
class Mixin: ...
|
||||
|
||||
|
||||
# We synthesize a class type using the name argument
|
||||
Foo = type("Foo", (), {})
|
||||
reveal_type(Foo) # revealed: <class 'Foo'>
|
||||
@@ -44,9 +47,11 @@ name = "IndirectClass"
|
||||
IndirectClass = type(name, (), {})
|
||||
reveal_type(IndirectClass) # revealed: <class 'IndirectClass'>
|
||||
|
||||
|
||||
# Works with base classes too
|
||||
class Base: ...
|
||||
|
||||
|
||||
base_name = "DerivedClass"
|
||||
DerivedClass = type(base_name, (Base,), {})
|
||||
reveal_type(DerivedClass) # revealed: <class 'DerivedClass'>
|
||||
@@ -59,8 +64,10 @@ Each `type()` call produces a distinct class type, even if they have the same na
|
||||
```py
|
||||
from ty_extensions import static_assert, is_equivalent_to
|
||||
|
||||
|
||||
class Base: ...
|
||||
|
||||
|
||||
Foo1 = type("Foo", (Base,), {})
|
||||
Foo2 = type("Foo", (Base,), {})
|
||||
|
||||
@@ -71,9 +78,11 @@ static_assert(not is_equivalent_to(Foo1, Foo2))
|
||||
foo1 = Foo1()
|
||||
foo2 = Foo2()
|
||||
|
||||
|
||||
def takes_foo1(x: Foo1) -> None: ...
|
||||
def takes_foo2(x: Foo2) -> None: ...
|
||||
|
||||
|
||||
takes_foo1(foo1) # OK
|
||||
takes_foo2(foo2) # OK
|
||||
|
||||
@@ -95,9 +104,11 @@ class Base:
|
||||
def base_method(self) -> str:
|
||||
return "hello"
|
||||
|
||||
|
||||
class Mixin:
|
||||
mixin_attr: str = "mixin"
|
||||
|
||||
|
||||
Foo = type("Foo", (Base,), {})
|
||||
foo = Foo()
|
||||
|
||||
@@ -120,6 +131,7 @@ Attributes from the namespace dict (third argument) are tracked:
|
||||
```py
|
||||
class Base: ...
|
||||
|
||||
|
||||
Foo = type("Foo", (Base,), {"custom_attr": 42})
|
||||
|
||||
# Class attribute access
|
||||
@@ -136,8 +148,10 @@ When the namespace dict is not a literal (e.g., passed as a parameter), attribut
|
||||
```py
|
||||
from typing import Any
|
||||
|
||||
|
||||
class DynamicBase: ...
|
||||
|
||||
|
||||
def f(attributes: dict[str, Any]):
|
||||
X = type("X", (DynamicBase,), attributes)
|
||||
|
||||
@@ -156,6 +170,7 @@ keys), static attributes have precise types while unknown attributes return `Unk
|
||||
```py
|
||||
from typing import Any
|
||||
|
||||
|
||||
def f(extra_attrs: dict[str, Any], y: str):
|
||||
X = type("X", (), {"a": 42, **extra_attrs})
|
||||
|
||||
@@ -177,9 +192,11 @@ arbitrary additional string keys), unknown attributes return `Unknown`:
|
||||
```py
|
||||
from typing import TypedDict
|
||||
|
||||
|
||||
class Namespace(TypedDict):
|
||||
z: int
|
||||
|
||||
|
||||
def g(attributes: Namespace):
|
||||
Y = type("Y", (), attributes)
|
||||
|
||||
@@ -205,10 +222,12 @@ emit an error instead of returning `Unknown`.
|
||||
```py
|
||||
from typing import TypedDict
|
||||
|
||||
|
||||
class ClosedNamespace(TypedDict, closed=True):
|
||||
x: int
|
||||
y: str
|
||||
|
||||
|
||||
def h(ns: ClosedNamespace):
|
||||
X = type("X", (), ns)
|
||||
|
||||
@@ -235,11 +254,14 @@ Regular classes can inherit from dynamic classes:
|
||||
class Base:
|
||||
base_attr: int = 1
|
||||
|
||||
|
||||
DynamicClass = type("DynamicClass", (Base,), {})
|
||||
|
||||
|
||||
class Child(DynamicClass):
|
||||
child_attr: str = "child"
|
||||
|
||||
|
||||
child = Child()
|
||||
|
||||
# Attributes from the dynamic class's base are accessible
|
||||
@@ -248,19 +270,24 @@ reveal_type(child.base_attr) # revealed: int
|
||||
# The child class's own attributes are accessible
|
||||
reveal_type(child.child_attr) # revealed: str
|
||||
|
||||
|
||||
# Child instances are subtypes of DynamicClass instances
|
||||
def takes_dynamic(x: DynamicClass) -> None: ...
|
||||
|
||||
|
||||
takes_dynamic(child) # No error - Child is a subtype of DynamicClass
|
||||
|
||||
|
||||
# isinstance narrows to the dynamic class instance type
|
||||
def check_isinstance(x: object) -> None:
|
||||
if isinstance(x, DynamicClass):
|
||||
reveal_type(x) # revealed: DynamicClass
|
||||
|
||||
|
||||
# Dynamic class inheriting from int narrows correctly with isinstance
|
||||
IntSubclass = type("IntSubclass", (int,), {})
|
||||
|
||||
|
||||
def check_int_subclass(x: IntSubclass | str) -> None:
|
||||
if isinstance(x, int):
|
||||
# IntSubclass inherits from int, so it's included in the narrowed type
|
||||
@@ -277,8 +304,10 @@ from both):
|
||||
```py
|
||||
class Base: ...
|
||||
|
||||
|
||||
Foo = type("Foo", (Base,), {})
|
||||
|
||||
|
||||
def check_disjointness(x: Foo | int) -> None:
|
||||
if isinstance(x, int):
|
||||
reveal_type(x) # revealed: int
|
||||
@@ -299,9 +328,11 @@ StrClass = type("StrClass", (str,), {})
|
||||
static_assert(is_disjoint_from(type[IntClass], type[StrClass]))
|
||||
static_assert(is_disjoint_from(type[StrClass], type[IntClass]))
|
||||
|
||||
|
||||
# Dynamic classes that share a common base are not disjoint.
|
||||
class Base: ...
|
||||
|
||||
|
||||
Foo = type("Foo", (Base,), {})
|
||||
Bar = type("Bar", (Base,), {})
|
||||
|
||||
@@ -317,6 +348,7 @@ class Base:
|
||||
def method(self) -> int:
|
||||
return 42
|
||||
|
||||
|
||||
DynamicChild = type("DynamicChild", (Base,), {})
|
||||
|
||||
# Using dynamic class as pivot with dynamic class instance owner
|
||||
@@ -324,10 +356,12 @@ fc = DynamicChild()
|
||||
reveal_type(super(DynamicChild, fc)) # revealed: <super: <class 'DynamicChild'>, DynamicChild>
|
||||
reveal_type(super(DynamicChild, fc).method()) # revealed: int
|
||||
|
||||
|
||||
# Regular class inheriting from dynamic class
|
||||
class RegularChild(DynamicChild):
|
||||
pass
|
||||
|
||||
|
||||
rc = RegularChild()
|
||||
reveal_type(super(RegularChild, rc)) # revealed: <super: <class 'RegularChild'>, RegularChild>
|
||||
reveal_type(super(RegularChild, rc).method()) # revealed: int
|
||||
@@ -345,6 +379,7 @@ Dynamic classes can inherit from other dynamic classes:
|
||||
class Base:
|
||||
base_attr: int = 1
|
||||
|
||||
|
||||
# Create a dynamic class that inherits from a regular class.
|
||||
Parent = type("Parent", (Base,), {})
|
||||
reveal_type(Parent) # revealed: <class 'Parent'>
|
||||
@@ -358,9 +393,11 @@ child = ChildCls()
|
||||
reveal_type(child) # revealed: ChildCls
|
||||
reveal_type(child.base_attr) # revealed: int
|
||||
|
||||
|
||||
# Child instances are subtypes of `Parent` instances.
|
||||
def takes_parent(x: Parent) -> None: ...
|
||||
|
||||
|
||||
takes_parent(child) # No error - `ChildCls` is a subtype of `Parent`
|
||||
```
|
||||
|
||||
@@ -373,12 +410,14 @@ dataclass-like and have the synthesized `__dataclass_fields__` attribute:
|
||||
from dataclasses import Field
|
||||
from typing_extensions import dataclass_transform
|
||||
|
||||
|
||||
@dataclass_transform()
|
||||
class DataclassBase:
|
||||
"""Base class decorated with @dataclass_transform()."""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
# A dynamic class inheriting from a dataclass_transform base
|
||||
DynamicModel = type("DynamicModel", (DataclassBase,), {})
|
||||
|
||||
@@ -408,9 +447,11 @@ from typing import Generic, TypeVar
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
|
||||
class Container(Generic[T]):
|
||||
value: T
|
||||
|
||||
|
||||
# Dynamic class inheriting from a generic class specialization
|
||||
IntContainer = type("IntContainer", (Container[int],), {})
|
||||
reveal_type(IntContainer) # revealed: <class 'IntContainer'>
|
||||
@@ -427,6 +468,7 @@ reveal_type(container.value) # revealed: int
|
||||
```py
|
||||
class Base: ...
|
||||
|
||||
|
||||
Foo = type("Foo", (Base,), {})
|
||||
foo = Foo()
|
||||
|
||||
@@ -439,6 +481,7 @@ reveal_type(type(foo)) # revealed: type[Foo]
|
||||
```py
|
||||
class Base: ...
|
||||
|
||||
|
||||
Foo = type("Foo", (Base,), {})
|
||||
foo = Foo()
|
||||
|
||||
@@ -451,6 +494,7 @@ reveal_type(foo.__class__) # revealed: type[Foo]
|
||||
```py
|
||||
class StaticClass: ...
|
||||
|
||||
|
||||
DynamicClass = type("DynamicClass", (), {})
|
||||
|
||||
# Both static and dynamic classes have `type` as their metaclass
|
||||
@@ -465,12 +509,15 @@ Dynamic instances are subtypes of `object`:
|
||||
```py
|
||||
class Base: ...
|
||||
|
||||
|
||||
Foo = type("Foo", (Base,), {})
|
||||
foo = Foo()
|
||||
|
||||
|
||||
# All dynamic instances are subtypes of object
|
||||
def takes_object(x: object) -> None: ...
|
||||
|
||||
|
||||
takes_object(foo) # No error - Foo is a subtype of object
|
||||
|
||||
# Even dynamic classes with no explicit bases are subtypes of object
|
||||
@@ -522,6 +569,7 @@ produces slightly different error messages than assigned dynamic class creation:
|
||||
```py
|
||||
class Base: ...
|
||||
|
||||
|
||||
# error: 6 [invalid-argument-type] "Argument to class `type` is incorrect: Expected `str`, found `Literal[b"Foo"]`"
|
||||
type(b"Foo", (), {})
|
||||
|
||||
@@ -544,9 +592,11 @@ diagnostic about the unsupported base, rather than cascading errors:
|
||||
```py
|
||||
from ty_extensions import reveal_mro
|
||||
|
||||
|
||||
class Base:
|
||||
base_attr: int = 1
|
||||
|
||||
|
||||
def f(x: type[Base]):
|
||||
# error: [unsupported-dynamic-base] "Unsupported class base"
|
||||
Child = type("Child", (x,), {})
|
||||
@@ -568,6 +618,7 @@ MRO errors are detected and reported:
|
||||
```py
|
||||
class A: ...
|
||||
|
||||
|
||||
# Duplicate bases are detected
|
||||
# error: [duplicate-base] "Duplicate base class <class 'A'> in class `Dup`"
|
||||
Dup = type("Dup", (A, A), {})
|
||||
@@ -586,14 +637,22 @@ X = type("X", (Bar, Baz), {})
|
||||
|
||||
```py
|
||||
class A: ...
|
||||
|
||||
|
||||
class B(A): ...
|
||||
|
||||
|
||||
class C(A): ...
|
||||
|
||||
|
||||
# This creates an inconsistent MRO because D would need B before C (from first base)
|
||||
# but also C before B (from second base inheritance through A)
|
||||
class X(B, C): ...
|
||||
|
||||
|
||||
class Y(C, B): ...
|
||||
|
||||
|
||||
# error: [inconsistent-mro] "Cannot create a consistent method resolution order (MRO) for class `Conflict` with bases `[<class 'X'>, <class 'Y'>]`"
|
||||
Conflict = type("Conflict", (X, Y), {})
|
||||
```
|
||||
@@ -604,10 +663,17 @@ Metaclass conflicts are detected and reported:
|
||||
|
||||
```py
|
||||
class Meta1(type): ...
|
||||
|
||||
|
||||
class Meta2(type): ...
|
||||
|
||||
|
||||
class A(metaclass=Meta1): ...
|
||||
|
||||
|
||||
class B(metaclass=Meta2): ...
|
||||
|
||||
|
||||
# error: [conflicting-metaclass] "The metaclass of a derived class (`Bad`) must be a subclass of the metaclasses of all its bases, but `Meta1` (metaclass of base class `<class 'A'>`) and `Meta2` (metaclass of base class `<class 'B'>`) have no subclass relationship"
|
||||
Bad = type("Bad", (A, B), {})
|
||||
```
|
||||
@@ -640,8 +706,10 @@ Dynamic classes with non-empty `__slots__` cannot coexist with other disjoint ba
|
||||
class RegularSlotted:
|
||||
__slots__ = ("a",)
|
||||
|
||||
|
||||
DynSlotted = type("DynSlotted", (), {"__slots__": ("b",)})
|
||||
|
||||
|
||||
# error: [instance-layout-conflict]
|
||||
class Conflict(
|
||||
RegularSlotted,
|
||||
@@ -655,6 +723,7 @@ Two dynamic classes with non-empty `__slots__` also conflict:
|
||||
A = type("A", (), {"__slots__": ("x",)})
|
||||
B = type("B", (), {"__slots__": ("y",)})
|
||||
|
||||
|
||||
# error: [instance-layout-conflict]
|
||||
class Conflict(
|
||||
A,
|
||||
@@ -668,12 +737,15 @@ with disjoint bases:
|
||||
```py
|
||||
from typing import Any
|
||||
|
||||
|
||||
class DisjointBase1:
|
||||
__slots__ = ("a",)
|
||||
|
||||
|
||||
class DisjointBase2:
|
||||
__slots__ = ("b",)
|
||||
|
||||
|
||||
def f(ns: dict[str, Any]):
|
||||
cls1 = type("cls1", (DisjointBase1,), ns)
|
||||
cls2 = type("cls2", (DisjointBase2,), ns)
|
||||
@@ -691,9 +763,11 @@ defined, so no diagnostic is emitted:
|
||||
```py
|
||||
from typing import Any
|
||||
|
||||
|
||||
class SlottedBase:
|
||||
__slots__ = ("a",)
|
||||
|
||||
|
||||
def f(ns: dict[str, Any]):
|
||||
# The namespace might or might not contain __slots__, so no error is emitted
|
||||
Dynamic = type("Dynamic", (), ns)
|
||||
@@ -712,9 +786,11 @@ When the bases are a tuple literal, the diagnostic includes annotations for each
|
||||
class A:
|
||||
__slots__ = ("x",)
|
||||
|
||||
|
||||
class B:
|
||||
__slots__ = ("y",)
|
||||
|
||||
|
||||
# error: [instance-layout-conflict]
|
||||
X = type("X", (A, B), {})
|
||||
```
|
||||
@@ -726,9 +802,11 @@ per-base annotations:
|
||||
class C:
|
||||
__slots__ = ("x",)
|
||||
|
||||
|
||||
class D:
|
||||
__slots__ = ("y",)
|
||||
|
||||
|
||||
bases: tuple[type[C], type[D]] = (C, D)
|
||||
# error: [instance-layout-conflict]
|
||||
Y = type("Y", bases, {})
|
||||
@@ -756,6 +834,7 @@ def make_class(name: str):
|
||||
reveal_type(cls) # revealed: <class '<unknown>'>
|
||||
return cls
|
||||
|
||||
|
||||
def make_classes(name1: str, name2: str):
|
||||
cls1 = type(name1, (), {})
|
||||
cls2 = type(name2, (), {})
|
||||
@@ -788,9 +867,13 @@ any attribute access returns `Unknown`:
|
||||
```py
|
||||
from ty_extensions import reveal_mro
|
||||
|
||||
|
||||
class Base1: ...
|
||||
|
||||
|
||||
class Base2: ...
|
||||
|
||||
|
||||
def make_class(bases: tuple[type, ...]):
|
||||
# Class literal is created with Unknown base in MRO
|
||||
cls = type("Cls", bases, {})
|
||||
@@ -813,6 +896,7 @@ classes:
|
||||
class Base:
|
||||
attr: int = 1
|
||||
|
||||
|
||||
bases = (Base,)
|
||||
Cls = type("Cls", bases, {})
|
||||
reveal_type(Cls) # revealed: <class 'Cls'>
|
||||
@@ -828,8 +912,10 @@ Unpacking arguments with `*args` or `**kwargs`:
|
||||
```py
|
||||
from ty_extensions import reveal_mro
|
||||
|
||||
|
||||
class Base: ...
|
||||
|
||||
|
||||
# Unpacking a tuple for bases
|
||||
bases_tuple = (Base,)
|
||||
Cls1 = type("Cls1", (*bases_tuple,), {})
|
||||
@@ -883,6 +969,7 @@ This will be fixed when we support all `type()` calls (including inline) via gen
|
||||
```py
|
||||
class Base: ...
|
||||
|
||||
|
||||
# TODO: Should infer `<class 'T'>` instead of `type`
|
||||
T: type = type("T", (), {})
|
||||
reveal_type(T) # revealed: type
|
||||
@@ -905,9 +992,11 @@ synthesized:
|
||||
from typing import Protocol
|
||||
from ty_extensions import reveal_mro
|
||||
|
||||
|
||||
class MyProtocol(Protocol):
|
||||
def method(self) -> int: ...
|
||||
|
||||
|
||||
ProtoImpl = type("ProtoImpl", (MyProtocol,), {})
|
||||
reveal_type(ProtoImpl) # revealed: <class 'ProtoImpl'>
|
||||
reveal_mro(ProtoImpl) # revealed: (<class 'ProtoImpl'>, <class 'MyProtocol'>, typing.Protocol, typing.Generic, <class 'object'>)
|
||||
@@ -923,10 +1012,12 @@ reveal_type(instance) # revealed: ProtoImpl
|
||||
from typing_extensions import TypedDict
|
||||
from ty_extensions import reveal_mro
|
||||
|
||||
|
||||
class MyDict(TypedDict):
|
||||
name: str
|
||||
age: int
|
||||
|
||||
|
||||
DictSubclass = type("DictSubclass", (MyDict,), {})
|
||||
reveal_type(DictSubclass) # revealed: <class 'DictSubclass'>
|
||||
reveal_mro(DictSubclass) # revealed: (<class 'DictSubclass'>, <class 'MyDict'>, typing.TypedDict, <class 'object'>)
|
||||
@@ -939,10 +1030,12 @@ reveal_mro(DictSubclass) # revealed: (<class 'DictSubclass'>, <class 'MyDict'>,
|
||||
from typing import NamedTuple
|
||||
from ty_extensions import reveal_mro
|
||||
|
||||
|
||||
class Point(NamedTuple):
|
||||
x: int
|
||||
y: int
|
||||
|
||||
|
||||
Point3D = type("Point3D", (Point,), {})
|
||||
reveal_type(Point3D) # revealed: <class 'Point3D'>
|
||||
# fmt: off
|
||||
@@ -957,13 +1050,16 @@ reveal_mro(Point3D) # revealed: (<class 'Point3D'>, <class 'Point'>, <class 'tu
|
||||
# that type() cannot provide. This applies even to empty enums.
|
||||
from enum import Enum
|
||||
|
||||
|
||||
class Color(Enum):
|
||||
RED = 1
|
||||
GREEN = 2
|
||||
|
||||
|
||||
class EmptyEnum(Enum):
|
||||
pass
|
||||
|
||||
|
||||
# TODO: We should emit a diagnostic here - type() cannot create Enum subclasses
|
||||
ExtendedColor = type("ExtendedColor", (Color,), {})
|
||||
reveal_type(ExtendedColor) # revealed: <class 'ExtendedColor'>
|
||||
@@ -985,10 +1081,12 @@ class Base:
|
||||
super().__init_subclass__(**kwargs)
|
||||
cls.config = required_arg
|
||||
|
||||
|
||||
# Regular class definition - this works and passes the argument
|
||||
class Child(Base, required_arg="value"):
|
||||
pass
|
||||
|
||||
|
||||
# The dynamically assigned attribute has Unknown in its type
|
||||
reveal_type(Child.config) # revealed: Unknown | str
|
||||
|
||||
@@ -1025,8 +1123,10 @@ When a base class has a custom metaclass, the dynamic class inherits that metacl
|
||||
class MyMeta(type):
|
||||
custom_attr: str = "meta"
|
||||
|
||||
|
||||
class Base(metaclass=MyMeta): ...
|
||||
|
||||
|
||||
# Dynamic class inherits the metaclass from Base
|
||||
Dynamic = type("Dynamic", (Base,), {})
|
||||
reveal_type(Dynamic) # revealed: <class 'Dynamic'>
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -28,18 +28,25 @@ python-version = "3.12"
|
||||
from __future__ import annotations
|
||||
from ty_extensions import reveal_mro
|
||||
|
||||
|
||||
class A:
|
||||
def a(self): ...
|
||||
|
||||
aa: int = 1
|
||||
|
||||
|
||||
class B(A):
|
||||
def b(self): ...
|
||||
|
||||
bb: int = 2
|
||||
|
||||
|
||||
class C(B):
|
||||
def c(self): ...
|
||||
|
||||
cc: int = 3
|
||||
|
||||
|
||||
reveal_mro(C) # revealed: (<class 'C'>, <class 'B'>, <class 'A'>, <class 'object'>)
|
||||
|
||||
super(C, C()).a
|
||||
@@ -68,19 +75,24 @@ synthesized `Protocol`s that cannot be upcast to, or interpreted as, a non-`obje
|
||||
import types
|
||||
from typing_extensions import Callable, TypeIs, Literal, NewType, TypedDict
|
||||
|
||||
|
||||
def f(): ...
|
||||
|
||||
|
||||
class Foo[T]:
|
||||
def method(self): ...
|
||||
@property
|
||||
def some_property(self): ...
|
||||
|
||||
|
||||
type Alias = int
|
||||
|
||||
|
||||
class SomeTypedDict(TypedDict):
|
||||
x: int
|
||||
y: bytes
|
||||
|
||||
|
||||
N = NewType("N", int)
|
||||
|
||||
# revealed: <super: <class 'object'>, FunctionType>
|
||||
@@ -100,9 +112,11 @@ reveal_type(super(object, Foo.some_property))
|
||||
# revealed: <super: <class 'object'>, int>
|
||||
reveal_type(super(object, N(42)))
|
||||
|
||||
|
||||
def g(x: object) -> TypeIs[list[object]]:
|
||||
return isinstance(x, list)
|
||||
|
||||
|
||||
def _(x: object, y: SomeTypedDict, z: Callable[[int, str], bool]):
|
||||
if hasattr(x, "bar"):
|
||||
# revealed: <Protocol with members 'bar'>
|
||||
@@ -124,6 +138,7 @@ def _(x: object, y: SomeTypedDict, z: Callable[[int, str], bool]):
|
||||
# revealed: <super: <class 'object'>, dict[Literal["x", "y"], int | bytes]>
|
||||
reveal_type(super(object, y))
|
||||
|
||||
|
||||
# The first argument to `super()` must be an actual class object;
|
||||
# instances of `GenericAlias` are not accepted at runtime:
|
||||
#
|
||||
@@ -139,6 +154,7 @@ class Super:
|
||||
def method(self) -> int:
|
||||
return 42
|
||||
|
||||
|
||||
class Sub(Super):
|
||||
def method(self: Sub) -> int:
|
||||
# revealed: <super: <class 'Sub'>, Sub>
|
||||
@@ -161,11 +177,13 @@ python-version = "3.12"
|
||||
```py
|
||||
from __future__ import annotations
|
||||
|
||||
|
||||
class A:
|
||||
def __init__(self, a: int): ...
|
||||
@classmethod
|
||||
def f(cls): ...
|
||||
|
||||
|
||||
class B(A):
|
||||
def __init__(self, a: int):
|
||||
reveal_type(super()) # revealed: <super: <class 'B'>, Self@__init__>
|
||||
@@ -177,6 +195,7 @@ class B(A):
|
||||
reveal_type(super()) # revealed: <super: <class 'B'>, type[Self@f]>
|
||||
super().f()
|
||||
|
||||
|
||||
super(B, B(42)).__init__(42)
|
||||
super(B, B).f()
|
||||
```
|
||||
@@ -188,6 +207,7 @@ import enum
|
||||
from typing import Any, Self, Never, Protocol, Callable
|
||||
from ty_extensions import Intersection
|
||||
|
||||
|
||||
class BuilderMeta(type):
|
||||
def __new__(
|
||||
cls: type[Any],
|
||||
@@ -200,6 +220,7 @@ class BuilderMeta(type):
|
||||
# revealed: Any
|
||||
return reveal_type(s.__new__(cls, name, bases, dct))
|
||||
|
||||
|
||||
class BuilderMeta2(type):
|
||||
def __new__(
|
||||
cls: type[BuilderMeta2],
|
||||
@@ -211,6 +232,7 @@ class BuilderMeta2(type):
|
||||
s = reveal_type(super())
|
||||
return reveal_type(s.__new__(cls, name, bases, dct)) # revealed: BuilderMeta2
|
||||
|
||||
|
||||
class Foo[T]:
|
||||
x: T
|
||||
|
||||
@@ -265,12 +287,14 @@ class Foo[T]:
|
||||
# revealed: Unknown
|
||||
reveal_type(super())
|
||||
return self
|
||||
|
||||
# TypeVar bounded by `type[Foo]` rather than `Foo`
|
||||
# TODO: Should error on signature - `self` is annotated as a class type, not an instance type
|
||||
def method11[S: type[Foo[int]]](self: S, other: S) -> S:
|
||||
# Delegates to the bound to resolve the super type
|
||||
reveal_type(super()) # revealed: <super: <class 'Foo'>, <class 'Foo[int]'>>
|
||||
return self
|
||||
|
||||
# TypeVar bounded by `type[Foo]`, used in `type[T]` position
|
||||
# TODO: Should error on signature - `cls` would be `type[type[Foo[int]]]`, a metaclass
|
||||
# Delegates to `type[Unknown]` since `type[type[Foo[int]]]` can't be constructed
|
||||
@@ -279,8 +303,10 @@ class Foo[T]:
|
||||
reveal_type(super()) # revealed: <super: <class 'Foo'>, Unknown>
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
type Alias = Bar
|
||||
|
||||
|
||||
class Bar:
|
||||
def method(self: Alias):
|
||||
# revealed: <super: <class 'Bar'>, Bar>
|
||||
@@ -294,11 +320,13 @@ class Bar:
|
||||
# revealed: <super: <class 'Bar'>, Bar>
|
||||
reveal_type(super())
|
||||
|
||||
|
||||
class P(Protocol):
|
||||
def method(self: P):
|
||||
# revealed: <super: <class 'P'>, P>
|
||||
reveal_type(super())
|
||||
|
||||
|
||||
class E(enum.Enum):
|
||||
X = 1
|
||||
|
||||
@@ -318,8 +346,10 @@ a plain `super` instance and does not support name lookup via the MRO.
|
||||
class A:
|
||||
a: int = 42
|
||||
|
||||
|
||||
class B(A): ...
|
||||
|
||||
|
||||
reveal_type(super(B)) # revealed: super
|
||||
|
||||
# error: [unresolved-attribute] "Object of type `super` has no attribute `a`"
|
||||
@@ -335,8 +365,10 @@ successfully.
|
||||
class A:
|
||||
a: int = 3
|
||||
|
||||
|
||||
class B(A): ...
|
||||
|
||||
|
||||
reveal_type(super(B, B()).a) # revealed: int
|
||||
# error: [invalid-assignment] "Cannot assign to attribute `a` on type `<super: <class 'B'>, B>`"
|
||||
super(B, B()).a = 3
|
||||
@@ -353,6 +385,7 @@ member, it should effectively behave like a dynamic type.
|
||||
class A:
|
||||
a: int = 1
|
||||
|
||||
|
||||
def f(x):
|
||||
reveal_type(x) # revealed: Unknown
|
||||
|
||||
@@ -370,6 +403,7 @@ def f(x):
|
||||
```py
|
||||
from __future__ import annotations
|
||||
|
||||
|
||||
class A:
|
||||
def test(self):
|
||||
reveal_type(super()) # revealed: <super: <class 'A'>, Self@test>
|
||||
@@ -384,6 +418,7 @@ class A:
|
||||
|
||||
def inner(t: C):
|
||||
reveal_type(super()) # revealed: <super: <class 'B'>, C>
|
||||
|
||||
lambda x: reveal_type(super()) # revealed: <super: <class 'B'>, Unknown>
|
||||
```
|
||||
|
||||
@@ -399,9 +434,11 @@ reveal_type(super(int, 3)) # revealed: <super: <class 'int'>, int>
|
||||
reveal_type(super(str, "")) # revealed: <super: <class 'str'>, str>
|
||||
reveal_type(super(bytes, b"")) # revealed: <super: <class 'bytes'>, bytes>
|
||||
|
||||
|
||||
class E(Enum):
|
||||
X = 42
|
||||
|
||||
|
||||
reveal_type(super(E, E.X)) # revealed: <super: <class 'E'>, E>
|
||||
```
|
||||
|
||||
@@ -424,8 +461,10 @@ class A:
|
||||
@classmethod
|
||||
def a2(cls): ...
|
||||
|
||||
|
||||
class B(A): ...
|
||||
|
||||
|
||||
# A.__dict__["a1"].__get__(B(), B)
|
||||
reveal_type(super(B, B()).a1) # revealed: bound method B.a1() -> Unknown
|
||||
# A.__dict__["a2"].__get__(B(), B)
|
||||
@@ -445,14 +484,20 @@ super objects are combined into a union.
|
||||
```py
|
||||
from ty_extensions import reveal_mro
|
||||
|
||||
|
||||
class A: ...
|
||||
|
||||
|
||||
class B:
|
||||
b: int = 42
|
||||
|
||||
|
||||
class C(A, B): ...
|
||||
|
||||
|
||||
class D(B, A): ...
|
||||
|
||||
|
||||
def f(x: C | D):
|
||||
reveal_mro(C) # revealed: (<class 'C'>, <class 'A'>, <class 'B'>, <class 'object'>)
|
||||
reveal_mro(D) # revealed: (<class 'D'>, <class 'B'>, <class 'A'>, <class 'object'>)
|
||||
@@ -463,11 +508,13 @@ def f(x: C | D):
|
||||
# error: [possibly-missing-attribute] "Attribute `b` may be missing on object of type `<super: <class 'A'>, C> | <super: <class 'A'>, D>`"
|
||||
s.b
|
||||
|
||||
|
||||
def f(flag: bool):
|
||||
x = str() if flag else str("hello")
|
||||
reveal_type(x) # revealed: Literal["", "hello"]
|
||||
reveal_type(super(str, x)) # revealed: <super: <class 'str'>, str>
|
||||
|
||||
|
||||
def f(x: int | str):
|
||||
# error: [invalid-super-argument] "`str` is not an instance or subclass of `<class 'int'>` in `super(<class 'int'>, str)` call"
|
||||
super(int, x)
|
||||
@@ -479,6 +526,7 @@ in all cases.
|
||||
```py
|
||||
def f(flag: bool):
|
||||
if flag:
|
||||
|
||||
class A:
|
||||
x = 1
|
||||
y: int = 1
|
||||
@@ -486,13 +534,16 @@ def f(flag: bool):
|
||||
a: str = "hello"
|
||||
|
||||
class B(A): ...
|
||||
|
||||
s = super(B, B())
|
||||
else:
|
||||
|
||||
class C:
|
||||
x = 2
|
||||
y: int | str = "test"
|
||||
|
||||
class D(C): ...
|
||||
|
||||
s = super(D, D())
|
||||
|
||||
reveal_type(s) # revealed: <super: <class 'B'>, B> | <super: <class 'D'>, D>
|
||||
@@ -514,10 +565,12 @@ python-version = "3.12"
|
||||
```py
|
||||
from ty_extensions import TypeOf, static_assert, is_subtype_of
|
||||
|
||||
|
||||
class A[T]:
|
||||
def f(self, a: T) -> T:
|
||||
return a
|
||||
|
||||
|
||||
class B[T](A[T]):
|
||||
def f(self, a: T) -> T:
|
||||
return super().f(a)
|
||||
@@ -535,10 +588,12 @@ from __future__ import annotations
|
||||
# error: [unavailable-implicit-super-arguments] "Cannot determine implicit arguments for 'super()' in this context"
|
||||
reveal_type(super()) # revealed: Unknown
|
||||
|
||||
|
||||
def f():
|
||||
# error: [unavailable-implicit-super-arguments] "Cannot determine implicit arguments for 'super()' in this context"
|
||||
super()
|
||||
|
||||
|
||||
# No first argument in its scope
|
||||
class A:
|
||||
# error: [unavailable-implicit-super-arguments] "Cannot determine implicit arguments for 'super()' in this context"
|
||||
@@ -548,6 +603,7 @@ class A:
|
||||
def g():
|
||||
# error: [unavailable-implicit-super-arguments] "Cannot determine implicit arguments for 'super()' in this context"
|
||||
super()
|
||||
|
||||
# error: [unavailable-implicit-super-arguments] "Cannot determine implicit arguments for 'super()' in this context"
|
||||
lambda: super()
|
||||
|
||||
@@ -575,6 +631,7 @@ runtime.
|
||||
import typing
|
||||
import collections
|
||||
|
||||
|
||||
def f(x: int):
|
||||
# error: [invalid-super-argument] "`int` is not a valid class"
|
||||
super(x, x)
|
||||
@@ -583,6 +640,7 @@ def f(x: int):
|
||||
# error: [invalid-super-argument] "`TypeAliasType` is not a valid class"
|
||||
super(IntAlias, 0)
|
||||
|
||||
|
||||
# error: [invalid-super-argument] "`str` is not an instance or subclass of `<class 'int'>` in `super(<class 'int'>, str)` call"
|
||||
# revealed: Unknown
|
||||
reveal_type(super(int, str()))
|
||||
@@ -591,9 +649,13 @@ reveal_type(super(int, str()))
|
||||
# revealed: Unknown
|
||||
reveal_type(super(int, str))
|
||||
|
||||
|
||||
class A: ...
|
||||
|
||||
|
||||
class B(A): ...
|
||||
|
||||
|
||||
# error: [invalid-super-argument] "`A` is not an instance or subclass of `<class 'B'>` in `super(<class 'B'>, A)` call"
|
||||
# revealed: Unknown
|
||||
reveal_type(super(B, A()))
|
||||
@@ -624,6 +686,7 @@ reveal_type(super(typing.ChainMap, collections.ChainMap())) # revealed: Unknown
|
||||
# revealed: <super: <special-form 'typing.Generic'>, <class 'SupportsInt'>>
|
||||
reveal_type(super(typing.Generic, typing.SupportsInt))
|
||||
|
||||
|
||||
def _(x: type[typing.Any], y: typing.Any):
|
||||
reveal_type(super(x, y)) # revealed: <super: Any, Any>
|
||||
```
|
||||
@@ -636,11 +699,16 @@ def _(x: type[typing.Any], y: typing.Any):
|
||||
def coinflip() -> bool:
|
||||
return False
|
||||
|
||||
|
||||
def f():
|
||||
if coinflip():
|
||||
|
||||
class A: ...
|
||||
|
||||
else:
|
||||
|
||||
class A: ...
|
||||
|
||||
super(A, A()) # error: [invalid-super-argument]
|
||||
```
|
||||
|
||||
@@ -651,16 +719,19 @@ Accessing instance members through `super()` is not allowed.
|
||||
```py
|
||||
from __future__ import annotations
|
||||
|
||||
|
||||
class A:
|
||||
def __init__(self, a: int):
|
||||
self.a = a
|
||||
|
||||
|
||||
class B(A):
|
||||
def __init__(self, a: int):
|
||||
super().__init__(a)
|
||||
# error: [unresolved-attribute] "Object of type `<super: <class 'B'>, Self@__init__>` has no attribute `a`"
|
||||
super().a
|
||||
|
||||
|
||||
# error: [unresolved-attribute] "Object of type `<super: <class 'B'>, B>` has no attribute `a`"
|
||||
super(B, B(42)).a
|
||||
```
|
||||
@@ -676,8 +747,10 @@ class A:
|
||||
def __getitem__(self, key: int) -> int:
|
||||
return 42
|
||||
|
||||
|
||||
class B(A): ...
|
||||
|
||||
|
||||
reveal_type(A()[0]) # revealed: int
|
||||
reveal_type(super(B, B()).__getitem__) # revealed: bound method B.__getitem__(key: int) -> int
|
||||
# error: [not-subscriptable] "Cannot subscript object of type `<super: <class 'B'>, B>` with no `__getitem__` method"
|
||||
@@ -700,21 +773,26 @@ from __future__ import annotations
|
||||
from collections.abc import Mapping
|
||||
from typing import Self
|
||||
|
||||
|
||||
class Parent:
|
||||
def __init__(self, children: Mapping[str, Self] | None = None) -> None:
|
||||
self.children = children
|
||||
|
||||
|
||||
class Child(Parent):
|
||||
def __init__(self, children: Mapping[str, Child] | None = None) -> None:
|
||||
# error: [invalid-argument-type] "Argument to bound method `__init__` is incorrect: Expected `Mapping[str, Self@__init__] | None`, found `Mapping[str, Child] | None`"
|
||||
super().__init__(children)
|
||||
|
||||
|
||||
# The fix is to use `Self` consistently in the subclass:
|
||||
|
||||
|
||||
class Parent2:
|
||||
def __init__(self, children: Mapping[str, Self] | None = None) -> None:
|
||||
self.children = children
|
||||
|
||||
|
||||
class Child2(Parent2):
|
||||
def __init__(self, children: Mapping[str, Self] | None = None) -> None:
|
||||
super().__init__(children) # OK
|
||||
@@ -730,6 +808,7 @@ from typing import Protocol, Generic, TypeVar
|
||||
|
||||
_T_co = TypeVar("_T_co", covariant=True)
|
||||
|
||||
|
||||
class MyProtocol(Protocol, Generic[_T_co]):
|
||||
def __class_getitem__(cls, item):
|
||||
# Accessing parent's __class_getitem__ through super()
|
||||
|
||||
@@ -17,9 +17,13 @@ from ty_extensions import reveal_mro
|
||||
|
||||
A = int
|
||||
|
||||
|
||||
class G[T]: ...
|
||||
|
||||
|
||||
class C(A, G["B"]): ...
|
||||
|
||||
|
||||
A = str
|
||||
B = bytes
|
||||
|
||||
@@ -33,14 +37,22 @@ These are currently not supported, but ideally we would support them in some lim
|
||||
```py
|
||||
from ty_extensions import reveal_mro
|
||||
|
||||
|
||||
class A: ...
|
||||
|
||||
|
||||
class B: ...
|
||||
|
||||
|
||||
class C: ...
|
||||
|
||||
|
||||
bases = (A, B, C)
|
||||
|
||||
|
||||
class Foo(*bases): ...
|
||||
|
||||
|
||||
# revealed: (<class 'Foo'>, @Todo(StarredExpression), <class 'object'>)
|
||||
reveal_mro(Foo)
|
||||
```
|
||||
|
||||
@@ -3,10 +3,12 @@
|
||||
```py
|
||||
from enum import Enum
|
||||
|
||||
|
||||
class Answer(Enum):
|
||||
NO = 0
|
||||
YES = 1
|
||||
|
||||
|
||||
reveal_type(Answer.NO == Answer.NO) # revealed: Literal[True]
|
||||
reveal_type(Answer.NO == Answer.YES) # revealed: Literal[False]
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
```py
|
||||
class A: ...
|
||||
|
||||
|
||||
def _(a1: A, a2: A, o: object):
|
||||
n1 = None
|
||||
n2 = None
|
||||
|
||||
@@ -19,6 +19,7 @@ class A:
|
||||
def __contains__(self, item: str) -> bool:
|
||||
return True
|
||||
|
||||
|
||||
reveal_type("hello" in A()) # revealed: bool
|
||||
reveal_type("hello" not in A()) # revealed: bool
|
||||
# error: [unsupported-operator] "Operator `in` is not supported between objects of type `Literal[42]` and `A`"
|
||||
@@ -37,10 +38,12 @@ class StringIterator:
|
||||
def __next__(self) -> str:
|
||||
return "foo"
|
||||
|
||||
|
||||
class A:
|
||||
def __iter__(self) -> StringIterator:
|
||||
return StringIterator()
|
||||
|
||||
|
||||
reveal_type("hello" in A()) # revealed: bool
|
||||
reveal_type("hello" not in A()) # revealed: bool
|
||||
reveal_type(42 in A()) # revealed: bool
|
||||
@@ -59,6 +62,7 @@ class A:
|
||||
def __getitem__(self, key: int) -> str:
|
||||
return "foo"
|
||||
|
||||
|
||||
reveal_type("hello" in A()) # revealed: bool
|
||||
reveal_type("hello" not in A()) # revealed: bool
|
||||
reveal_type(42 in A()) # revealed: bool
|
||||
@@ -75,6 +79,7 @@ class A:
|
||||
def __contains__(self, item: str) -> str:
|
||||
return "foo"
|
||||
|
||||
|
||||
reveal_type("hello" in A()) # revealed: bool
|
||||
reveal_type("hello" not in A()) # revealed: bool
|
||||
```
|
||||
@@ -86,14 +91,17 @@ reveal_type("hello" not in A()) # revealed: bool
|
||||
```py
|
||||
from typing import Literal
|
||||
|
||||
|
||||
class AlwaysTrue:
|
||||
def __contains__(self, item: int) -> Literal[1]:
|
||||
return 1
|
||||
|
||||
|
||||
class AlwaysFalse:
|
||||
def __contains__(self, item: int) -> Literal[""]:
|
||||
return ""
|
||||
|
||||
|
||||
reveal_type(42 in AlwaysTrue()) # revealed: Literal[True]
|
||||
reveal_type(42 not in AlwaysTrue()) # revealed: Literal[False]
|
||||
|
||||
@@ -108,13 +116,19 @@ doesn't result in a fallback to `__iter__` or `__getitem__`:
|
||||
|
||||
```py
|
||||
class CheckContains: ...
|
||||
|
||||
|
||||
class CheckIter: ...
|
||||
|
||||
|
||||
class CheckGetItem: ...
|
||||
|
||||
|
||||
class CheckIterIterator:
|
||||
def __next__(self) -> CheckIter:
|
||||
return CheckIter()
|
||||
|
||||
|
||||
class A:
|
||||
def __contains__(self, item: CheckContains) -> bool:
|
||||
return True
|
||||
@@ -125,6 +139,7 @@ class A:
|
||||
def __getitem__(self, key: int) -> CheckGetItem:
|
||||
return CheckGetItem()
|
||||
|
||||
|
||||
reveal_type(CheckContains() in A()) # revealed: bool
|
||||
|
||||
# error: [unsupported-operator] "Operator `in` is not supported between objects of type `CheckIter` and `A`"
|
||||
@@ -132,6 +147,7 @@ reveal_type(CheckIter() in A()) # revealed: bool
|
||||
# error: [unsupported-operator] "Operator `in` is not supported between objects of type `CheckGetItem` and `A`"
|
||||
reveal_type(CheckGetItem() in A()) # revealed: bool
|
||||
|
||||
|
||||
class B:
|
||||
def __iter__(self) -> CheckIterIterator:
|
||||
return CheckIterIterator()
|
||||
@@ -139,6 +155,7 @@ class B:
|
||||
def __getitem__(self, key: int) -> CheckGetItem:
|
||||
return CheckGetItem()
|
||||
|
||||
|
||||
reveal_type(CheckIter() in B()) # revealed: bool
|
||||
# Always use `__iter__`, regardless of iterated type; there's no NotImplemented
|
||||
# in this case, so there's no fallback to `__getitem__`
|
||||
@@ -155,6 +172,7 @@ class A:
|
||||
def __getitem__(self, key: str) -> str:
|
||||
return "foo"
|
||||
|
||||
|
||||
# error: [unsupported-operator] "Operator `in` is not supported between objects of type `Literal[42]` and `A`"
|
||||
reveal_type(42 in A()) # revealed: bool
|
||||
# error: [unsupported-operator] "Operator `in` is not supported between objects of type `Literal["hello"]` and `A`"
|
||||
@@ -193,10 +211,12 @@ It may also be more appropriate to use `unsupported-operator` as the error code.
|
||||
class NotBoolable:
|
||||
__bool__: int = 3
|
||||
|
||||
|
||||
class WithContains:
|
||||
def __contains__(self, item) -> NotBoolable:
|
||||
return NotBoolable()
|
||||
|
||||
|
||||
# error: [unsupported-bool-conversion]
|
||||
10 in WithContains()
|
||||
# error: [unsupported-bool-conversion]
|
||||
|
||||
@@ -16,13 +16,25 @@ most common case involves implementing these methods for the same type:
|
||||
```py
|
||||
from __future__ import annotations
|
||||
|
||||
|
||||
class EqReturnType: ...
|
||||
|
||||
|
||||
class NeReturnType: ...
|
||||
|
||||
|
||||
class LtReturnType: ...
|
||||
|
||||
|
||||
class LeReturnType: ...
|
||||
|
||||
|
||||
class GtReturnType: ...
|
||||
|
||||
|
||||
class GeReturnType: ...
|
||||
|
||||
|
||||
class A:
|
||||
def __eq__(self, other: A) -> EqReturnType: # error: [invalid-method-override]
|
||||
return EqReturnType()
|
||||
@@ -42,6 +54,7 @@ class A:
|
||||
def __ge__(self, other: A) -> GeReturnType:
|
||||
return GeReturnType()
|
||||
|
||||
|
||||
reveal_type(A() == A()) # revealed: EqReturnType
|
||||
reveal_type(A() != A()) # revealed: NeReturnType
|
||||
reveal_type(A() < A()) # revealed: LtReturnType
|
||||
@@ -58,13 +71,25 @@ type:
|
||||
```py
|
||||
from __future__ import annotations
|
||||
|
||||
|
||||
class EqReturnType: ...
|
||||
|
||||
|
||||
class NeReturnType: ...
|
||||
|
||||
|
||||
class LtReturnType: ...
|
||||
|
||||
|
||||
class LeReturnType: ...
|
||||
|
||||
|
||||
class GtReturnType: ...
|
||||
|
||||
|
||||
class GeReturnType: ...
|
||||
|
||||
|
||||
class A:
|
||||
def __eq__(self, other: B) -> EqReturnType: # error: [invalid-method-override]
|
||||
return EqReturnType()
|
||||
@@ -84,8 +109,10 @@ class A:
|
||||
def __ge__(self, other: B) -> GeReturnType:
|
||||
return GeReturnType()
|
||||
|
||||
|
||||
class B: ...
|
||||
|
||||
|
||||
reveal_type(A() == B()) # revealed: EqReturnType
|
||||
reveal_type(A() != B()) # revealed: NeReturnType
|
||||
reveal_type(A() < B()) # revealed: LtReturnType
|
||||
@@ -103,13 +130,25 @@ these methods will be ignored here because they require a mismatched operand typ
|
||||
```py
|
||||
from __future__ import annotations
|
||||
|
||||
|
||||
class EqReturnType: ...
|
||||
|
||||
|
||||
class NeReturnType: ...
|
||||
|
||||
|
||||
class LtReturnType: ...
|
||||
|
||||
|
||||
class LeReturnType: ...
|
||||
|
||||
|
||||
class GtReturnType: ...
|
||||
|
||||
|
||||
class GeReturnType: ...
|
||||
|
||||
|
||||
class A:
|
||||
def __eq__(self, other: B) -> EqReturnType: # error: [invalid-method-override]
|
||||
return EqReturnType()
|
||||
@@ -129,8 +168,10 @@ class A:
|
||||
def __ge__(self, other: B) -> GeReturnType:
|
||||
return GeReturnType()
|
||||
|
||||
|
||||
class Unrelated: ...
|
||||
|
||||
|
||||
class B:
|
||||
def __eq__(self, other: Unrelated) -> B: # error: [invalid-method-override]
|
||||
return B()
|
||||
@@ -138,6 +179,7 @@ class B:
|
||||
def __ne__(self, other: Unrelated) -> B: # error: [invalid-method-override]
|
||||
return B()
|
||||
|
||||
|
||||
# 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.
|
||||
@@ -150,6 +192,7 @@ reveal_type(B() <= A()) # revealed: GeReturnType
|
||||
reveal_type(B() > A()) # revealed: LtReturnType
|
||||
reveal_type(B() >= A()) # revealed: LeReturnType
|
||||
|
||||
|
||||
class C:
|
||||
def __gt__(self, other: C) -> EqReturnType:
|
||||
return EqReturnType()
|
||||
@@ -157,6 +200,7 @@ class C:
|
||||
def __ge__(self, other: C) -> NeReturnType:
|
||||
return NeReturnType()
|
||||
|
||||
|
||||
reveal_type(C() < C()) # revealed: EqReturnType
|
||||
reveal_type(C() <= C()) # revealed: NeReturnType
|
||||
```
|
||||
@@ -170,13 +214,25 @@ than `A`.
|
||||
```py
|
||||
from __future__ import annotations
|
||||
|
||||
|
||||
class EqReturnType: ...
|
||||
|
||||
|
||||
class NeReturnType: ...
|
||||
|
||||
|
||||
class LtReturnType: ...
|
||||
|
||||
|
||||
class LeReturnType: ...
|
||||
|
||||
|
||||
class GtReturnType: ...
|
||||
|
||||
|
||||
class GeReturnType: ...
|
||||
|
||||
|
||||
class A:
|
||||
def __eq__(self, other: A) -> A: # error: [invalid-method-override]
|
||||
return A()
|
||||
@@ -196,6 +252,7 @@ class A:
|
||||
def __ge__(self, other: A) -> A:
|
||||
return A()
|
||||
|
||||
|
||||
class B(A):
|
||||
def __eq__(self, other: A) -> EqReturnType: # error: [invalid-method-override]
|
||||
return EqReturnType()
|
||||
@@ -215,6 +272,7 @@ class B(A):
|
||||
def __ge__(self, other: A) -> GeReturnType: # error: [invalid-method-override]
|
||||
return GeReturnType()
|
||||
|
||||
|
||||
reveal_type(A() == B()) # revealed: EqReturnType
|
||||
reveal_type(A() != B()) # revealed: NeReturnType
|
||||
|
||||
@@ -233,6 +291,7 @@ method has an mismatched type to operand, the comparison will fall back to the l
|
||||
```py
|
||||
from __future__ import annotations
|
||||
|
||||
|
||||
class A:
|
||||
def __lt__(self, other: A) -> A:
|
||||
return A()
|
||||
@@ -240,6 +299,7 @@ class A:
|
||||
def __gt__(self, other: A) -> A:
|
||||
return A()
|
||||
|
||||
|
||||
class B(A):
|
||||
def __lt__(self, other: int) -> B: # error: [invalid-method-override]
|
||||
return B()
|
||||
@@ -247,6 +307,7 @@ class B(A):
|
||||
def __gt__(self, other: int) -> B: # error: [invalid-method-override]
|
||||
return B()
|
||||
|
||||
|
||||
reveal_type(A() < B()) # revealed: A
|
||||
reveal_type(A() > B()) # revealed: A
|
||||
```
|
||||
@@ -268,12 +329,15 @@ from does_not_exist import Foo # error: [unresolved-import]
|
||||
|
||||
reveal_type(Foo) # revealed: Unknown
|
||||
|
||||
|
||||
class X:
|
||||
def __lt__(self, other: object) -> int:
|
||||
return 42
|
||||
|
||||
|
||||
class Y(Foo): ...
|
||||
|
||||
|
||||
# TODO: Should be `int | Unknown`; see above discussion.
|
||||
reveal_type(X() < Y()) # revealed: int
|
||||
```
|
||||
@@ -288,6 +352,7 @@ Please refer to the [docs](https://docs.python.org/3/reference/datamodel.html#ob
|
||||
```py
|
||||
from __future__ import annotations
|
||||
|
||||
|
||||
class A:
|
||||
def __eq__(self, other: int) -> A: # error: [invalid-method-override]
|
||||
return A()
|
||||
@@ -295,6 +360,7 @@ class A:
|
||||
def __ne__(self, other: int) -> A: # error: [invalid-method-override]
|
||||
return A()
|
||||
|
||||
|
||||
reveal_type(A() == A()) # revealed: bool
|
||||
reveal_type(A() != A()) # revealed: bool
|
||||
```
|
||||
@@ -304,6 +370,7 @@ reveal_type(A() != A()) # revealed: bool
|
||||
```py
|
||||
class A: ...
|
||||
|
||||
|
||||
reveal_type(A() == object()) # revealed: bool
|
||||
reveal_type(A() != object()) # revealed: bool
|
||||
reveal_type(object() == A()) # revealed: bool
|
||||
@@ -336,6 +403,7 @@ reveal_type(1 > 2j) # revealed: Unknown
|
||||
# error: [unsupported-operator] "Operator `>=` is not supported between objects of type `Literal[1]` and `complex`"
|
||||
reveal_type(1 >= 2j) # revealed: Unknown
|
||||
|
||||
|
||||
def f(x: bool, y: int):
|
||||
reveal_type(x < y) # revealed: bool
|
||||
reveal_type(y < x) # revealed: bool
|
||||
@@ -354,6 +422,7 @@ element) of a chained comparison.
|
||||
class NotBoolable:
|
||||
__bool__: int = 3
|
||||
|
||||
|
||||
class Comparable:
|
||||
def __lt__(self, item) -> NotBoolable:
|
||||
return NotBoolable()
|
||||
@@ -361,6 +430,7 @@ class Comparable:
|
||||
def __gt__(self, item) -> NotBoolable:
|
||||
return NotBoolable()
|
||||
|
||||
|
||||
# error: [unsupported-bool-conversion]
|
||||
10 < Comparable() < 20
|
||||
# error: [unsupported-bool-conversion]
|
||||
@@ -374,14 +444,17 @@ Comparable() < Comparable() # fine
|
||||
```py
|
||||
from typing import Literal
|
||||
|
||||
|
||||
class AlwaysTrue:
|
||||
def __call__(self, other: object) -> Literal[True]:
|
||||
return True
|
||||
|
||||
|
||||
class A:
|
||||
__eq__: AlwaysTrue = AlwaysTrue()
|
||||
__lt__: AlwaysTrue = AlwaysTrue()
|
||||
|
||||
|
||||
reveal_type(A() == A()) # revealed: Literal[True]
|
||||
reveal_type(A() < A()) # revealed: Literal[True]
|
||||
reveal_type(A() > A()) # revealed: Literal[True]
|
||||
|
||||
@@ -8,16 +8,20 @@ types, we can infer that the result for the intersection type is also true/false
|
||||
```py
|
||||
from typing import Literal
|
||||
|
||||
|
||||
class Base:
|
||||
def __gt__(self, other) -> bool:
|
||||
return False
|
||||
|
||||
|
||||
class Child1(Base):
|
||||
def __eq__(self, other) -> Literal[True]:
|
||||
return True
|
||||
|
||||
|
||||
class Child2(Base): ...
|
||||
|
||||
|
||||
def _(x: Base):
|
||||
c1 = Child1()
|
||||
|
||||
@@ -95,6 +99,7 @@ def _(x: int):
|
||||
```py
|
||||
class A: ...
|
||||
|
||||
|
||||
def _(o: object):
|
||||
a = A()
|
||||
n = None
|
||||
@@ -116,8 +121,11 @@ intersection type:
|
||||
|
||||
```py
|
||||
class NonContainer1: ...
|
||||
|
||||
|
||||
class NonContainer2: ...
|
||||
|
||||
|
||||
def _(x: object):
|
||||
if isinstance(x, NonContainer1):
|
||||
if isinstance(x, NonContainer2):
|
||||
@@ -135,6 +143,7 @@ class Container:
|
||||
def __contains__(self, x) -> bool:
|
||||
return False
|
||||
|
||||
|
||||
def _(x: object):
|
||||
if isinstance(x, NonContainer1):
|
||||
if isinstance(x, Container):
|
||||
@@ -167,8 +176,10 @@ class Container:
|
||||
def __contains__(self, x) -> bool:
|
||||
return False
|
||||
|
||||
|
||||
class NonContainer: ...
|
||||
|
||||
|
||||
def _(x: object):
|
||||
if isinstance(x, Container):
|
||||
if not isinstance(x, NonContainer):
|
||||
|
||||
@@ -21,6 +21,7 @@ Walking through examples:
|
||||
```py
|
||||
from __future__ import annotations
|
||||
|
||||
|
||||
class A:
|
||||
def __lt__(self, other) -> A:
|
||||
return self
|
||||
@@ -28,14 +29,17 @@ class A:
|
||||
def __gt__(self, other) -> bool:
|
||||
return False
|
||||
|
||||
|
||||
class B:
|
||||
def __lt__(self, other) -> B:
|
||||
return self
|
||||
|
||||
|
||||
class C:
|
||||
def __lt__(self, other) -> C:
|
||||
return self
|
||||
|
||||
|
||||
x = A() < B() < C()
|
||||
reveal_type(x) # revealed: (A & ~AlwaysTruthy) | B
|
||||
|
||||
|
||||
@@ -152,13 +152,25 @@ of the dunder methods.)
|
||||
```py
|
||||
from __future__ import annotations
|
||||
|
||||
|
||||
class EqReturnType: ...
|
||||
|
||||
|
||||
class NeReturnType: ...
|
||||
|
||||
|
||||
class LtReturnType: ...
|
||||
|
||||
|
||||
class LeReturnType: ...
|
||||
|
||||
|
||||
class GtReturnType: ...
|
||||
|
||||
|
||||
class GeReturnType: ...
|
||||
|
||||
|
||||
class A:
|
||||
def __eq__(self, o: object) -> EqReturnType: # error: [invalid-method-override]
|
||||
return EqReturnType()
|
||||
@@ -178,6 +190,7 @@ class A:
|
||||
def __ge__(self, o: A) -> GeReturnType:
|
||||
return GeReturnType()
|
||||
|
||||
|
||||
a = (A(), A())
|
||||
|
||||
reveal_type(a == a) # revealed: bool
|
||||
@@ -198,12 +211,15 @@ reveal_type(b <= c) # revealed: Literal[True]
|
||||
reveal_type(b > c) # revealed: Literal[False]
|
||||
reveal_type(b >= c) # revealed: Literal[False]
|
||||
|
||||
|
||||
class LtReturnTypeOnB: ...
|
||||
|
||||
|
||||
class B:
|
||||
def __lt__(self, o: B) -> LtReturnTypeOnB:
|
||||
return LtReturnTypeOnB()
|
||||
|
||||
|
||||
reveal_type((A(), B()) < (A(), B())) # revealed: LtReturnType | LtReturnTypeOnB | Literal[False]
|
||||
```
|
||||
|
||||
@@ -262,6 +278,7 @@ comparison can clearly conclude before encountering an error, the error should n
|
||||
```py
|
||||
def _(n: int, s: str):
|
||||
class A: ...
|
||||
|
||||
# error: [unsupported-operator] "Operator `<` is not supported between two objects of type `A`"
|
||||
A() < A()
|
||||
# error: [unsupported-operator] "Operator `<=` is not supported between two objects of type `A`"
|
||||
@@ -466,6 +483,7 @@ def compute_chained_comparison():
|
||||
class NotBoolable:
|
||||
__bool__: int = 5
|
||||
|
||||
|
||||
class Comparable:
|
||||
def __lt__(self, other) -> NotBoolable:
|
||||
return NotBoolable()
|
||||
@@ -473,6 +491,7 @@ class Comparable:
|
||||
def __gt__(self, other) -> NotBoolable:
|
||||
return NotBoolable()
|
||||
|
||||
|
||||
a = (1, Comparable())
|
||||
b = (1, Comparable())
|
||||
|
||||
@@ -494,11 +513,13 @@ pair of elements at equivalent positions cannot be converted to a `bool`:
|
||||
class NotBoolable:
|
||||
__bool__: None = None
|
||||
|
||||
|
||||
class A:
|
||||
# error: [invalid-method-override]
|
||||
def __eq__(self, other) -> NotBoolable:
|
||||
return NotBoolable()
|
||||
|
||||
|
||||
# error: [unsupported-bool-conversion]
|
||||
(A(),) == (A(),)
|
||||
```
|
||||
@@ -509,9 +530,11 @@ class A:
|
||||
from __future__ import annotations
|
||||
from typing import NamedTuple
|
||||
|
||||
|
||||
class Node(NamedTuple):
|
||||
parent: Node | None
|
||||
|
||||
|
||||
def _(n: Node):
|
||||
reveal_type(n.parent is n) # revealed: bool
|
||||
```
|
||||
|
||||
@@ -75,6 +75,7 @@ back to `bool` for the result type instead of trying to infer something more pre
|
||||
```py
|
||||
from typing import Literal
|
||||
|
||||
|
||||
def _(
|
||||
x: list[int] | Literal[1],
|
||||
y: list[int] | Literal[1],
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
```py
|
||||
def _(flag: bool, flag1: bool, flag2: bool):
|
||||
class A: ...
|
||||
|
||||
a = 1 in 7 # error: "Operator `in` is not supported between objects of type `Literal[1]` and `Literal[7]`"
|
||||
reveal_type(a) # revealed: bool
|
||||
|
||||
|
||||
@@ -6,14 +6,17 @@
|
||||
# revealed: int
|
||||
[reveal_type(x) for x in range(3)]
|
||||
|
||||
|
||||
class Row:
|
||||
def __next__(self) -> range:
|
||||
return range(3)
|
||||
|
||||
|
||||
class Table:
|
||||
def __iter__(self) -> Row:
|
||||
return Row()
|
||||
|
||||
|
||||
# revealed: tuple[int, range]
|
||||
[reveal_type((cell, row)) for row in Table() for cell in row]
|
||||
|
||||
@@ -38,10 +41,12 @@ class Row:
|
||||
def __next__(self) -> range:
|
||||
return range(3)
|
||||
|
||||
|
||||
class Table:
|
||||
def __iter__(self) -> Row:
|
||||
return Row()
|
||||
|
||||
|
||||
# revealed: tuple[int, range]
|
||||
[[reveal_type((cell, row)) for cell in row] for row in Table()]
|
||||
```
|
||||
@@ -85,6 +90,7 @@ Starred expressions must be iterable
|
||||
```py
|
||||
class NotIterable: ...
|
||||
|
||||
|
||||
# This is fine:
|
||||
x = [*range(3)]
|
||||
|
||||
@@ -101,10 +107,12 @@ class AsyncIterator:
|
||||
async def __anext__(self) -> int:
|
||||
return 42
|
||||
|
||||
|
||||
class AsyncIterable:
|
||||
def __aiter__(self) -> AsyncIterator:
|
||||
return AsyncIterator()
|
||||
|
||||
|
||||
async def _():
|
||||
# revealed: int
|
||||
[reveal_type(x) async for x in AsyncIterable()]
|
||||
@@ -180,6 +188,7 @@ The type context is propagated down into the comprehension:
|
||||
class Person(TypedDict):
|
||||
name: str
|
||||
|
||||
|
||||
# TODO: This should not error.
|
||||
# error: [invalid-assignment]
|
||||
persons: list[Person] = [{"name": n} for n in ["Alice", "Bob"]]
|
||||
|
||||
@@ -42,6 +42,7 @@ def _(flag: bool):
|
||||
class NotBoolable:
|
||||
__bool__: int = 3
|
||||
|
||||
|
||||
# error: [unsupported-bool-conversion] "Boolean conversion is not supported for type `NotBoolable`"
|
||||
3 if NotBoolable() else 4
|
||||
```
|
||||
|
||||
@@ -122,6 +122,7 @@ def _(flag: bool, flag2: bool):
|
||||
def check(x: int) -> bool:
|
||||
return bool(x)
|
||||
|
||||
|
||||
if check(x := 1):
|
||||
x = 2
|
||||
elif check(x := 3):
|
||||
@@ -136,6 +137,7 @@ reveal_type(x) # revealed: Literal[2, 3, 4]
|
||||
def check(x) -> bool:
|
||||
return bool(x)
|
||||
|
||||
|
||||
def _(flag: bool):
|
||||
x = 1 if flag else None
|
||||
y = 0
|
||||
@@ -154,6 +156,7 @@ def _(flag: bool):
|
||||
class NotBoolable:
|
||||
__bool__: int = 3
|
||||
|
||||
|
||||
# error: [unsupported-bool-conversion] "Boolean conversion is not supported for type `NotBoolable`"
|
||||
if NotBoolable():
|
||||
...
|
||||
|
||||
@@ -63,10 +63,12 @@ This leads us to infer `Literal[1, 3]` as the type of `y` after the `match` stat
|
||||
```py
|
||||
from typing import final
|
||||
|
||||
|
||||
@final
|
||||
class C:
|
||||
pass
|
||||
|
||||
|
||||
def _(subject: C):
|
||||
y = 1
|
||||
match subject:
|
||||
@@ -85,19 +87,24 @@ all subpatterns in the class pattern match.
|
||||
```py
|
||||
from typing import final
|
||||
|
||||
|
||||
class Foo:
|
||||
pass
|
||||
|
||||
|
||||
class FooSub(Foo):
|
||||
pass
|
||||
|
||||
|
||||
class Bar:
|
||||
pass
|
||||
|
||||
|
||||
@final
|
||||
class Baz:
|
||||
pass
|
||||
|
||||
|
||||
def _(target: FooSub):
|
||||
y = 1
|
||||
|
||||
@@ -111,6 +118,7 @@ def _(target: FooSub):
|
||||
|
||||
reveal_type(y) # revealed: Literal[3]
|
||||
|
||||
|
||||
def _(target: FooSub):
|
||||
y = 1
|
||||
|
||||
@@ -124,6 +132,7 @@ def _(target: FooSub):
|
||||
|
||||
reveal_type(y) # revealed: Literal[3, 4]
|
||||
|
||||
|
||||
def _(target: FooSub | str):
|
||||
y = 1
|
||||
|
||||
@@ -144,13 +153,16 @@ def _(target: FooSub | str):
|
||||
from typing_extensions import assert_never
|
||||
from dataclasses import dataclass
|
||||
|
||||
|
||||
@dataclass
|
||||
class Point:
|
||||
x: int
|
||||
y: int
|
||||
|
||||
|
||||
class Other: ...
|
||||
|
||||
|
||||
def _(target: Point):
|
||||
y = 1
|
||||
|
||||
@@ -164,6 +176,7 @@ def _(target: Point):
|
||||
|
||||
reveal_type(y) # revealed: Literal[1, 2, 3, 4]
|
||||
|
||||
|
||||
def _(target: Point):
|
||||
match target:
|
||||
case Point(x, y): # irrefutable sub-patterns
|
||||
@@ -171,6 +184,7 @@ def _(target: Point):
|
||||
case _:
|
||||
assert_never(target)
|
||||
|
||||
|
||||
def _(target: Point | Other):
|
||||
match target:
|
||||
case Point(0, 0):
|
||||
@@ -190,6 +204,7 @@ Singleton patterns are matched based on identity, not equality comparisons or `i
|
||||
```py
|
||||
from typing import Literal
|
||||
|
||||
|
||||
def _(target: Literal[True, False]):
|
||||
y = 1
|
||||
|
||||
@@ -203,6 +218,7 @@ def _(target: Literal[True, False]):
|
||||
|
||||
reveal_type(y) # revealed: Literal[2, 3]
|
||||
|
||||
|
||||
def _(target: bool):
|
||||
y = 1
|
||||
|
||||
@@ -216,6 +232,7 @@ def _(target: bool):
|
||||
|
||||
reveal_type(y) # revealed: Literal[2, 3]
|
||||
|
||||
|
||||
def _(target: None):
|
||||
y = 1
|
||||
|
||||
@@ -229,6 +246,7 @@ def _(target: None):
|
||||
|
||||
reveal_type(y) # revealed: Literal[4]
|
||||
|
||||
|
||||
def _(target: None | Literal[True]):
|
||||
y = 1
|
||||
|
||||
@@ -242,6 +260,7 @@ def _(target: None | Literal[True]):
|
||||
|
||||
reveal_type(y) # revealed: Literal[2, 4]
|
||||
|
||||
|
||||
# bool is an int subclass
|
||||
def _(target: int):
|
||||
y = 1
|
||||
@@ -256,6 +275,7 @@ def _(target: int):
|
||||
|
||||
reveal_type(y) # revealed: Literal[1, 2, 3]
|
||||
|
||||
|
||||
def _(target: str):
|
||||
y = 1
|
||||
|
||||
@@ -275,10 +295,12 @@ def _(target: str):
|
||||
```py
|
||||
from enum import Enum
|
||||
|
||||
|
||||
class Answer(Enum):
|
||||
NO = 0
|
||||
YES = 1
|
||||
|
||||
|
||||
def _(answer: Answer):
|
||||
y = 0
|
||||
match answer:
|
||||
@@ -299,6 +321,7 @@ A `|` pattern matches if any of the subpatterns match.
|
||||
```py
|
||||
from typing import Literal, final
|
||||
|
||||
|
||||
def _(target: Literal["foo", "baz"]):
|
||||
y = 1
|
||||
|
||||
@@ -310,6 +333,7 @@ def _(target: Literal["foo", "baz"]):
|
||||
|
||||
reveal_type(y) # revealed: Literal[2, 3]
|
||||
|
||||
|
||||
def _(target: None):
|
||||
y = 1
|
||||
|
||||
@@ -321,10 +345,12 @@ def _(target: None):
|
||||
|
||||
reveal_type(y) # revealed: Literal[2]
|
||||
|
||||
|
||||
@final
|
||||
class Baz:
|
||||
pass
|
||||
|
||||
|
||||
def _(target: int | None | float):
|
||||
y = 1
|
||||
|
||||
@@ -336,8 +362,10 @@ def _(target: int | None | float):
|
||||
|
||||
reveal_type(y) # revealed: Literal[1, 2]
|
||||
|
||||
|
||||
class Foo: ...
|
||||
|
||||
|
||||
def _(target: None | Foo):
|
||||
y = 1
|
||||
|
||||
@@ -375,6 +403,7 @@ def _(target: int | str):
|
||||
class NotBoolable:
|
||||
__bool__: int = 3
|
||||
|
||||
|
||||
def _(target: int, flag: NotBoolable):
|
||||
y = 1
|
||||
match target:
|
||||
@@ -395,10 +424,12 @@ is not covered by any case, even when all enum members are covered.
|
||||
```py
|
||||
from enum import Enum
|
||||
|
||||
|
||||
class Answer(Enum):
|
||||
YES = 1
|
||||
NO = 2
|
||||
|
||||
|
||||
def _(answer: Answer | None):
|
||||
y = 0
|
||||
match answer:
|
||||
@@ -411,6 +442,7 @@ def _(answer: Answer | None):
|
||||
# so y could still be 0
|
||||
reveal_type(y) # revealed: Literal[0, 1, 2]
|
||||
|
||||
|
||||
def _(answer: Answer | None):
|
||||
match answer:
|
||||
case Answer.YES:
|
||||
@@ -422,8 +454,10 @@ def _(answer: Answer | None):
|
||||
reveal_type(answer) # revealed: None
|
||||
return 3
|
||||
|
||||
|
||||
class Foo: ...
|
||||
|
||||
|
||||
def _(answer: Answer | None):
|
||||
match answer:
|
||||
case Answer.YES:
|
||||
|
||||
@@ -7,10 +7,12 @@ Deferred annotations can result in cycles in resolving a function signature:
|
||||
```py
|
||||
from __future__ import annotations
|
||||
|
||||
|
||||
# error: [invalid-type-form]
|
||||
def f(x: f):
|
||||
pass
|
||||
|
||||
|
||||
reveal_type(f) # revealed: def f(x: Unknown) -> Unknown
|
||||
```
|
||||
|
||||
@@ -27,6 +29,7 @@ class Point:
|
||||
def replace_with(self, other: "Point") -> None:
|
||||
self.x, self.y = other.x, other.y
|
||||
|
||||
|
||||
p = Point()
|
||||
reveal_type(p.x) # revealed: Unknown | int
|
||||
reveal_type(p.y) # revealed: Unknown | int
|
||||
@@ -44,15 +47,18 @@ from typing import Union, TypeAliasType, Sequence, Mapping
|
||||
|
||||
A = list["A" | None]
|
||||
|
||||
|
||||
def f(x: A):
|
||||
# TODO: should be `list[A | None]`?
|
||||
reveal_type(x) # revealed: list[Divergent]
|
||||
# TODO: should be `A | None`?
|
||||
reveal_type(x[0]) # revealed: Divergent
|
||||
|
||||
|
||||
JSONPrimitive = Union[str, int, float, bool, None]
|
||||
JSONValue = TypeAliasType("JSONValue", 'Union[JSONPrimitive, Sequence["JSONValue"], Mapping[str, "JSONValue"]]')
|
||||
|
||||
|
||||
def _(x: JSONValue):
|
||||
# TODO: should be `JSONValue`
|
||||
reveal_type(x) # revealed: Divergent
|
||||
@@ -65,6 +71,7 @@ from typing import Generic, TypeVar
|
||||
|
||||
B = TypeVar("B", bound="Base")
|
||||
|
||||
|
||||
class Base(Generic[B]):
|
||||
pass
|
||||
```
|
||||
@@ -86,24 +93,28 @@ class C:
|
||||
def f(self: "C"):
|
||||
def inner_a(positional=self.a):
|
||||
return
|
||||
|
||||
self.a = inner_a
|
||||
# revealed: def inner_a(positional=...) -> Unknown
|
||||
reveal_type(inner_a)
|
||||
|
||||
def inner_b(*, kw_only=self.b):
|
||||
return
|
||||
|
||||
self.b = inner_b
|
||||
# revealed: def inner_b(*, kw_only=...) -> Unknown
|
||||
reveal_type(inner_b)
|
||||
|
||||
def inner_c(positional_only=self.c, /):
|
||||
return
|
||||
|
||||
self.c = inner_c
|
||||
# revealed: def inner_c(positional_only=..., /) -> Unknown
|
||||
reveal_type(inner_c)
|
||||
|
||||
def inner_d(*, kw_only=self.d):
|
||||
return
|
||||
|
||||
self.d = inner_d
|
||||
# revealed: def inner_d(*, kw_only=...) -> Unknown
|
||||
reveal_type(inner_d)
|
||||
@@ -116,6 +127,7 @@ class D:
|
||||
def f(self: "D"):
|
||||
# error: [invalid-parameter-default] "Default value of type `Unknown | (def inner_a(a: int = ...) -> Unknown)` is not assignable to annotated parameter type `int`"
|
||||
def inner_a(a: int = self.a): ...
|
||||
|
||||
self.a = inner_a
|
||||
```
|
||||
|
||||
@@ -153,6 +165,7 @@ class Cyclic:
|
||||
if isinstance(self.data, str):
|
||||
self.data = {"url": self.data}
|
||||
|
||||
|
||||
# revealed: Unknown | str | dict[Unknown, Unknown] | dict[Unknown | str, Unknown | str]
|
||||
reveal_type(Cyclic("").data)
|
||||
```
|
||||
|
||||
@@ -13,16 +13,19 @@ class, or metaclass is a `dataclass`-like construct.
|
||||
```py
|
||||
from typing_extensions import dataclass_transform
|
||||
|
||||
|
||||
@dataclass_transform()
|
||||
def my_dataclass[T](cls: type[T]) -> type[T]:
|
||||
# modify cls
|
||||
return cls
|
||||
|
||||
|
||||
@my_dataclass
|
||||
class Person:
|
||||
name: str
|
||||
age: int | None = None
|
||||
|
||||
|
||||
Person("Alice", 20)
|
||||
Person("Bob", None)
|
||||
Person("Bob")
|
||||
@@ -38,18 +41,22 @@ If we want our `dataclass`-like decorator to also take parameters, that is also
|
||||
```py
|
||||
from typing_extensions import dataclass_transform, Callable
|
||||
|
||||
|
||||
@dataclass_transform()
|
||||
def versioned_class[T](*, version: int = 1):
|
||||
def decorator(cls):
|
||||
# modify cls
|
||||
return cls
|
||||
|
||||
return decorator
|
||||
|
||||
|
||||
@versioned_class(version=2)
|
||||
class Person:
|
||||
name: str
|
||||
age: int | None = None
|
||||
|
||||
|
||||
Person("Alice", 20)
|
||||
|
||||
# error: [missing-argument]
|
||||
@@ -61,6 +68,7 @@ We properly type-check the arguments to the decorator:
|
||||
```py
|
||||
from typing_extensions import dataclass_transform, Callable
|
||||
|
||||
|
||||
# error: [invalid-argument-type]
|
||||
@versioned_class(version="a string")
|
||||
class C:
|
||||
@@ -77,16 +85,19 @@ The examples from this section are straight from the Python documentation on
|
||||
```py
|
||||
from typing_extensions import dataclass_transform
|
||||
|
||||
|
||||
@dataclass_transform()
|
||||
def create_model[T](cls: type[T]) -> type[T]:
|
||||
...
|
||||
return cls
|
||||
|
||||
|
||||
@create_model
|
||||
class CustomerModel:
|
||||
id: int
|
||||
name: str
|
||||
|
||||
|
||||
CustomerModel(id=1, name="Test")
|
||||
```
|
||||
|
||||
@@ -95,15 +106,19 @@ CustomerModel(id=1, name="Test")
|
||||
```py
|
||||
from typing_extensions import dataclass_transform
|
||||
|
||||
|
||||
@dataclass_transform()
|
||||
class ModelMeta(type): ...
|
||||
|
||||
|
||||
class ModelBase(metaclass=ModelMeta): ...
|
||||
|
||||
|
||||
class CustomerModel(ModelBase):
|
||||
id: int
|
||||
name: str
|
||||
|
||||
|
||||
CustomerModel(id=1, name="Test")
|
||||
|
||||
# error: [missing-argument]
|
||||
@@ -115,13 +130,16 @@ CustomerModel()
|
||||
```py
|
||||
from typing_extensions import dataclass_transform
|
||||
|
||||
|
||||
@dataclass_transform()
|
||||
class ModelBase: ...
|
||||
|
||||
|
||||
class CustomerModel(ModelBase):
|
||||
id: int
|
||||
name: str
|
||||
|
||||
|
||||
CustomerModel(id=1, name="Test")
|
||||
```
|
||||
|
||||
@@ -140,52 +158,67 @@ This can be overwritten using the `order` argument to the custom decorator:
|
||||
```py
|
||||
from typing_extensions import dataclass_transform
|
||||
|
||||
|
||||
@dataclass_transform()
|
||||
def normal(*, order: bool = False):
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
@dataclass_transform(order_default=False)
|
||||
def order_default_false(*, order: bool = False):
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
@dataclass_transform(order_default=True)
|
||||
def order_default_true(*, order: bool = True):
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
@normal
|
||||
class Normal:
|
||||
inner: int
|
||||
|
||||
|
||||
Normal(1) < Normal(2) # error: [unsupported-operator]
|
||||
|
||||
|
||||
@normal(order=True)
|
||||
class NormalOverwritten:
|
||||
inner: int
|
||||
|
||||
|
||||
reveal_type(NormalOverwritten(1) < NormalOverwritten(2)) # revealed: bool
|
||||
|
||||
|
||||
@order_default_false
|
||||
class OrderFalse:
|
||||
inner: int
|
||||
|
||||
|
||||
OrderFalse(1) < OrderFalse(2) # error: [unsupported-operator]
|
||||
|
||||
|
||||
@order_default_false(order=True)
|
||||
class OrderFalseOverwritten:
|
||||
inner: int
|
||||
|
||||
|
||||
reveal_type(OrderFalseOverwritten(1) < OrderFalseOverwritten(2)) # revealed: bool
|
||||
|
||||
|
||||
@order_default_true
|
||||
class OrderTrue:
|
||||
inner: int
|
||||
|
||||
|
||||
reveal_type(OrderTrue(1) < OrderTrue(2)) # revealed: bool
|
||||
|
||||
|
||||
@order_default_true(order=False)
|
||||
class OrderTrueOverwritten:
|
||||
inner: int
|
||||
|
||||
|
||||
# error: [unsupported-operator]
|
||||
OrderTrueOverwritten(1) < OrderTrueOverwritten(2)
|
||||
```
|
||||
@@ -196,11 +229,14 @@ This also works for metaclass-based transformers:
|
||||
@dataclass_transform(order_default=True)
|
||||
class OrderedModelMeta(type): ...
|
||||
|
||||
|
||||
class OrderedModel(metaclass=OrderedModelMeta): ...
|
||||
|
||||
|
||||
class TestWithMeta(OrderedModel):
|
||||
inner: int
|
||||
|
||||
|
||||
reveal_type(TestWithMeta(1) < TestWithMeta(2)) # revealed: bool
|
||||
```
|
||||
|
||||
@@ -210,9 +246,11 @@ And for base-class-based transformers:
|
||||
@dataclass_transform(order_default=True)
|
||||
class OrderedModelBase: ...
|
||||
|
||||
|
||||
class TestWithBase(OrderedModelBase):
|
||||
inner: int
|
||||
|
||||
|
||||
reveal_type(TestWithBase(1) < TestWithBase(2)) # revealed: bool
|
||||
```
|
||||
|
||||
@@ -224,12 +262,14 @@ When provided, sets the default value for the `kw_only` parameter of `field()`.
|
||||
from typing import dataclass_transform
|
||||
from dataclasses import field
|
||||
|
||||
|
||||
@dataclass_transform(kw_only_default=True)
|
||||
def create_model(*, kw_only: bool = True): ...
|
||||
@create_model()
|
||||
class A:
|
||||
name: str
|
||||
|
||||
|
||||
a = A(name="Harry")
|
||||
# error: [missing-argument]
|
||||
# error: [too-many-positional-arguments]
|
||||
@@ -244,6 +284,7 @@ class CustomerModel:
|
||||
id: int
|
||||
name: str
|
||||
|
||||
|
||||
c = CustomerModel(1, "Harry")
|
||||
```
|
||||
|
||||
@@ -253,11 +294,14 @@ This also works for metaclass-based transformers:
|
||||
@dataclass_transform(kw_only_default=True)
|
||||
class ModelMeta(type): ...
|
||||
|
||||
|
||||
class ModelBase(metaclass=ModelMeta): ...
|
||||
|
||||
|
||||
class TestMeta(ModelBase):
|
||||
name: str
|
||||
|
||||
|
||||
reveal_type(TestMeta.__init__) # revealed: (self: TestMeta, *, name: str) -> None
|
||||
```
|
||||
|
||||
@@ -267,9 +311,11 @@ And for base-class-based transformers:
|
||||
@dataclass_transform(kw_only_default=True)
|
||||
class ModelBase: ...
|
||||
|
||||
|
||||
class TestBase(ModelBase):
|
||||
name: str
|
||||
|
||||
|
||||
reveal_type(TestBase.__init__) # revealed: (self: TestBase, *, name: str) -> None
|
||||
```
|
||||
|
||||
@@ -280,12 +326,14 @@ When provided, sets the default value for the `frozen` parameter of `field()`.
|
||||
```py
|
||||
from typing import dataclass_transform
|
||||
|
||||
|
||||
@dataclass_transform(frozen_default=True)
|
||||
def create_model(*, frozen: bool = True): ...
|
||||
@create_model()
|
||||
class ImmutableModel:
|
||||
name: str
|
||||
|
||||
|
||||
i = ImmutableModel(name="test")
|
||||
i.name = "new" # error: [invalid-assignment]
|
||||
```
|
||||
@@ -297,6 +345,7 @@ Again, this can be overridden by setting `frozen=False` when applying the decora
|
||||
class MutableModel:
|
||||
name: str
|
||||
|
||||
|
||||
m = MutableModel(name="test")
|
||||
m.name = "new" # No error
|
||||
```
|
||||
@@ -307,11 +356,14 @@ This also works for metaclass-based transformers:
|
||||
@dataclass_transform(frozen_default=True)
|
||||
class ModelMeta(type): ...
|
||||
|
||||
|
||||
class ModelBase(metaclass=ModelMeta): ...
|
||||
|
||||
|
||||
class TestMeta(ModelBase):
|
||||
name: str
|
||||
|
||||
|
||||
t = TestMeta(name="test")
|
||||
t.name = "new" # error: [invalid-assignment]
|
||||
```
|
||||
@@ -322,9 +374,11 @@ And for base-class-based transformers:
|
||||
@dataclass_transform(frozen_default=True)
|
||||
class ModelBase: ...
|
||||
|
||||
|
||||
class TestMeta(ModelBase):
|
||||
name: str
|
||||
|
||||
|
||||
t = TestMeta(name="test")
|
||||
|
||||
t.name = "new" # error: [invalid-assignment]
|
||||
@@ -337,6 +391,7 @@ Combining several of these parameters also works as expected:
|
||||
```py
|
||||
from typing import dataclass_transform
|
||||
|
||||
|
||||
@dataclass_transform(eq_default=True, order_default=False, kw_only_default=True, frozen_default=True)
|
||||
def create_model(*, eq: bool = True, order: bool = False, kw_only: bool = True, frozen: bool = True): ...
|
||||
@create_model(eq=False, order=True, kw_only=False, frozen=False)
|
||||
@@ -344,6 +399,7 @@ class OverridesAllParametersModel:
|
||||
name: str
|
||||
age: int
|
||||
|
||||
|
||||
# Positional arguments are allowed:
|
||||
model = OverridesAllParametersModel("test", 25)
|
||||
|
||||
@@ -365,21 +421,25 @@ from `order=False` (default) to `order=True`:
|
||||
```py
|
||||
from typing import dataclass_transform
|
||||
|
||||
|
||||
@dataclass_transform(frozen_default=True)
|
||||
def default_frozen_model(*, frozen: bool = True, order: bool = False): ...
|
||||
@default_frozen_model()
|
||||
class Frozen:
|
||||
name: str
|
||||
|
||||
|
||||
f = Frozen(name="test")
|
||||
f.name = "new" # error: [invalid-assignment]
|
||||
|
||||
Frozen(name="A") < Frozen(name="B") # error: [unsupported-operator]
|
||||
|
||||
|
||||
@default_frozen_model(frozen=False, order=True)
|
||||
class Mutable:
|
||||
name: str
|
||||
|
||||
|
||||
m = Mutable(name="test")
|
||||
m.name = "new" # No error
|
||||
|
||||
@@ -391,6 +451,7 @@ reveal_type(Mutable(name="A") < Mutable(name="B")) # revealed: bool
|
||||
```py
|
||||
from typing import dataclass_transform
|
||||
|
||||
|
||||
@dataclass_transform(frozen_default=True)
|
||||
class DefaultFrozenMeta(type):
|
||||
def __new__(
|
||||
@@ -403,19 +464,24 @@ class DefaultFrozenMeta(type):
|
||||
order: bool = False,
|
||||
): ...
|
||||
|
||||
|
||||
class DefaultFrozenModel(metaclass=DefaultFrozenMeta): ...
|
||||
|
||||
|
||||
class Frozen(DefaultFrozenModel):
|
||||
name: str
|
||||
|
||||
|
||||
f = Frozen(name="test")
|
||||
f.name = "new" # error: [invalid-assignment]
|
||||
|
||||
Frozen(name="A") < Frozen(name="B") # error: [unsupported-operator]
|
||||
|
||||
|
||||
class Mutable(DefaultFrozenModel, frozen=False, order=True):
|
||||
name: str
|
||||
|
||||
|
||||
m = Mutable(name="test")
|
||||
# TODO: This should not be an error. In order to support this, we need to implement the precise `frozen` semantics of
|
||||
# `dataclass_transform` described here: https://typing.python.org/en/latest/spec/dataclasses.html#dataclass-semantics
|
||||
@@ -429,6 +495,7 @@ reveal_type(Mutable(name="A") < Mutable(name="B")) # revealed: bool
|
||||
```py
|
||||
from typing import dataclass_transform
|
||||
|
||||
|
||||
@dataclass_transform(frozen_default=True)
|
||||
class DefaultFrozenModel:
|
||||
def __init_subclass__(
|
||||
@@ -438,17 +505,21 @@ class DefaultFrozenModel:
|
||||
order: bool = False,
|
||||
): ...
|
||||
|
||||
|
||||
class Frozen(DefaultFrozenModel):
|
||||
name: str
|
||||
|
||||
|
||||
f = Frozen(name="test")
|
||||
f.name = "new" # error: [invalid-assignment]
|
||||
|
||||
Frozen(name="A") < Frozen(name="B") # error: [unsupported-operator]
|
||||
|
||||
|
||||
class Mutable(DefaultFrozenModel, frozen=False, order=True):
|
||||
name: str
|
||||
|
||||
|
||||
m = Mutable(name="test")
|
||||
m.name = "new" # No error
|
||||
|
||||
@@ -467,20 +538,25 @@ from typing_extensions import dataclass_transform, TypeVar, Callable
|
||||
|
||||
T = TypeVar("T", bound=type)
|
||||
|
||||
|
||||
@dataclass_transform()
|
||||
def fancy_model(*, slots: bool = False) -> Callable[[T], T]:
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
@fancy_model()
|
||||
class NoSlots:
|
||||
name: str
|
||||
|
||||
|
||||
NoSlots.__slots__ # error: [unresolved-attribute]
|
||||
|
||||
|
||||
@fancy_model(slots=True)
|
||||
class WithSlots:
|
||||
name: str
|
||||
|
||||
|
||||
reveal_type(WithSlots.__slots__) # revealed: tuple[Literal["name"]]
|
||||
```
|
||||
|
||||
@@ -489,23 +565,29 @@ reveal_type(WithSlots.__slots__) # revealed: tuple[Literal["name"]]
|
||||
```py
|
||||
from typing_extensions import dataclass_transform
|
||||
|
||||
|
||||
@dataclass_transform()
|
||||
class FancyMeta(type):
|
||||
def __new__(cls, name, bases, namespace, *, slots: bool = False):
|
||||
...
|
||||
return super().__new__(cls, name, bases, namespace)
|
||||
|
||||
|
||||
class FancyBase(metaclass=FancyMeta): ...
|
||||
|
||||
|
||||
class NoSlots(FancyBase):
|
||||
name: str
|
||||
|
||||
|
||||
# error: [unresolved-attribute]
|
||||
NoSlots.__slots__
|
||||
|
||||
|
||||
class WithSlots(FancyBase, slots=True):
|
||||
name: str
|
||||
|
||||
|
||||
reveal_type(WithSlots.__slots__) # revealed: tuple[Literal["name"]]
|
||||
```
|
||||
|
||||
@@ -514,20 +596,25 @@ reveal_type(WithSlots.__slots__) # revealed: tuple[Literal["name"]]
|
||||
```py
|
||||
from typing_extensions import dataclass_transform
|
||||
|
||||
|
||||
@dataclass_transform()
|
||||
class FancyBase:
|
||||
def __init_subclass__(cls, *, slots: bool = False):
|
||||
...
|
||||
super().__init_subclass__()
|
||||
|
||||
|
||||
class NoSlots(FancyBase):
|
||||
name: str
|
||||
|
||||
|
||||
NoSlots.__slots__ # error: [unresolved-attribute]
|
||||
|
||||
|
||||
class WithSlots(FancyBase, slots=True):
|
||||
name: str
|
||||
|
||||
|
||||
reveal_type(WithSlots.__slots__) # revealed: tuple[Literal["name"]]
|
||||
```
|
||||
|
||||
@@ -545,18 +632,21 @@ checkers do not seem to support this either.
|
||||
```py
|
||||
from typing_extensions import dataclass_transform, Any
|
||||
|
||||
|
||||
def fancy_field(*, init: bool = True, kw_only: bool = False, alias: str | None = None) -> Any: ...
|
||||
@dataclass_transform(field_specifiers=(fancy_field,))
|
||||
def fancy_model[T](cls: type[T]) -> type[T]:
|
||||
...
|
||||
return cls
|
||||
|
||||
|
||||
@fancy_model
|
||||
class Person:
|
||||
id: int = fancy_field(init=False)
|
||||
internal_name: str = fancy_field(alias="name")
|
||||
age: int | None = fancy_field(kw_only=True)
|
||||
|
||||
|
||||
reveal_type(Person.__init__) # revealed: (self: Person, name: str, *, age: int | None) -> None
|
||||
|
||||
alice = Person("Alice", age=30)
|
||||
@@ -571,6 +661,7 @@ reveal_type(alice.age) # revealed: int | None
|
||||
```py
|
||||
from typing_extensions import dataclass_transform, Any
|
||||
|
||||
|
||||
def fancy_field(*, init: bool = True, kw_only: bool = False, alias: str | None = None) -> Any: ...
|
||||
@dataclass_transform(field_specifiers=(fancy_field,))
|
||||
class FancyMeta(type):
|
||||
@@ -578,13 +669,16 @@ class FancyMeta(type):
|
||||
...
|
||||
return super().__new__(cls, name, bases, namespace)
|
||||
|
||||
|
||||
class FancyBase(metaclass=FancyMeta): ...
|
||||
|
||||
|
||||
class Person(FancyBase):
|
||||
id: int = fancy_field(init=False)
|
||||
internal_name: str = fancy_field(alias="name")
|
||||
age: int | None = fancy_field(kw_only=True)
|
||||
|
||||
|
||||
reveal_type(Person.__init__) # revealed: (self: Person, name: str, *, age: int | None) -> None
|
||||
|
||||
alice = Person("Alice", age=30)
|
||||
@@ -599,6 +693,7 @@ reveal_type(alice.age) # revealed: int | None
|
||||
```py
|
||||
from typing_extensions import dataclass_transform, Any
|
||||
|
||||
|
||||
def fancy_field(*, init: bool = True, kw_only: bool = False, alias: str | None = None) -> Any: ...
|
||||
@dataclass_transform(field_specifiers=(fancy_field,))
|
||||
class FancyBase:
|
||||
@@ -606,11 +701,13 @@ class FancyBase:
|
||||
...
|
||||
super().__init_subclass__()
|
||||
|
||||
|
||||
class Person(FancyBase):
|
||||
id: int = fancy_field(init=False)
|
||||
internal_name: str = fancy_field(alias="name")
|
||||
age: int | None = fancy_field(kw_only=True)
|
||||
|
||||
|
||||
reveal_type(Person.__init__) # revealed: (self: Person, name: str, *, age: int | None) -> None
|
||||
|
||||
alice = Person("Alice", age=30)
|
||||
@@ -627,17 +724,20 @@ Field specifiers can have default arguments that should be respected:
|
||||
```py
|
||||
from typing_extensions import dataclass_transform, Any
|
||||
|
||||
|
||||
def fancy_field(*, init: bool = False) -> Any: ...
|
||||
@dataclass_transform(field_specifiers=(fancy_field,))
|
||||
def fancy_model[T](cls: type[T]) -> type[T]:
|
||||
...
|
||||
return cls
|
||||
|
||||
|
||||
@fancy_model
|
||||
class Person:
|
||||
id: int = fancy_field()
|
||||
name: str = fancy_field(init=True)
|
||||
|
||||
|
||||
reveal_type(Person.__init__) # revealed: (self: Person, name: str) -> None
|
||||
|
||||
Person(name="Alice")
|
||||
@@ -655,11 +755,13 @@ correctly when passed via `**kwargs` for all three kinds of transformers.
|
||||
from typing import Any
|
||||
from typing_extensions import dataclass_transform
|
||||
|
||||
|
||||
def field(**kwargs: Any) -> Any: ...
|
||||
@dataclass_transform(field_specifiers=(field,))
|
||||
def create_model[T](cls: type[T]) -> type[T]:
|
||||
return cls
|
||||
|
||||
|
||||
@create_model
|
||||
class Person:
|
||||
id: int = field(init=False)
|
||||
@@ -669,6 +771,7 @@ class Person:
|
||||
email: str = field(kw_only=True)
|
||||
internal_notes: str = field(alias="notes")
|
||||
|
||||
|
||||
# revealed: (self: Person, name: str, age: int = ..., tags: list[str] = ..., notes: str, *, email: str) -> None
|
||||
reveal_type(Person.__init__)
|
||||
|
||||
@@ -682,12 +785,15 @@ Person("Bob", email="bob@example.com", notes="other notes")
|
||||
from typing import Any
|
||||
from typing_extensions import dataclass_transform
|
||||
|
||||
|
||||
def field(**kwargs: Any) -> Any: ...
|
||||
@dataclass_transform(field_specifiers=(field,))
|
||||
class ModelMeta(type): ...
|
||||
|
||||
|
||||
class ModelBase(metaclass=ModelMeta): ...
|
||||
|
||||
|
||||
class Person(ModelBase):
|
||||
id: int = field(init=False)
|
||||
name: str
|
||||
@@ -696,6 +802,7 @@ class Person(ModelBase):
|
||||
email: str = field(kw_only=True)
|
||||
internal_notes: str = field(alias="notes")
|
||||
|
||||
|
||||
# revealed: (self: Person, name: str, age: int = ..., tags: list[str] = ..., notes: str, *, email: str) -> None
|
||||
reveal_type(Person.__init__)
|
||||
|
||||
@@ -709,10 +816,12 @@ Person("Bob", email="bob@example.com", notes="other notes")
|
||||
from typing import Any
|
||||
from typing_extensions import dataclass_transform
|
||||
|
||||
|
||||
def field(**kwargs: Any) -> Any: ...
|
||||
@dataclass_transform(field_specifiers=(field,))
|
||||
class ModelBase: ...
|
||||
|
||||
|
||||
class Person(ModelBase):
|
||||
id: int = field(init=False)
|
||||
name: str
|
||||
@@ -721,6 +830,7 @@ class Person(ModelBase):
|
||||
email: str = field(kw_only=True)
|
||||
internal_notes: str = field(alias="notes")
|
||||
|
||||
|
||||
# revealed: (self: Person, name: str, age: int = ..., tags: list[str] = ..., notes: str, *, email: str) -> None
|
||||
reveal_type(Person.__init__)
|
||||
|
||||
@@ -736,11 +846,13 @@ the synthesized `__init__` method.
|
||||
```py
|
||||
from typing_extensions import dataclass_transform, Any
|
||||
|
||||
|
||||
def field_with_alias(*, alias: str | None = None, kw_only: bool = False) -> Any: ...
|
||||
@dataclass_transform(field_specifiers=(field_with_alias,))
|
||||
def model[T](cls: type[T]) -> type[T]:
|
||||
return cls
|
||||
|
||||
|
||||
@model
|
||||
class Person:
|
||||
internal_name: str = field_with_alias(alias="name")
|
||||
@@ -785,6 +897,7 @@ p = Person(name="Alice", internal_age=30)
|
||||
```py
|
||||
from typing_extensions import dataclass_transform, overload, Any
|
||||
|
||||
|
||||
@overload
|
||||
def fancy_field(*, init: bool = True) -> Any: ...
|
||||
@overload
|
||||
@@ -795,12 +908,14 @@ def fancy_model[T](cls: type[T]) -> type[T]:
|
||||
...
|
||||
return cls
|
||||
|
||||
|
||||
@fancy_model
|
||||
class Person:
|
||||
id: int = fancy_field(init=False)
|
||||
name: str = fancy_field()
|
||||
age: int | None = fancy_field(kw_only=True)
|
||||
|
||||
|
||||
reveal_type(Person.__init__) # revealed: (self: Person, name: str, *, age: int | None) -> None
|
||||
```
|
||||
|
||||
@@ -812,18 +927,21 @@ Make sure that models are only affected by the field specifiers of their own tra
|
||||
from typing_extensions import dataclass_transform, Any
|
||||
from dataclasses import field
|
||||
|
||||
|
||||
def outer_field(*, init: bool = True, kw_only: bool = False) -> Any: ...
|
||||
@dataclass_transform(field_specifiers=(outer_field,))
|
||||
def outer_model[T](cls: type[T]) -> type[T]:
|
||||
# ...
|
||||
return cls
|
||||
|
||||
|
||||
def inner_field(*, init: bool = True, kw_only: bool = False) -> Any: ...
|
||||
@dataclass_transform(field_specifiers=(inner_field,))
|
||||
def inner_model[T](cls: type[T]) -> type[T]:
|
||||
# ...
|
||||
return cls
|
||||
|
||||
|
||||
@outer_model
|
||||
class Outer:
|
||||
@inner_model
|
||||
@@ -834,6 +952,7 @@ class Outer:
|
||||
outer_a: int = outer_field(init=False)
|
||||
outer_b: str = inner_field(init=False)
|
||||
|
||||
|
||||
reveal_type(Outer.__init__) # revealed: (self: Outer, outer_b: str = ...) -> None
|
||||
reveal_type(Outer.Inner.__init__) # revealed: (self: Inner, inner_b: str = ...) -> None
|
||||
```
|
||||
@@ -850,6 +969,7 @@ from typing_extensions import dataclass_transform, TypeVar, Callable, overload
|
||||
|
||||
T = TypeVar("T", bound=type)
|
||||
|
||||
|
||||
@overload
|
||||
def versioned_class(
|
||||
cls: T,
|
||||
@@ -869,14 +989,17 @@ def versioned_class(
|
||||
) -> T | Callable[[T], T]:
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
@versioned_class
|
||||
class D1:
|
||||
x: str
|
||||
|
||||
|
||||
@versioned_class(version=2)
|
||||
class D2:
|
||||
x: str
|
||||
|
||||
|
||||
D1("a")
|
||||
D2("a")
|
||||
|
||||
@@ -891,6 +1014,7 @@ from typing_extensions import dataclass_transform, TypeVar, Callable, overload
|
||||
|
||||
T = TypeVar("T", bound=type)
|
||||
|
||||
|
||||
@overload
|
||||
@dataclass_transform()
|
||||
def versioned_class(
|
||||
@@ -910,14 +1034,17 @@ def versioned_class(
|
||||
) -> T | Callable[[T], T]:
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
@versioned_class
|
||||
class D1:
|
||||
x: str
|
||||
|
||||
|
||||
@versioned_class(version=2)
|
||||
class D2:
|
||||
x: str
|
||||
|
||||
|
||||
D1("a")
|
||||
D2("a")
|
||||
|
||||
@@ -937,17 +1064,21 @@ sure that we recognize all fields in a hierarchy like this:
|
||||
from dataclasses import dataclass
|
||||
from typing import dataclass_transform
|
||||
|
||||
|
||||
@dataclass_transform()
|
||||
class ModelMeta(type):
|
||||
pass
|
||||
|
||||
|
||||
class Sensor(metaclass=ModelMeta):
|
||||
key: int
|
||||
|
||||
|
||||
@dataclass(frozen=True, kw_only=True)
|
||||
class TemperatureSensor(Sensor):
|
||||
name: str
|
||||
|
||||
|
||||
t = TemperatureSensor(key=1, name="Temperature Sensor")
|
||||
reveal_type(t.key) # revealed: int
|
||||
reveal_type(t.name) # revealed: str
|
||||
@@ -965,15 +1096,18 @@ enables use of `dataclasses.fields`, `dataclasses.asdict`, `dataclasses.replace`
|
||||
from dataclasses import fields, asdict, replace, Field
|
||||
from typing import dataclass_transform, Any
|
||||
|
||||
|
||||
@dataclass_transform()
|
||||
def create_model[T](cls: type[T]) -> type[T]:
|
||||
return cls
|
||||
|
||||
|
||||
@create_model
|
||||
class Person:
|
||||
name: str
|
||||
age: int
|
||||
|
||||
|
||||
p = Person("Alice", 30)
|
||||
|
||||
reveal_type(Person.__dataclass_fields__) # revealed: dict[str, Field[Any]]
|
||||
@@ -990,15 +1124,19 @@ reveal_type(replace(p, name="Bob")) # revealed: Person
|
||||
from dataclasses import fields, asdict, replace, Field
|
||||
from typing import dataclass_transform, Any
|
||||
|
||||
|
||||
@dataclass_transform()
|
||||
class ModelMeta(type): ...
|
||||
|
||||
|
||||
class ModelBase(metaclass=ModelMeta): ...
|
||||
|
||||
|
||||
class Person(ModelBase):
|
||||
name: str
|
||||
age: int
|
||||
|
||||
|
||||
p = Person("Alice", 30)
|
||||
|
||||
reveal_type(Person.__dataclass_fields__) # revealed: dict[str, Field[Any]]
|
||||
@@ -1015,13 +1153,16 @@ reveal_type(replace(p, name="Bob")) # revealed: Person
|
||||
from dataclasses import fields, asdict, replace, Field
|
||||
from typing import dataclass_transform, Any
|
||||
|
||||
|
||||
@dataclass_transform()
|
||||
class ModelBase: ...
|
||||
|
||||
|
||||
class Person(ModelBase):
|
||||
name: str
|
||||
age: int
|
||||
|
||||
|
||||
p = Person("Alice", 30)
|
||||
|
||||
reveal_type(Person.__dataclass_fields__) # revealed: dict[str, Field[Any]]
|
||||
@@ -1042,13 +1183,16 @@ When a function decorated with `@dataclass_transform()` is called directly with
|
||||
```py
|
||||
from typing_extensions import dataclass_transform
|
||||
|
||||
|
||||
@dataclass_transform()
|
||||
def my_dataclass[T](cls: type[T]) -> type[T]:
|
||||
return cls
|
||||
|
||||
|
||||
class A:
|
||||
x: int
|
||||
|
||||
|
||||
B = my_dataclass(A)
|
||||
|
||||
reveal_type(B) # revealed: <class 'A'>
|
||||
@@ -1061,13 +1205,16 @@ B(1)
|
||||
```py
|
||||
from typing_extensions import dataclass_transform
|
||||
|
||||
|
||||
@dataclass_transform()
|
||||
def my_dataclass[T](cls: type[T], *, order: bool = False) -> type[T]:
|
||||
return cls
|
||||
|
||||
|
||||
class A:
|
||||
x: int
|
||||
|
||||
|
||||
B = my_dataclass(A, order=True)
|
||||
|
||||
reveal_type(B) # revealed: <class 'A'>
|
||||
@@ -1083,6 +1230,7 @@ decorator), calling it with a class should return the class type.
|
||||
```py
|
||||
from typing_extensions import dataclass_transform, Callable, overload
|
||||
|
||||
|
||||
@overload
|
||||
@dataclass_transform()
|
||||
def my_dataclass[T](cls: type[T]) -> type[T]: ...
|
||||
@@ -1091,9 +1239,11 @@ def my_dataclass[T]() -> Callable[[type[T]], type[T]]: ...
|
||||
def my_dataclass[T](cls: type[T] | None = None) -> type[T] | Callable[[type[T]], type[T]]:
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class A:
|
||||
x: int
|
||||
|
||||
|
||||
B = my_dataclass(A)
|
||||
|
||||
reveal_type(B) # revealed: <class 'A'>
|
||||
@@ -1109,13 +1259,16 @@ specialization should be preserved.
|
||||
```py
|
||||
from typing_extensions import dataclass_transform
|
||||
|
||||
|
||||
@dataclass_transform()
|
||||
def my_dataclass[T](cls: type[T]) -> type[T]:
|
||||
return cls
|
||||
|
||||
|
||||
class A[T]:
|
||||
x: T
|
||||
|
||||
|
||||
B = my_dataclass(A[int])
|
||||
|
||||
reveal_type(B) # revealed: <class 'A[int]'>
|
||||
@@ -1132,22 +1285,28 @@ class, not to the parameter class.
|
||||
```py
|
||||
from typing_extensions import dataclass_transform
|
||||
|
||||
|
||||
@dataclass_transform()
|
||||
def hydrated_dataclass[T](target: type[T], *, frozen: bool = False):
|
||||
def decorator[U](cls: type[U]) -> type[U]:
|
||||
return cls
|
||||
|
||||
return decorator
|
||||
|
||||
|
||||
class Target:
|
||||
pass
|
||||
|
||||
|
||||
decorator = hydrated_dataclass(Target)
|
||||
reveal_type(decorator) # revealed: <decorator produced by dataclass-like function>
|
||||
|
||||
|
||||
@hydrated_dataclass(Target)
|
||||
class Model:
|
||||
x: int
|
||||
|
||||
|
||||
# Model should be a dataclass-like class with x as a field
|
||||
Model(x=1)
|
||||
reveal_type(Model.__init__) # revealed: (self: Model, x: int) -> None
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -5,12 +5,14 @@
|
||||
```py
|
||||
from dataclasses import dataclass, field
|
||||
|
||||
|
||||
@dataclass
|
||||
class Member:
|
||||
name: str
|
||||
role: str = field(default="user")
|
||||
tag: str | None = field(default=None, init=False)
|
||||
|
||||
|
||||
# revealed: (self: Member, name: str, role: str = "user") -> None
|
||||
reveal_type(Member.__init__)
|
||||
|
||||
@@ -32,11 +34,13 @@ field:
|
||||
from dataclasses import dataclass, field
|
||||
from datetime import datetime
|
||||
|
||||
|
||||
@dataclass
|
||||
class Data:
|
||||
content: list[int] = field(default_factory=list)
|
||||
timestamp: datetime = field(default_factory=datetime.now, init=False)
|
||||
|
||||
|
||||
# revealed: (self: Data, content: list[int] = ...) -> None
|
||||
reveal_type(Data.__init__)
|
||||
|
||||
@@ -57,12 +61,14 @@ If `kw_only` is set to `True`, the field can only be set using keyword arguments
|
||||
```py
|
||||
from dataclasses import dataclass, field
|
||||
|
||||
|
||||
@dataclass
|
||||
class Person:
|
||||
name: str
|
||||
age: int | None = field(default=None, kw_only=True)
|
||||
role: str = field(default="user", kw_only=True)
|
||||
|
||||
|
||||
# revealed: (self: Person, name: str, *, age: int | None = None, role: str = "user") -> None
|
||||
reveal_type(Person.__init__)
|
||||
|
||||
@@ -77,9 +83,11 @@ bob = Person("Bob", 30)
|
||||
```py
|
||||
from dataclasses import field
|
||||
|
||||
|
||||
def get_default() -> str:
|
||||
return "default"
|
||||
|
||||
|
||||
reveal_type(field(default=1)) # revealed: dataclasses.Field[Literal[1]]
|
||||
reveal_type(field(default=None)) # revealed: dataclasses.Field[None]
|
||||
reveal_type(field(default_factory=get_default)) # revealed: dataclasses.Field[str]
|
||||
@@ -97,23 +105,29 @@ from typing import TypeVar
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
|
||||
@dataclass_transform()
|
||||
def create_model(*, init: bool = True):
|
||||
def deco(cls: type[T]) -> type[T]:
|
||||
return cls
|
||||
|
||||
return deco
|
||||
|
||||
|
||||
@create_model()
|
||||
class A:
|
||||
name: str = field(init=False)
|
||||
|
||||
|
||||
# field(init=False) should be ignored for dataclass_transform without explicit field_specifiers
|
||||
reveal_type(A.__init__) # revealed: (self: A, name: str) -> None
|
||||
|
||||
|
||||
@dataclass
|
||||
class B:
|
||||
name: str = field(init=False)
|
||||
|
||||
|
||||
# Regular @dataclass should respect field(init=False)
|
||||
reveal_type(B.__init__) # revealed: (self: B) -> None
|
||||
```
|
||||
|
||||
@@ -13,9 +13,11 @@ of the decorator (which does not necessarily need to be a callable type):
|
||||
def custom_decorator(f) -> int:
|
||||
return 1
|
||||
|
||||
|
||||
@custom_decorator
|
||||
def f(x): ...
|
||||
|
||||
|
||||
reveal_type(f) # revealed: int
|
||||
```
|
||||
|
||||
@@ -26,13 +28,16 @@ More commonly, a decorator returns a modified callable type:
|
||||
```py
|
||||
from typing import Callable
|
||||
|
||||
|
||||
def ensure_positive(wrapped: Callable[[int], bool]) -> Callable[[int], bool]:
|
||||
return lambda x: wrapped(x) and x > 0
|
||||
|
||||
|
||||
@ensure_positive
|
||||
def even(x: int) -> bool:
|
||||
return x % 2 == 0
|
||||
|
||||
|
||||
reveal_type(even) # revealed: (int, /) -> bool
|
||||
reveal_type(even(4)) # revealed: bool
|
||||
```
|
||||
@@ -45,15 +50,19 @@ arguments:
|
||||
```py
|
||||
from typing import Callable
|
||||
|
||||
|
||||
def ensure_larger_than(lower_bound: int) -> Callable[[Callable[[int], bool]], Callable[[int], bool]]:
|
||||
def decorator(wrapped: Callable[[int], bool]) -> Callable[[int], bool]:
|
||||
return lambda x: wrapped(x) and x >= lower_bound
|
||||
|
||||
return decorator
|
||||
|
||||
|
||||
@ensure_larger_than(10)
|
||||
def even(x: int) -> bool:
|
||||
return x % 2 == 0
|
||||
|
||||
|
||||
reveal_type(even) # revealed: (int, /) -> bool
|
||||
reveal_type(even(14)) # revealed: bool
|
||||
```
|
||||
@@ -67,17 +76,21 @@ meaning that the decorator closest to the function definition is applied first:
|
||||
def maps_to_str(f) -> str:
|
||||
return "a"
|
||||
|
||||
|
||||
def maps_to_int(f) -> int:
|
||||
return 1
|
||||
|
||||
|
||||
def maps_to_bytes(f) -> bytes:
|
||||
return b"a"
|
||||
|
||||
|
||||
@maps_to_str
|
||||
@maps_to_int
|
||||
@maps_to_bytes
|
||||
def f(x): ...
|
||||
|
||||
|
||||
reveal_type(f) # revealed: str
|
||||
```
|
||||
|
||||
@@ -97,10 +110,12 @@ class accept_strings:
|
||||
def __call__(self, x: str | int) -> bool:
|
||||
return self.f(int(x))
|
||||
|
||||
|
||||
@accept_strings
|
||||
def even(x: int) -> bool:
|
||||
return x > 0
|
||||
|
||||
|
||||
reveal_type(even) # revealed: accept_strings
|
||||
reveal_type(even.custom_attribute) # revealed: str
|
||||
reveal_type(even("1")) # revealed: bool
|
||||
@@ -121,17 +136,21 @@ implemented using `functools.wraps`.
|
||||
from typing import Callable
|
||||
from functools import wraps
|
||||
|
||||
|
||||
def custom_decorator(f) -> Callable[[int], str]:
|
||||
@wraps(f)
|
||||
def wrapper(*args, **kwargs):
|
||||
print("Calling decorated function")
|
||||
return f(*args, **kwargs)
|
||||
|
||||
return wrapper
|
||||
|
||||
|
||||
@custom_decorator
|
||||
def f(x: int) -> str:
|
||||
return str(x)
|
||||
|
||||
|
||||
reveal_type(f) # revealed: (int, /) -> str
|
||||
```
|
||||
|
||||
@@ -140,10 +159,12 @@ reveal_type(f) # revealed: (int, /) -> str
|
||||
```py
|
||||
from functools import cache
|
||||
|
||||
|
||||
@cache
|
||||
def f(x: int) -> int:
|
||||
return x**2
|
||||
|
||||
|
||||
# revealed: _lru_cache_wrapper[int]
|
||||
reveal_type(f)
|
||||
# revealed: int
|
||||
@@ -157,6 +178,7 @@ reveal_type(f(1))
|
||||
def g(x: int) -> str:
|
||||
return "a"
|
||||
|
||||
|
||||
# TODO: This should be `Literal[g]` or `(int, /) -> str`
|
||||
reveal_type(g) # revealed: Unknown
|
||||
```
|
||||
@@ -170,6 +192,7 @@ reveal_type(g) # revealed: Unknown
|
||||
@unknown_decorator
|
||||
def f(x): ...
|
||||
|
||||
|
||||
reveal_type(f) # revealed: Unknown
|
||||
```
|
||||
|
||||
@@ -180,6 +203,7 @@ reveal_type(f) # revealed: Unknown
|
||||
@(1 + "a")
|
||||
def f(x): ...
|
||||
|
||||
|
||||
reveal_type(f) # revealed: Unknown
|
||||
```
|
||||
|
||||
@@ -188,10 +212,12 @@ reveal_type(f) # revealed: Unknown
|
||||
```py
|
||||
non_callable = 1
|
||||
|
||||
|
||||
# error: [call-non-callable] "Object of type `Literal[1]` is not callable"
|
||||
@non_callable
|
||||
def f(x): ...
|
||||
|
||||
|
||||
reveal_type(f) # revealed: Unknown
|
||||
```
|
||||
|
||||
@@ -206,10 +232,12 @@ first argument:
|
||||
def wrong_signature(f: int) -> str:
|
||||
return "a"
|
||||
|
||||
|
||||
# error: [invalid-argument-type] "Argument to function `wrong_signature` is incorrect: Expected `int`, found `def f(x) -> Unknown`"
|
||||
@wrong_signature
|
||||
def f(x): ...
|
||||
|
||||
|
||||
reveal_type(f) # revealed: str
|
||||
```
|
||||
|
||||
@@ -221,15 +249,19 @@ Decorators need to be callable with a single argument. If they are not, we emit
|
||||
def takes_two_arguments(f, g) -> str:
|
||||
return "a"
|
||||
|
||||
|
||||
# error: [missing-argument] "No argument provided for required parameter `g` of function `takes_two_arguments`"
|
||||
@takes_two_arguments
|
||||
def f(x): ...
|
||||
|
||||
|
||||
reveal_type(f) # revealed: str
|
||||
|
||||
|
||||
def takes_no_argument() -> str:
|
||||
return "a"
|
||||
|
||||
|
||||
# error: [too-many-positional-arguments] "Too many positional arguments to function `takes_no_argument`: expected 0, got 1"
|
||||
@takes_no_argument
|
||||
def g(x): ...
|
||||
@@ -243,6 +275,7 @@ Class decorator calls are validated, emitting diagnostics for invalid arguments:
|
||||
def takes_int(x: int) -> int:
|
||||
return x
|
||||
|
||||
|
||||
# error: [invalid-argument-type]
|
||||
@takes_int
|
||||
class Foo: ...
|
||||
@@ -262,10 +295,12 @@ A decorator can enforce type constraints on the class being decorated:
|
||||
def decorator(cls: type[int]) -> type[int]:
|
||||
return cls
|
||||
|
||||
|
||||
# error: [invalid-argument-type]
|
||||
@decorator
|
||||
class Baz: ...
|
||||
|
||||
|
||||
# TODO: the revealed type should ideally be `type[int]` (the decorator's return type)
|
||||
reveal_type(Baz) # revealed: <class 'Baz'>
|
||||
```
|
||||
|
||||
@@ -12,6 +12,7 @@ When a class defines `__eq__` and `__lt__`, the decorator synthesizes `__le__`,
|
||||
```py
|
||||
from functools import total_ordering
|
||||
|
||||
|
||||
@total_ordering
|
||||
class Student:
|
||||
def __init__(self, grade: int):
|
||||
@@ -25,6 +26,7 @@ class Student:
|
||||
def __lt__(self, other: "Student") -> bool:
|
||||
return self.grade < other.grade
|
||||
|
||||
|
||||
s1 = Student(85)
|
||||
s2 = Student(90)
|
||||
|
||||
@@ -47,6 +49,7 @@ other than the class itself:
|
||||
```py
|
||||
from functools import total_ordering
|
||||
|
||||
|
||||
@total_ordering
|
||||
class Comparable:
|
||||
def __init__(self, value: int):
|
||||
@@ -66,6 +69,7 @@ class Comparable:
|
||||
return self.value < other
|
||||
return NotImplemented
|
||||
|
||||
|
||||
a = Comparable(10)
|
||||
b = Comparable(20)
|
||||
|
||||
@@ -88,6 +92,7 @@ overridden.
|
||||
```py
|
||||
from functools import total_ordering
|
||||
|
||||
|
||||
@total_ordering
|
||||
class MultiSig:
|
||||
def __init__(self, value: int):
|
||||
@@ -95,13 +100,16 @@ class MultiSig:
|
||||
|
||||
def __eq__(self, other: object) -> bool:
|
||||
return True
|
||||
|
||||
# __lt__ accepts `object` (highest priority, used as root)
|
||||
def __lt__(self, other: object) -> bool:
|
||||
return True
|
||||
|
||||
# __gt__ only accepts `MultiSig` (not overridden by decorator)
|
||||
def __gt__(self, other: "MultiSig") -> bool:
|
||||
return True
|
||||
|
||||
|
||||
a = MultiSig(10)
|
||||
b = MultiSig(20)
|
||||
|
||||
@@ -125,6 +133,7 @@ all overloads:
|
||||
from functools import total_ordering
|
||||
from typing import overload
|
||||
|
||||
|
||||
@total_ordering
|
||||
class Flexible:
|
||||
def __init__(self, value: int):
|
||||
@@ -142,6 +151,7 @@ class Flexible:
|
||||
return self.value < other.value
|
||||
return self.value < other
|
||||
|
||||
|
||||
a = Flexible(10)
|
||||
b = Flexible(20)
|
||||
|
||||
@@ -165,6 +175,7 @@ When a class defines `__eq__` and `__gt__`, the decorator synthesizes `__lt__`,
|
||||
```py
|
||||
from functools import total_ordering
|
||||
|
||||
|
||||
@total_ordering
|
||||
class Priority:
|
||||
def __init__(self, level: int):
|
||||
@@ -178,6 +189,7 @@ class Priority:
|
||||
def __gt__(self, other: "Priority") -> bool:
|
||||
return self.level > other.level
|
||||
|
||||
|
||||
p1 = Priority(1)
|
||||
p2 = Priority(2)
|
||||
|
||||
@@ -199,6 +211,7 @@ A class only needs to define a single comparison method. The `__eq__` method can
|
||||
```py
|
||||
from functools import total_ordering
|
||||
|
||||
|
||||
@total_ordering
|
||||
class Score:
|
||||
def __init__(self, value: int):
|
||||
@@ -207,6 +220,7 @@ class Score:
|
||||
def __lt__(self, other: "Score") -> bool:
|
||||
return self.value < other.value
|
||||
|
||||
|
||||
s1 = Score(85)
|
||||
s2 = Score(90)
|
||||
|
||||
@@ -226,10 +240,12 @@ The decorator also works when the ordering method is inherited from a superclass
|
||||
```py
|
||||
from functools import total_ordering
|
||||
|
||||
|
||||
class Base:
|
||||
def __lt__(self, other: "Base") -> bool:
|
||||
return True
|
||||
|
||||
|
||||
@total_ordering
|
||||
class Child(Base):
|
||||
def __eq__(self, other: object) -> bool:
|
||||
@@ -237,6 +253,7 @@ class Child(Base):
|
||||
return NotImplemented
|
||||
return True
|
||||
|
||||
|
||||
c1 = Child()
|
||||
c2 = Child()
|
||||
|
||||
@@ -256,16 +273,19 @@ over the locally-defined `__gt__`:
|
||||
from functools import total_ordering
|
||||
from typing import Literal
|
||||
|
||||
|
||||
class Base:
|
||||
def __lt__(self, other: "Base") -> Literal[True]:
|
||||
return True
|
||||
|
||||
|
||||
@total_ordering
|
||||
class Child(Base):
|
||||
# __gt__ is defined locally, but __lt__ (inherited) takes precedence
|
||||
def __gt__(self, other: "Child") -> Literal[False]:
|
||||
return False
|
||||
|
||||
|
||||
c1 = Child()
|
||||
c2 = Child()
|
||||
|
||||
@@ -290,6 +310,7 @@ We use a narrower return type (`Literal[True]`) to verify that the explicit meth
|
||||
from functools import total_ordering
|
||||
from typing import Literal
|
||||
|
||||
|
||||
@total_ordering
|
||||
class Temperature:
|
||||
def __init__(self, celsius: float):
|
||||
@@ -301,6 +322,7 @@ class Temperature:
|
||||
def __gt__(self, other: "Temperature") -> Literal[True]:
|
||||
return True
|
||||
|
||||
|
||||
t1 = Temperature(20.0)
|
||||
t2 = Temperature(25.0)
|
||||
|
||||
@@ -321,6 +343,7 @@ The decorator works with `@dataclass`:
|
||||
from dataclasses import dataclass
|
||||
from functools import total_ordering
|
||||
|
||||
|
||||
@total_ordering
|
||||
@dataclass
|
||||
class Point:
|
||||
@@ -330,6 +353,7 @@ class Point:
|
||||
def __lt__(self, other: "Point") -> bool:
|
||||
return (self.x, self.y) < (other.x, other.y)
|
||||
|
||||
|
||||
p1 = Point(1, 2)
|
||||
p2 = Point(3, 4)
|
||||
|
||||
@@ -353,11 +377,13 @@ a diagnostic is emitted at the decorator site:
|
||||
```py
|
||||
from functools import total_ordering
|
||||
|
||||
|
||||
@total_ordering # error: [invalid-total-ordering]
|
||||
class NoOrdering:
|
||||
def __eq__(self, other: object) -> bool:
|
||||
return True
|
||||
|
||||
|
||||
n1 = NoOrdering()
|
||||
n2 = NoOrdering()
|
||||
|
||||
@@ -384,6 +410,7 @@ class NoDecorator:
|
||||
def __lt__(self, other: "NoDecorator") -> bool:
|
||||
return self.value < other.value
|
||||
|
||||
|
||||
n1 = NoDecorator(1)
|
||||
n2 = NoDecorator(2)
|
||||
|
||||
@@ -416,6 +443,7 @@ simplifies to `int`:
|
||||
```py
|
||||
from functools import total_ordering
|
||||
|
||||
|
||||
@total_ordering
|
||||
class IntReturn:
|
||||
def __init__(self, value: int):
|
||||
@@ -429,6 +457,7 @@ class IntReturn:
|
||||
def __lt__(self, other: "IntReturn") -> int:
|
||||
return self.value - other.value
|
||||
|
||||
|
||||
a = IntReturn(10)
|
||||
b = IntReturn(20)
|
||||
|
||||
@@ -447,6 +476,7 @@ When the root method returns a type that is not a supertype of `bool`, the union
|
||||
```py
|
||||
from functools import total_ordering
|
||||
|
||||
|
||||
@total_ordering
|
||||
class StrReturn:
|
||||
def __init__(self, value: str):
|
||||
@@ -460,6 +490,7 @@ class StrReturn:
|
||||
def __lt__(self, other: "StrReturn") -> str:
|
||||
return self.value
|
||||
|
||||
|
||||
a = StrReturn("a")
|
||||
b = StrReturn("b")
|
||||
|
||||
@@ -480,10 +511,12 @@ performed:
|
||||
```py
|
||||
from functools import total_ordering
|
||||
|
||||
|
||||
class NoOrderingMethod:
|
||||
def __eq__(self, other: object) -> bool:
|
||||
return True
|
||||
|
||||
|
||||
# error: [invalid-total-ordering]
|
||||
InvalidOrderedClass = total_ordering(NoOrderingMethod)
|
||||
```
|
||||
@@ -493,6 +526,7 @@ When the class does define an ordering method, no error is emitted:
|
||||
```py
|
||||
from functools import total_ordering
|
||||
|
||||
|
||||
class HasOrderingMethod:
|
||||
def __eq__(self, other: object) -> bool:
|
||||
return True
|
||||
@@ -500,6 +534,7 @@ class HasOrderingMethod:
|
||||
def __lt__(self, other: "HasOrderingMethod") -> bool:
|
||||
return True
|
||||
|
||||
|
||||
# No error (class defines `__lt__`).
|
||||
ValidOrderedClass = total_ordering(HasOrderingMethod)
|
||||
reveal_type(ValidOrderedClass) # revealed: type[HasOrderingMethod]
|
||||
@@ -512,9 +547,11 @@ When `total_ordering` is called on a class created with `type()`, the same valid
|
||||
```py
|
||||
from functools import total_ordering
|
||||
|
||||
|
||||
def lt_impl(self, other) -> bool:
|
||||
return True
|
||||
|
||||
|
||||
# No error: the functional class defines `__lt__` in its namespace
|
||||
ValidFunctional = total_ordering(type("ValidFunctional", (), {"__lt__": lt_impl}))
|
||||
|
||||
@@ -531,21 +568,26 @@ correctly detects it:
|
||||
```py
|
||||
from functools import total_ordering
|
||||
|
||||
|
||||
def lt_impl(self, other) -> bool:
|
||||
return True
|
||||
|
||||
|
||||
def eq_impl(self, other) -> bool:
|
||||
return True
|
||||
|
||||
|
||||
# Functional class with __lt__ method
|
||||
OrderedBase = type("OrderedBase", (), {"__lt__": lt_impl})
|
||||
|
||||
|
||||
# A class inheriting from OrderedBase gets the ordering method
|
||||
@total_ordering
|
||||
class Ordered(OrderedBase):
|
||||
def __eq__(self, other: object) -> bool:
|
||||
return True
|
||||
|
||||
|
||||
o1 = Ordered()
|
||||
o2 = Ordered()
|
||||
|
||||
@@ -566,6 +608,7 @@ from functools import total_ordering
|
||||
# Dynamic class without ordering methods (invalid for @total_ordering)
|
||||
NoOrderBase = type("NoOrderBase", (), {})
|
||||
|
||||
|
||||
@total_ordering # error: [invalid-total-ordering]
|
||||
class NoOrder(NoOrderBase):
|
||||
def __eq__(self, other: object) -> bool:
|
||||
@@ -582,6 +625,7 @@ passed to `@total_ordering`:
|
||||
from functools import total_ordering
|
||||
from typing import Any
|
||||
|
||||
|
||||
def f(ns: dict[str, Any]):
|
||||
# Dynamic class with dynamic namespace - might have ordering methods
|
||||
DynamicBase = type("DynamicBase", (), ns)
|
||||
|
||||
@@ -21,9 +21,11 @@ reveal_type(x) # revealed: Unknown
|
||||
# error: [unresolved-reference]
|
||||
reveal_type(y) # revealed: Unknown
|
||||
|
||||
|
||||
def cond() -> bool:
|
||||
return True
|
||||
|
||||
|
||||
b = 1
|
||||
if cond():
|
||||
del b
|
||||
@@ -42,17 +44,21 @@ reveal_type(c) # revealed: Literal[2]
|
||||
|
||||
d = [1, 2, 3]
|
||||
|
||||
|
||||
def delete():
|
||||
del d # error: [unresolved-reference] "Name `d` used when not defined"
|
||||
|
||||
|
||||
delete()
|
||||
reveal_type(d) # revealed: list[Unknown | int]
|
||||
|
||||
|
||||
def delete_element():
|
||||
# When the `del` target isn't a name, it doesn't force local resolution.
|
||||
del d[0]
|
||||
print(d)
|
||||
|
||||
|
||||
def delete_global():
|
||||
global d
|
||||
del d
|
||||
@@ -60,10 +66,12 @@ def delete_global():
|
||||
# be careful about false positives if `d` got reinitialized somehow in between the two `del`s.
|
||||
del d
|
||||
|
||||
|
||||
delete_global()
|
||||
# Again, the variable should have been removed, but we don't check it.
|
||||
reveal_type(d) # revealed: list[Unknown | int]
|
||||
|
||||
|
||||
def delete_nonlocal():
|
||||
e = 2
|
||||
|
||||
@@ -86,6 +94,7 @@ local error:
|
||||
```py
|
||||
x = 1
|
||||
|
||||
|
||||
def foo():
|
||||
print(x) # error: [unresolved-reference] "Name `x` used when not defined"
|
||||
if False:
|
||||
@@ -104,11 +113,14 @@ However, with `global x` in `foo`, `print(x)` in `bar` resolves in the global sc
|
||||
```py
|
||||
x = 1
|
||||
|
||||
|
||||
def foo():
|
||||
global x
|
||||
|
||||
def bar():
|
||||
# allowed, refers to `x` in the global scope
|
||||
reveal_type(x) # revealed: Literal[1]
|
||||
|
||||
bar()
|
||||
del x # allowed, deletes `x` in the global scope (though we don't track that)
|
||||
```
|
||||
@@ -119,11 +131,14 @@ refer to:
|
||||
```py
|
||||
def enclosing():
|
||||
x = 2
|
||||
|
||||
def foo():
|
||||
nonlocal x
|
||||
|
||||
def bar():
|
||||
# allowed, refers to `x` in `enclosing`
|
||||
reveal_type(x) # revealed: Literal[2]
|
||||
|
||||
bar()
|
||||
del x # allowed, deletes `x` in `enclosing` (though we don't track that)
|
||||
```
|
||||
@@ -141,6 +156,7 @@ assignment, and the attribute type will be the originally declared type.
|
||||
class C:
|
||||
x: int = 1
|
||||
|
||||
|
||||
c = C()
|
||||
del c.x
|
||||
reveal_type(c.x) # revealed: int
|
||||
@@ -160,6 +176,7 @@ reveal_type(c.x) # revealed: int
|
||||
class C:
|
||||
x: int = 1
|
||||
|
||||
|
||||
c = C()
|
||||
reveal_type(c.x) # revealed: int
|
||||
|
||||
@@ -202,17 +219,21 @@ from typing import Protocol, TypeVar
|
||||
|
||||
KT = TypeVar("KT")
|
||||
|
||||
|
||||
class CanDelItem(Protocol[KT]):
|
||||
def __delitem__(self, k: KT, /) -> None: ...
|
||||
|
||||
|
||||
def f(x: CanDelItem[int], k: int):
|
||||
# This should be valid - the object has __delitem__
|
||||
del x[k]
|
||||
|
||||
|
||||
class OnlyDelItem:
|
||||
def __delitem__(self, key: int) -> None:
|
||||
pass
|
||||
|
||||
|
||||
d = OnlyDelItem()
|
||||
del d[0] # OK
|
||||
|
||||
@@ -229,6 +250,7 @@ class OnlyGetItem:
|
||||
def __getitem__(self, key: int) -> str:
|
||||
return "value"
|
||||
|
||||
|
||||
g = OnlyGetItem()
|
||||
reveal_type(g[0]) # revealed: str
|
||||
|
||||
@@ -247,18 +269,22 @@ a valid instance of that TypedDict type. However, deleting `NotRequired` keys (o
|
||||
```py
|
||||
from typing_extensions import TypedDict, NotRequired
|
||||
|
||||
|
||||
class Movie(TypedDict):
|
||||
name: str
|
||||
year: int
|
||||
|
||||
|
||||
class PartialMovie(TypedDict, total=False):
|
||||
name: str
|
||||
year: int
|
||||
|
||||
|
||||
class MixedMovie(TypedDict):
|
||||
name: str
|
||||
year: NotRequired[int]
|
||||
|
||||
|
||||
m: Movie = {"name": "Blade Runner", "year": 1982}
|
||||
p: PartialMovie = {"name": "Test"}
|
||||
mixed: MixedMovie = {"name": "Test"}
|
||||
|
||||
@@ -10,30 +10,36 @@ classes. Uses of these items should subsequently produce a warning.
|
||||
```py
|
||||
from typing_extensions import deprecated
|
||||
|
||||
|
||||
@deprecated("use OtherClass")
|
||||
def myfunc(x: int): ...
|
||||
|
||||
|
||||
myfunc(1) # error: [deprecated] "use OtherClass"
|
||||
```
|
||||
|
||||
```py
|
||||
from typing_extensions import deprecated
|
||||
|
||||
|
||||
@deprecated("use BetterClass")
|
||||
class MyClass: ...
|
||||
|
||||
|
||||
MyClass() # error: [deprecated] "use BetterClass"
|
||||
```
|
||||
|
||||
```py
|
||||
from typing_extensions import deprecated
|
||||
|
||||
|
||||
class MyClass:
|
||||
@deprecated("use something else")
|
||||
def afunc(): ...
|
||||
@deprecated("don't use this!")
|
||||
def amethod(self): ...
|
||||
|
||||
|
||||
MyClass.afunc() # error: [deprecated] "use something else"
|
||||
MyClass().amethod() # error: [deprecated] "don't use this!"
|
||||
```
|
||||
@@ -59,18 +65,22 @@ runtime behavior.
|
||||
```py
|
||||
from typing_extensions import deprecated
|
||||
|
||||
|
||||
@deprecated # error: [invalid-argument-type] "LiteralString"
|
||||
def invalid_deco(): ...
|
||||
|
||||
|
||||
invalid_deco() # error: [missing-argument]
|
||||
```
|
||||
|
||||
```py
|
||||
from typing_extensions import deprecated
|
||||
|
||||
|
||||
@deprecated() # error: [missing-argument] "message"
|
||||
def invalid_deco(): ...
|
||||
|
||||
|
||||
invalid_deco()
|
||||
```
|
||||
|
||||
@@ -82,9 +92,11 @@ from typing_extensions import deprecated
|
||||
|
||||
x = "message"
|
||||
|
||||
|
||||
@deprecated(x)
|
||||
def invalid_deco(): ...
|
||||
|
||||
|
||||
invalid_deco() # error: [deprecated] "message"
|
||||
```
|
||||
|
||||
@@ -93,12 +105,15 @@ However sufficiently opaque LiteralStrings we can't resolve, and so we lose the
|
||||
```py
|
||||
from typing_extensions import deprecated, LiteralString
|
||||
|
||||
|
||||
def opaque() -> LiteralString:
|
||||
return "message"
|
||||
|
||||
|
||||
@deprecated(opaque())
|
||||
def valid_deco(): ...
|
||||
|
||||
|
||||
valid_deco() # error: [deprecated]
|
||||
```
|
||||
|
||||
@@ -108,12 +123,15 @@ LiteralString, so we can/should emit a diagnostic for this:
|
||||
```py
|
||||
from typing_extensions import deprecated
|
||||
|
||||
|
||||
def opaque() -> str:
|
||||
return "message"
|
||||
|
||||
|
||||
@deprecated(opaque()) # error: [invalid-argument-type] "LiteralString"
|
||||
def dubious_deco(): ...
|
||||
|
||||
|
||||
dubious_deco()
|
||||
```
|
||||
|
||||
@@ -122,9 +140,11 @@ Although we have no use for the other arguments, we should still error if they'r
|
||||
```py
|
||||
from typing_extensions import deprecated
|
||||
|
||||
|
||||
@deprecated("some message", dsfsdf="whatever") # error: [unknown-argument] "dsfsdf"
|
||||
def invalid_deco(): ...
|
||||
|
||||
|
||||
invalid_deco()
|
||||
```
|
||||
|
||||
@@ -133,9 +153,11 @@ And we should always handle correct ones fine.
|
||||
```py
|
||||
from typing_extensions import deprecated
|
||||
|
||||
|
||||
@deprecated("some message", category=DeprecationWarning, stacklevel=1)
|
||||
def valid_deco(): ...
|
||||
|
||||
|
||||
valid_deco() # error: [deprecated] "some message"
|
||||
```
|
||||
|
||||
@@ -155,11 +177,13 @@ python-version = "3.13"
|
||||
import warnings
|
||||
import typing_extensions
|
||||
|
||||
|
||||
@warnings.deprecated("nope")
|
||||
def func1(): ...
|
||||
@typing_extensions.deprecated("nada")
|
||||
def func2(): ...
|
||||
|
||||
|
||||
func1() # error: [deprecated] "nope"
|
||||
func2() # error: [deprecated] "nada"
|
||||
```
|
||||
@@ -176,9 +200,11 @@ shouldn't produce a warning.
|
||||
```py
|
||||
from typing_extensions import deprecated
|
||||
|
||||
|
||||
@deprecated("Use OtherType instead")
|
||||
class DeprType: ...
|
||||
|
||||
|
||||
@deprecated("Use other_func instead")
|
||||
def depr_func(): ...
|
||||
```
|
||||
@@ -194,8 +220,10 @@ from module import DeprType, depr_func
|
||||
DeprType() # error: [deprecated] "Use OtherType instead"
|
||||
depr_func() # error: [deprecated] "Use other_func instead"
|
||||
|
||||
|
||||
def higher_order(x): ...
|
||||
|
||||
|
||||
# TODO: these diagnostics ideally shouldn't fire since we warn on the import
|
||||
higher_order(DeprType) # error: [deprecated] "Use OtherType instead"
|
||||
higher_order(depr_func) # error: [deprecated] "Use other_func instead"
|
||||
@@ -215,9 +243,11 @@ a warning.
|
||||
```py
|
||||
from typing_extensions import deprecated
|
||||
|
||||
|
||||
@deprecated("Use OtherType instead")
|
||||
class DeprType: ...
|
||||
|
||||
|
||||
@deprecated("Use other_func instead")
|
||||
def depr_func(): ...
|
||||
```
|
||||
@@ -230,8 +260,10 @@ import module
|
||||
module.DeprType() # error: [deprecated] "Use OtherType instead"
|
||||
module.depr_func() # error: [deprecated] "Use other_func instead"
|
||||
|
||||
|
||||
def higher_order(x): ...
|
||||
|
||||
|
||||
higher_order(module.DeprType) # error: [deprecated] "Use OtherType instead"
|
||||
higher_order(module.depr_func) # error: [deprecated] "Use other_func instead"
|
||||
|
||||
@@ -248,9 +280,11 @@ If the items are instead star-imported, then the actual uses should warn.
|
||||
```py
|
||||
from typing_extensions import deprecated
|
||||
|
||||
|
||||
@deprecated("Use OtherType instead")
|
||||
class DeprType: ...
|
||||
|
||||
|
||||
@deprecated("Use other_func instead")
|
||||
def depr_func(): ...
|
||||
```
|
||||
@@ -263,8 +297,10 @@ from module import *
|
||||
DeprType() # error: [deprecated] "Use OtherType instead"
|
||||
depr_func() # error: [deprecated] "Use other_func instead"
|
||||
|
||||
|
||||
def higher_order(x): ...
|
||||
|
||||
|
||||
higher_order(DeprType) # error: [deprecated] "Use OtherType instead"
|
||||
higher_order(depr_func) # error: [deprecated] "Use other_func instead"
|
||||
|
||||
@@ -281,12 +317,15 @@ redundant and annoying.
|
||||
```py
|
||||
from typing_extensions import deprecated
|
||||
|
||||
|
||||
@deprecated("Use OtherType instead")
|
||||
class DeprType: ...
|
||||
|
||||
|
||||
@deprecated("Use other_func instead")
|
||||
def depr_func(): ...
|
||||
|
||||
|
||||
alias_func = depr_func # error: [deprecated] "Use other_func instead"
|
||||
AliasClass = DeprType # error: [deprecated] "Use OtherType instead"
|
||||
|
||||
@@ -303,6 +342,7 @@ diagnostic.
|
||||
```py
|
||||
from typing_extensions import deprecated
|
||||
|
||||
|
||||
class MyInt:
|
||||
def __init__(self, val):
|
||||
self.val = val
|
||||
@@ -311,6 +351,7 @@ class MyInt:
|
||||
def __add__(self, other):
|
||||
return MyInt(self.val + other.val)
|
||||
|
||||
|
||||
x = MyInt(1)
|
||||
y = MyInt(2)
|
||||
z = x + y # TODO error: [deprecated] "MyInt `+` support is broken"
|
||||
@@ -324,6 +365,7 @@ Overloads can be deprecated, but only trigger warnings when invoked.
|
||||
from typing_extensions import deprecated
|
||||
from typing_extensions import overload
|
||||
|
||||
|
||||
@overload
|
||||
@deprecated("strings are no longer supported")
|
||||
def f(x: str): ...
|
||||
@@ -332,6 +374,7 @@ def f(x: int): ...
|
||||
def f(x):
|
||||
print(x)
|
||||
|
||||
|
||||
f(1)
|
||||
f("hello") # TODO: error: [deprecated] "strings are no longer supported"
|
||||
```
|
||||
@@ -342,6 +385,7 @@ If the actual impl is deprecated, the deprecation always fires.
|
||||
from typing_extensions import deprecated
|
||||
from typing_extensions import overload
|
||||
|
||||
|
||||
@overload
|
||||
def f(x: str): ...
|
||||
@overload
|
||||
@@ -350,6 +394,7 @@ def f(x: int): ...
|
||||
def f(x):
|
||||
print(x)
|
||||
|
||||
|
||||
f(1) # error: [deprecated] "unusable"
|
||||
f("hello") # error: [deprecated] "unusable"
|
||||
```
|
||||
|
||||
@@ -16,6 +16,7 @@ descriptor that returns a constant value:
|
||||
```py
|
||||
from typing import Literal
|
||||
|
||||
|
||||
class Ten:
|
||||
def __get__(self, instance: object, owner: type | None = None) -> Literal[10]:
|
||||
return 10
|
||||
@@ -23,9 +24,11 @@ class Ten:
|
||||
def __set__(self, instance: object, value: Literal[10]) -> None:
|
||||
pass
|
||||
|
||||
|
||||
class C:
|
||||
ten: Ten = Ten()
|
||||
|
||||
|
||||
c = C()
|
||||
|
||||
reveal_type(c.ten) # revealed: Literal[10]
|
||||
@@ -66,9 +69,11 @@ class FlexibleInt:
|
||||
def __set__(self, instance: object, value: int | str) -> None:
|
||||
self._value = int(value)
|
||||
|
||||
|
||||
class C:
|
||||
flexible_int: FlexibleInt = FlexibleInt()
|
||||
|
||||
|
||||
c = C()
|
||||
|
||||
reveal_type(c.flexible_int) # revealed: int | None
|
||||
@@ -97,6 +102,7 @@ non-data descriptors.
|
||||
```py
|
||||
from typing import Literal
|
||||
|
||||
|
||||
class DataDescriptor:
|
||||
def __get__(self, instance: object, owner: type | None = None) -> Literal["data"]:
|
||||
return "data"
|
||||
@@ -104,10 +110,12 @@ class DataDescriptor:
|
||||
def __set__(self, instance: object, value: int) -> None:
|
||||
pass
|
||||
|
||||
|
||||
class NonDataDescriptor:
|
||||
def __get__(self, instance: object, owner: type | None = None) -> Literal["non-data"]:
|
||||
return "non-data"
|
||||
|
||||
|
||||
class C:
|
||||
data_descriptor = DataDescriptor()
|
||||
non_data_descriptor = NonDataDescriptor()
|
||||
@@ -123,6 +131,7 @@ class C:
|
||||
# So it is possible to override them.
|
||||
self.non_data_descriptor = 1
|
||||
|
||||
|
||||
c = C()
|
||||
|
||||
reveal_type(c.data_descriptor) # revealed: Unknown | Literal["data"]
|
||||
@@ -148,6 +157,7 @@ all possible results accordingly. We start by defining a data and a non-data des
|
||||
```py
|
||||
from typing import Literal
|
||||
|
||||
|
||||
class DataDescriptor:
|
||||
def __get__(self, instance: object, owner: type | None = None) -> Literal["data"]:
|
||||
return "data"
|
||||
@@ -155,6 +165,7 @@ class DataDescriptor:
|
||||
def __set__(self, instance: object, value: int) -> None:
|
||||
pass
|
||||
|
||||
|
||||
class NonDataDescriptor:
|
||||
def __get__(self, instance: object, owner: type | None = None) -> Literal["non-data"]:
|
||||
return "non-data"
|
||||
@@ -186,8 +197,10 @@ descriptor here:
|
||||
class C2:
|
||||
def f(self):
|
||||
self.attr = "normal"
|
||||
|
||||
attr = NonDataDescriptor()
|
||||
|
||||
|
||||
reveal_type(C2().attr) # revealed: Unknown | Literal["non-data", "normal"]
|
||||
|
||||
# Assignments always go to the instance attribute in this case
|
||||
@@ -201,14 +214,17 @@ Descriptors only work when used as class variables. When put in instances, they
|
||||
```py
|
||||
from typing import Literal
|
||||
|
||||
|
||||
class Ten:
|
||||
def __get__(self, instance: object, owner: type | None = None) -> Literal[10]:
|
||||
return 10
|
||||
|
||||
|
||||
class C:
|
||||
def __init__(self):
|
||||
self.ten: Ten = Ten()
|
||||
|
||||
|
||||
reveal_type(C().ten) # revealed: Ten
|
||||
|
||||
C().ten = Ten()
|
||||
@@ -233,6 +249,7 @@ To verify this, we define a data and a non-data descriptor:
|
||||
```py
|
||||
from typing import Literal, Any
|
||||
|
||||
|
||||
class DataDescriptor:
|
||||
def __get__(self, instance: object, owner: type | None = None) -> Literal["data"]:
|
||||
return "data"
|
||||
@@ -240,6 +257,7 @@ class DataDescriptor:
|
||||
def __set__(self, instance: object, value: int) -> None:
|
||||
pass
|
||||
|
||||
|
||||
class NonDataDescriptor:
|
||||
def __get__(self, instance: object, owner: type | None = None) -> Literal["non-data"]:
|
||||
return "non-data"
|
||||
@@ -253,10 +271,12 @@ class Meta1(type):
|
||||
meta_data_descriptor: DataDescriptor = DataDescriptor()
|
||||
meta_non_data_descriptor: NonDataDescriptor = NonDataDescriptor()
|
||||
|
||||
|
||||
class C1(metaclass=Meta1):
|
||||
class_data_descriptor: DataDescriptor = DataDescriptor()
|
||||
class_non_data_descriptor: NonDataDescriptor = NonDataDescriptor()
|
||||
|
||||
|
||||
reveal_type(C1.meta_data_descriptor) # revealed: Literal["data"]
|
||||
reveal_type(C1.meta_non_data_descriptor) # revealed: Literal["non-data"]
|
||||
|
||||
@@ -293,6 +313,7 @@ class Meta2(type):
|
||||
meta_data_descriptor1: DataDescriptor = DataDescriptor()
|
||||
meta_data_descriptor2: DataDescriptor = DataDescriptor()
|
||||
|
||||
|
||||
class ClassLevelDataDescriptor:
|
||||
def __get__(self, instance: object, owner: type | None = None) -> Literal["class level data descriptor"]:
|
||||
return "class level data descriptor"
|
||||
@@ -300,10 +321,12 @@ class ClassLevelDataDescriptor:
|
||||
def __set__(self, instance: object, value: str) -> None:
|
||||
pass
|
||||
|
||||
|
||||
class C2(metaclass=Meta2):
|
||||
meta_data_descriptor1: Literal["value on class"] = "value on class"
|
||||
meta_data_descriptor2: ClassLevelDataDescriptor = ClassLevelDataDescriptor()
|
||||
|
||||
|
||||
reveal_type(C2.meta_data_descriptor1) # revealed: Literal["data"]
|
||||
reveal_type(C2.meta_data_descriptor2) # revealed: Literal["data"]
|
||||
|
||||
@@ -326,12 +349,14 @@ class Meta3(type):
|
||||
meta_non_data_descriptor1: NonDataDescriptor = NonDataDescriptor()
|
||||
meta_non_data_descriptor2: NonDataDescriptor = NonDataDescriptor()
|
||||
|
||||
|
||||
class C3(metaclass=Meta3):
|
||||
meta_attribute1: Literal["value on class"] = "value on class"
|
||||
meta_attribute2: ClassLevelDataDescriptor = ClassLevelDataDescriptor()
|
||||
meta_non_data_descriptor1: Literal["value on class"] = "value on class"
|
||||
meta_non_data_descriptor2: ClassLevelDataDescriptor = ClassLevelDataDescriptor()
|
||||
|
||||
|
||||
reveal_type(C3.meta_attribute1) # revealed: Literal["value on class"]
|
||||
reveal_type(C3.meta_attribute2) # revealed: Literal["class level data descriptor"]
|
||||
reveal_type(C3.meta_non_data_descriptor1) # revealed: Literal["value on class"]
|
||||
@@ -346,8 +371,10 @@ class Meta4(type):
|
||||
meta_attribute: Literal["value on metaclass"] = "value on metaclass"
|
||||
meta_non_data_descriptor: NonDataDescriptor = NonDataDescriptor()
|
||||
|
||||
|
||||
class C4(metaclass=Meta4): ...
|
||||
|
||||
|
||||
reveal_type(C4.meta_attribute) # revealed: Literal["value on metaclass"]
|
||||
reveal_type(C4.meta_non_data_descriptor) # revealed: Literal["non-data"]
|
||||
```
|
||||
@@ -386,6 +413,7 @@ metaclass attribute (unless it's a data descriptor, which always takes precedenc
|
||||
```py
|
||||
from typing import Any
|
||||
|
||||
|
||||
def _(flag: bool):
|
||||
class Meta6(type):
|
||||
attribute1: DataDescriptor = DataDescriptor()
|
||||
@@ -448,6 +476,7 @@ when it is accessed on an instance. A real-world example of this is the `__get__
|
||||
```py
|
||||
from typing_extensions import Literal, LiteralString, overload
|
||||
|
||||
|
||||
class Descriptor:
|
||||
@overload
|
||||
def __get__(self, instance: None, owner: type, /) -> Literal["called on class object"]: ...
|
||||
@@ -459,9 +488,11 @@ class Descriptor:
|
||||
else:
|
||||
return "called on class object"
|
||||
|
||||
|
||||
class C:
|
||||
d: Descriptor = Descriptor()
|
||||
|
||||
|
||||
reveal_type(C.d) # revealed: Literal["called on class object"]
|
||||
|
||||
reveal_type(C().d) # revealed: Literal["called on instance"]
|
||||
@@ -479,13 +510,16 @@ class SomeCallable:
|
||||
def __call__(self, x: int) -> str:
|
||||
return "a"
|
||||
|
||||
|
||||
class Descriptor:
|
||||
def __get__(self, instance: object, owner: type | None = None) -> SomeCallable:
|
||||
return SomeCallable()
|
||||
|
||||
|
||||
class B:
|
||||
__call__: Descriptor = Descriptor()
|
||||
|
||||
|
||||
b_instance = B()
|
||||
reveal_type(b_instance(1)) # revealed: str
|
||||
|
||||
@@ -512,6 +546,7 @@ class C:
|
||||
def name(self, value: str | None) -> None:
|
||||
self._value = value
|
||||
|
||||
|
||||
c = C()
|
||||
|
||||
reveal_type(c._name) # revealed: str | None
|
||||
@@ -550,6 +585,7 @@ class Base:
|
||||
def other(self, v: float) -> None:
|
||||
self.value = v
|
||||
|
||||
|
||||
class Derived(Base):
|
||||
@property
|
||||
def other(self) -> float:
|
||||
@@ -573,6 +609,7 @@ class DontAssignToMe:
|
||||
@property
|
||||
def immutable(self): ...
|
||||
|
||||
|
||||
# error: [invalid-assignment]
|
||||
DontAssignToMe().immutable = "the properties, they are a-changing"
|
||||
```
|
||||
@@ -595,6 +632,7 @@ class C:
|
||||
def get_name(cls) -> str:
|
||||
return cls.__name__
|
||||
|
||||
|
||||
c1 = C.factory("test") # okay
|
||||
|
||||
reveal_type(c1) # revealed: C
|
||||
@@ -612,6 +650,7 @@ class C:
|
||||
def helper(value: str) -> str:
|
||||
return value
|
||||
|
||||
|
||||
reveal_type(C.helper("42")) # revealed: str
|
||||
c = C()
|
||||
reveal_type(c.helper("string")) # revealed: str
|
||||
@@ -628,9 +667,11 @@ import types
|
||||
from inspect import getattr_static
|
||||
from ty_extensions import static_assert, is_subtype_of, TypeOf
|
||||
|
||||
|
||||
def f(x: object) -> str:
|
||||
return "a"
|
||||
|
||||
|
||||
reveal_type(f) # revealed: def f(x: object) -> str
|
||||
reveal_type(f.__get__) # revealed: <method-wrapper '__get__' of function 'f'>
|
||||
static_assert(is_subtype_of(TypeOf[f.__get__], types.MethodWrapperType))
|
||||
@@ -655,6 +696,7 @@ We can also bind the free function `f` to an instance of a class `C`:
|
||||
```py
|
||||
class C: ...
|
||||
|
||||
|
||||
bound_method = wrapper_descriptor(f, C(), C)
|
||||
|
||||
reveal_type(bound_method) # revealed: bound method C.f() -> str
|
||||
@@ -708,25 +750,31 @@ This test makes sure that we call `__get__` with the right argument types for va
|
||||
```py
|
||||
from __future__ import annotations
|
||||
|
||||
|
||||
class TailoredForClassObjectAccess:
|
||||
def __get__(self, instance: None, owner: type[C]) -> int:
|
||||
return 1
|
||||
|
||||
|
||||
class TailoredForInstanceAccess:
|
||||
def __get__(self, instance: C, owner: type[C] | None = None) -> str:
|
||||
return "a"
|
||||
|
||||
|
||||
class TailoredForMetaclassAccess:
|
||||
def __get__(self, instance: type[C], owner: type[Meta]) -> bytes:
|
||||
return b"a"
|
||||
|
||||
|
||||
class Meta(type):
|
||||
metaclass_access: TailoredForMetaclassAccess = TailoredForMetaclassAccess()
|
||||
|
||||
|
||||
class C(metaclass=Meta):
|
||||
class_object_access: TailoredForClassObjectAccess = TailoredForClassObjectAccess()
|
||||
instance_access: TailoredForInstanceAccess = TailoredForInstanceAccess()
|
||||
|
||||
|
||||
reveal_type(C.class_object_access) # revealed: int
|
||||
reveal_type(C().instance_access) # revealed: str
|
||||
reveal_type(C.metaclass_access) # revealed: bytes
|
||||
@@ -752,9 +800,11 @@ class Descriptor:
|
||||
def __get__(self) -> int:
|
||||
return 1
|
||||
|
||||
|
||||
class C:
|
||||
descriptor: Descriptor = Descriptor()
|
||||
|
||||
|
||||
# TODO: This should be an error
|
||||
reveal_type(C.descriptor) # revealed: int
|
||||
|
||||
@@ -772,9 +822,11 @@ call `__get__`" on the descriptor object (leading us to infer `Unknown`):
|
||||
class BrokenDescriptor:
|
||||
__get__: None = None
|
||||
|
||||
|
||||
class Foo:
|
||||
desc: BrokenDescriptor = BrokenDescriptor()
|
||||
|
||||
|
||||
# TODO: this raises `TypeError` at runtime due to the implicit call to `__get__`;
|
||||
# we should emit a diagnostic
|
||||
reveal_type(Foo().desc) # revealed: Unknown
|
||||
@@ -794,9 +846,11 @@ class Descriptor:
|
||||
def __set__(self, instance: object, value: int) -> None:
|
||||
pass
|
||||
|
||||
|
||||
class C:
|
||||
descriptor = Descriptor()
|
||||
|
||||
|
||||
C.descriptor = "something else"
|
||||
reveal_type(C.descriptor) # revealed: Literal["something else"]
|
||||
```
|
||||
@@ -811,10 +865,12 @@ class DataDescriptor:
|
||||
def __set__(self, instance: int, value) -> None:
|
||||
pass
|
||||
|
||||
|
||||
class NonDataDescriptor:
|
||||
def __get__(self, instance: object, owner: type | None = None) -> int:
|
||||
return 1
|
||||
|
||||
|
||||
def _(flag: bool):
|
||||
class PossiblyUnbound:
|
||||
if flag:
|
||||
@@ -840,6 +896,7 @@ def _(flag: bool):
|
||||
def _(flag: bool):
|
||||
class MaybeDescriptor:
|
||||
if flag:
|
||||
|
||||
def __get__(self, instance: object, owner: type | None = None) -> int:
|
||||
return 1
|
||||
|
||||
@@ -859,36 +916,46 @@ descriptor protocol on the callable's `__call__` method:
|
||||
```py
|
||||
from __future__ import annotations
|
||||
|
||||
|
||||
class ReturnedCallable2:
|
||||
def __call__(self, descriptor: Descriptor1, instance: None, owner: type[C]) -> int:
|
||||
return 1
|
||||
|
||||
|
||||
class ReturnedCallable1:
|
||||
def __call__(self, descriptor: Descriptor2, instance: Callable1, owner: type[Callable1]) -> ReturnedCallable2:
|
||||
return ReturnedCallable2()
|
||||
|
||||
|
||||
class Callable3:
|
||||
def __call__(self, descriptor: Descriptor3, instance: Callable2, owner: type[Callable2]) -> ReturnedCallable1:
|
||||
return ReturnedCallable1()
|
||||
|
||||
|
||||
class Descriptor3:
|
||||
__get__: Callable3 = Callable3()
|
||||
|
||||
|
||||
class Callable2:
|
||||
__call__: Descriptor3 = Descriptor3()
|
||||
|
||||
|
||||
class Descriptor2:
|
||||
__get__: Callable2 = Callable2()
|
||||
|
||||
|
||||
class Callable1:
|
||||
__call__: Descriptor2 = Descriptor2()
|
||||
|
||||
|
||||
class Descriptor1:
|
||||
__get__: Callable1 = Callable1()
|
||||
|
||||
|
||||
class C:
|
||||
d: Descriptor1 = Descriptor1()
|
||||
|
||||
|
||||
reveal_type(C.d) # revealed: int
|
||||
```
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ These can be set on instances and on class objects.
|
||||
class C:
|
||||
attr: int = 0
|
||||
|
||||
|
||||
instance = C()
|
||||
instance.attr = 1 # fine
|
||||
instance.attr = "wrong" # error: [invalid-assignment]
|
||||
@@ -31,6 +32,7 @@ class C:
|
||||
def __init__(self):
|
||||
self.attr: int = 0
|
||||
|
||||
|
||||
instance = C()
|
||||
instance.attr = 1 # fine
|
||||
instance.attr = "wrong" # error: [invalid-assignment]
|
||||
@@ -46,9 +48,11 @@ diagnostic that mentions that the attribute is only available on class objects.
|
||||
```py
|
||||
from typing import ClassVar
|
||||
|
||||
|
||||
class C:
|
||||
attr: ClassVar[int] = 0
|
||||
|
||||
|
||||
C.attr = 1 # fine
|
||||
C.attr = "wrong" # error: [invalid-assignment]
|
||||
|
||||
@@ -63,6 +67,7 @@ When trying to set an attribute that is not defined, we also emit errors:
|
||||
```py
|
||||
class C: ...
|
||||
|
||||
|
||||
C.non_existent = 1 # error: [unresolved-attribute]
|
||||
|
||||
instance = C()
|
||||
@@ -97,9 +102,11 @@ class Descriptor:
|
||||
def __set__(self, instance: object, value: int) -> None:
|
||||
pass
|
||||
|
||||
|
||||
class C:
|
||||
attr: Descriptor = Descriptor()
|
||||
|
||||
|
||||
instance = C()
|
||||
instance.attr = 1 # fine
|
||||
|
||||
@@ -114,9 +121,11 @@ class WrongDescriptor:
|
||||
def __set__(self, instance: object, value: int, extra: int) -> None:
|
||||
pass
|
||||
|
||||
|
||||
class C:
|
||||
attr: WrongDescriptor = WrongDescriptor()
|
||||
|
||||
|
||||
instance = C()
|
||||
|
||||
# TODO: ideally, we would mention why this is an invalid assignment (wrong number of arguments for `__set__`)
|
||||
@@ -128,10 +137,12 @@ instance.attr = 1 # error: [invalid-assignment]
|
||||
```py
|
||||
def _(flag: bool) -> None:
|
||||
if flag:
|
||||
|
||||
class C1:
|
||||
attr: int = 0
|
||||
|
||||
else:
|
||||
|
||||
class C1:
|
||||
attr: str = ""
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ to the invalid argument.
|
||||
def foo(x: int) -> int:
|
||||
return x * x
|
||||
|
||||
|
||||
foo("hello") # error: [invalid-argument-type]
|
||||
```
|
||||
|
||||
@@ -22,6 +23,7 @@ This is like the basic test, except we put the call site above the function defi
|
||||
def bar():
|
||||
foo("hello") # error: [invalid-argument-type]
|
||||
|
||||
|
||||
def foo(x: int) -> int:
|
||||
return x * x
|
||||
```
|
||||
@@ -52,6 +54,7 @@ This checks that a diagnostic renders reasonably when there are multiple paramet
|
||||
def foo(x: int, y: int, z: int) -> int:
|
||||
return x * y * z
|
||||
|
||||
|
||||
foo(1, "hello", 3) # error: [invalid-argument-type]
|
||||
```
|
||||
|
||||
@@ -68,6 +71,7 @@ def foo(
|
||||
) -> int:
|
||||
return x * y * z
|
||||
|
||||
|
||||
foo(1, "hello", 3) # error: [invalid-argument-type]
|
||||
```
|
||||
|
||||
@@ -80,6 +84,7 @@ invalid argument types.
|
||||
def foo(x: int, y: int, z: int) -> int:
|
||||
return x * y * z
|
||||
|
||||
|
||||
# error: [invalid-argument-type]
|
||||
# error: [invalid-argument-type]
|
||||
# error: [invalid-argument-type]
|
||||
@@ -114,6 +119,7 @@ Tests a function definition with only positional parameters.
|
||||
def foo(x: int, y: int, z: int, /) -> int:
|
||||
return x * y * z
|
||||
|
||||
|
||||
foo(1, "hello", 3) # error: [invalid-argument-type]
|
||||
```
|
||||
|
||||
@@ -125,6 +131,7 @@ Tests a function definition with variadic arguments.
|
||||
def foo(*numbers: int) -> int:
|
||||
return len(numbers)
|
||||
|
||||
|
||||
foo(1, 2, 3, "hello", 5) # error: [invalid-argument-type]
|
||||
```
|
||||
|
||||
@@ -136,6 +143,7 @@ Tests a function definition with keyword-only arguments.
|
||||
def foo(x: int, y: int, *, z: int = 0) -> int:
|
||||
return x * y * z
|
||||
|
||||
|
||||
foo(1, 2, z="hello") # error: [invalid-argument-type]
|
||||
```
|
||||
|
||||
@@ -147,6 +155,7 @@ Tests a function definition with keyword-only arguments.
|
||||
def foo(x: int, y: int, z: int = 0) -> int:
|
||||
return x * y * z
|
||||
|
||||
|
||||
foo(1, 2, "hello") # error: [invalid-argument-type]
|
||||
```
|
||||
|
||||
@@ -156,6 +165,7 @@ foo(1, 2, "hello") # error: [invalid-argument-type]
|
||||
def foo(**numbers: int) -> int:
|
||||
return len(numbers)
|
||||
|
||||
|
||||
foo(a=1, b=2, c=3, d="hello", e=5) # error: [invalid-argument-type]
|
||||
```
|
||||
|
||||
@@ -167,6 +177,7 @@ Tests a function definition with multiple different kinds of arguments.
|
||||
def foo(x: int, /, y: int, *, z: int = 0) -> int:
|
||||
return x * y * z
|
||||
|
||||
|
||||
foo(1, 2, z="hello") # error: [invalid-argument-type]
|
||||
```
|
||||
|
||||
@@ -179,6 +190,7 @@ class C:
|
||||
def __call__(self, x: int) -> int:
|
||||
return 1
|
||||
|
||||
|
||||
c = C()
|
||||
c("wrong") # error: [invalid-argument-type]
|
||||
```
|
||||
@@ -192,6 +204,7 @@ class C:
|
||||
def square(self, x: int) -> int:
|
||||
return x * x
|
||||
|
||||
|
||||
c = C()
|
||||
c.square("hello") # error: [invalid-argument-type]
|
||||
```
|
||||
@@ -203,6 +216,7 @@ c.square("hello") # error: [invalid-argument-type]
|
||||
```py
|
||||
class Foo: ...
|
||||
|
||||
|
||||
def needs_a_foo(x: Foo): ...
|
||||
```
|
||||
|
||||
@@ -211,8 +225,10 @@ def needs_a_foo(x: Foo): ...
|
||||
```py
|
||||
from module import needs_a_foo
|
||||
|
||||
|
||||
class Foo: ...
|
||||
|
||||
|
||||
needs_a_foo(Foo()) # error: [invalid-argument-type]
|
||||
```
|
||||
|
||||
@@ -230,6 +246,7 @@ python-version = "3.12"
|
||||
```py
|
||||
class Foo: ...
|
||||
|
||||
|
||||
def needs_a_foo(x: Foo): ...
|
||||
```
|
||||
|
||||
@@ -238,8 +255,10 @@ def needs_a_foo(x: Foo): ...
|
||||
```py
|
||||
from module import needs_a_foo
|
||||
|
||||
|
||||
class Foo: ...
|
||||
|
||||
|
||||
def f[T: Foo](x: T) -> T:
|
||||
needs_a_foo(x) # error: [invalid-argument-type]
|
||||
return x
|
||||
|
||||
@@ -19,6 +19,7 @@ This diagnostic also points to the class definition if available.
|
||||
class MissingAwait:
|
||||
pass
|
||||
|
||||
|
||||
async def main() -> None:
|
||||
await MissingAwait() # error: [invalid-await]
|
||||
```
|
||||
@@ -30,11 +31,14 @@ This diagnostic also points to the method definition if available.
|
||||
```py
|
||||
from datetime import datetime
|
||||
|
||||
|
||||
class PossiblyUnbound:
|
||||
if datetime.today().weekday() == 0:
|
||||
|
||||
def __await__(self):
|
||||
yield
|
||||
|
||||
|
||||
async def main() -> None:
|
||||
await PossiblyUnbound() # error: [invalid-await]
|
||||
```
|
||||
@@ -50,6 +54,7 @@ class InvalidAwaitArgs:
|
||||
def __await__(self, value: int):
|
||||
yield value
|
||||
|
||||
|
||||
async def main() -> None:
|
||||
await InvalidAwaitArgs() # error: [invalid-await]
|
||||
```
|
||||
@@ -63,6 +68,7 @@ awaitable.
|
||||
class NonCallableAwait:
|
||||
__await__ = 42
|
||||
|
||||
|
||||
async def main() -> None:
|
||||
await NonCallableAwait() # error: [invalid-await]
|
||||
```
|
||||
@@ -77,6 +83,7 @@ class InvalidAwaitReturn:
|
||||
def __await__(self) -> int:
|
||||
return 5
|
||||
|
||||
|
||||
async def main() -> None:
|
||||
await InvalidAwaitReturn() # error: [invalid-await]
|
||||
```
|
||||
@@ -90,16 +97,19 @@ instance to be awaitable. In this specific case, no specific function definition
|
||||
import typing
|
||||
from datetime import datetime
|
||||
|
||||
|
||||
class UnawaitableUnion:
|
||||
if datetime.today().weekday() == 6:
|
||||
|
||||
def __await__(self) -> typing.Generator[typing.Any, None, None]:
|
||||
yield
|
||||
|
||||
else:
|
||||
|
||||
def __await__(self) -> int:
|
||||
return 5
|
||||
|
||||
|
||||
async def main() -> None:
|
||||
await UnawaitableUnion() # error: [invalid-await]
|
||||
```
|
||||
|
||||
@@ -17,25 +17,32 @@ T3 = TypeVar("T3")
|
||||
|
||||
DefaultStrT = TypeVar("DefaultStrT", default=str)
|
||||
|
||||
|
||||
class SubclassMe(Generic[T1, DefaultStrT]):
|
||||
x: DefaultStrT
|
||||
|
||||
|
||||
class Baz(SubclassMe[int, DefaultStrT]):
|
||||
pass
|
||||
|
||||
|
||||
# error: [invalid-generic-class] "Type parameter `T2` without a default cannot follow earlier parameter `T1` with a default"
|
||||
class Foo(Generic[T1, T2]):
|
||||
pass
|
||||
|
||||
|
||||
class Bar(Generic[T2, T1, T3]): # error: [invalid-generic-class]
|
||||
pass
|
||||
|
||||
|
||||
class Spam(Generic[T1, T2, DefaultStrT, T3]): # error: [invalid-generic-class]
|
||||
pass
|
||||
|
||||
|
||||
class Ham(Protocol[T1, T2, DefaultStrT, T3]): # error: [invalid-generic-class]
|
||||
pass
|
||||
|
||||
|
||||
class VeryBad(
|
||||
Protocol[T1, T2, DefaultStrT, T3], # error: [invalid-generic-class]
|
||||
Generic[T1, T2, DefaultStrT, T3],
|
||||
|
||||
@@ -102,9 +102,11 @@ T = TypeVar("T", covariant=True, contravariant=True)
|
||||
```py
|
||||
from typing_extensions import TypeVar
|
||||
|
||||
|
||||
def cond() -> bool:
|
||||
return True
|
||||
|
||||
|
||||
# error: [invalid-legacy-type-variable]
|
||||
T = TypeVar("T", covariant=cond())
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ too verbose for it to be worth it.
|
||||
def f(a, b=42): ...
|
||||
def g(a, b): ...
|
||||
|
||||
|
||||
class Foo:
|
||||
def method(self, a): ...
|
||||
```
|
||||
@@ -24,9 +25,11 @@ from module import f, g, Foo
|
||||
|
||||
f() # error: [missing-argument]
|
||||
|
||||
|
||||
def coinflip() -> bool:
|
||||
return True
|
||||
|
||||
|
||||
h = f if coinflip() else g
|
||||
|
||||
# error: [missing-argument]
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
```py
|
||||
from typing import overload
|
||||
|
||||
|
||||
@overload
|
||||
def f(x: int) -> int: ...
|
||||
@overload
|
||||
@@ -14,6 +15,7 @@ def f(x: str) -> str: ...
|
||||
def f(x: int | str) -> int | str:
|
||||
return x
|
||||
|
||||
|
||||
f(b"foo") # error: [no-matching-overload]
|
||||
```
|
||||
|
||||
@@ -27,8 +29,10 @@ Which in turn makes snapshotting a bit annoying, since the output can depend on
|
||||
```py
|
||||
from typing import overload
|
||||
|
||||
|
||||
class Foo: ...
|
||||
|
||||
|
||||
@overload
|
||||
def foo(a: int, b: int, c: int): ...
|
||||
@overload
|
||||
@@ -73,6 +77,7 @@ def foo(a: str, b: float, c: float): ...
|
||||
def foo(a: float, b: float, c: float): ...
|
||||
def foo(a, b, c): ...
|
||||
|
||||
|
||||
foo(Foo(), Foo()) # error: [no-matching-overload]
|
||||
```
|
||||
|
||||
@@ -84,8 +89,10 @@ cut off the list in the diagnostic and emit a message stating the number of omit
|
||||
```py
|
||||
from typing import overload
|
||||
|
||||
|
||||
class Foo: ...
|
||||
|
||||
|
||||
@overload
|
||||
def foo(a: int, b: int, c: int): ...
|
||||
@overload
|
||||
@@ -210,6 +217,7 @@ def foo(a: float, b: float, c: bool): ...
|
||||
def foo(a: bool, b: float, c: float): ...
|
||||
def foo(a, b, c): ...
|
||||
|
||||
|
||||
foo(Foo(), Foo()) # error: [no-matching-overload]
|
||||
```
|
||||
|
||||
@@ -218,6 +226,7 @@ foo(Foo(), Foo()) # error: [no-matching-overload]
|
||||
```py
|
||||
from typing import overload
|
||||
|
||||
|
||||
@overload
|
||||
def f(
|
||||
lion: int,
|
||||
@@ -276,6 +285,7 @@ def f(
|
||||
) -> int | str:
|
||||
return 0
|
||||
|
||||
|
||||
f(b"foo") # error: [no-matching-overload]
|
||||
```
|
||||
|
||||
@@ -284,6 +294,7 @@ f(b"foo") # error: [no-matching-overload]
|
||||
```py
|
||||
from typing import overload
|
||||
|
||||
|
||||
class Foo:
|
||||
@overload
|
||||
def bar(self, x: int) -> int: ...
|
||||
@@ -292,6 +303,7 @@ class Foo:
|
||||
def bar(self, x: int | str) -> int | str:
|
||||
return x
|
||||
|
||||
|
||||
foo = Foo()
|
||||
foo.bar(b"wat") # error: [no-matching-overload]
|
||||
```
|
||||
|
||||
@@ -11,10 +11,12 @@ class A:
|
||||
class B:
|
||||
pass
|
||||
|
||||
|
||||
class C:
|
||||
class B:
|
||||
pass
|
||||
|
||||
|
||||
a: A.B = C.B() # error: [invalid-assignment] "Object of type `test.C.B` is not assignable to `test.A.B`"
|
||||
```
|
||||
|
||||
@@ -26,6 +28,7 @@ a: A.B = C.B() # error: [invalid-assignment] "Object of type `test.C.B` is not
|
||||
class B:
|
||||
pass
|
||||
|
||||
|
||||
def f(b: B):
|
||||
class B:
|
||||
pass
|
||||
@@ -42,6 +45,7 @@ import b
|
||||
|
||||
df: a.DataFrame = b.DataFrame() # error: [invalid-assignment] "Object of type `b.DataFrame` is not assignable to `a.DataFrame`"
|
||||
|
||||
|
||||
def _(dfs: list[b.DataFrame]):
|
||||
# error: [invalid-assignment] "Object of type `list[b.DataFrame]` is not assignable to `list[a.DataFrame]`"
|
||||
dataframes: list[a.DataFrame] = dfs
|
||||
@@ -68,6 +72,7 @@ class DataFrame:
|
||||
```py
|
||||
from .foo import MyClass
|
||||
|
||||
|
||||
def make_MyClass() -> MyClass:
|
||||
return MyClass()
|
||||
```
|
||||
@@ -83,6 +88,7 @@ class MyClass: ...
|
||||
```py
|
||||
class MyClass: ...
|
||||
|
||||
|
||||
def get_MyClass() -> MyClass:
|
||||
from . import make_MyClass
|
||||
|
||||
@@ -105,6 +111,7 @@ s: status_a.Status = status_b.Status.ACTIVE
|
||||
```py
|
||||
from enum import Enum
|
||||
|
||||
|
||||
class Status(Enum):
|
||||
ACTIVE = 1
|
||||
INACTIVE = 2
|
||||
@@ -115,6 +122,7 @@ class Status(Enum):
|
||||
```py
|
||||
from enum import Enum
|
||||
|
||||
|
||||
class Status(Enum):
|
||||
ACTIVE = "active"
|
||||
INACTIVE = "inactive"
|
||||
@@ -127,16 +135,19 @@ class Status(Enum):
|
||||
```py
|
||||
from enum import Enum
|
||||
|
||||
|
||||
class A:
|
||||
class B(Enum):
|
||||
ACTIVE = "active"
|
||||
INACTIVE = "inactive"
|
||||
|
||||
|
||||
class C:
|
||||
class B(Enum):
|
||||
ACTIVE = "active"
|
||||
INACTIVE = "inactive"
|
||||
|
||||
|
||||
# error: [invalid-assignment] "Object of type `Literal[test.C.B.ACTIVE]` is not assignable to `test.A.B`"
|
||||
a: A.B = C.B.ACTIVE
|
||||
```
|
||||
@@ -182,6 +193,7 @@ from typing import Generic, TypeVar
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
|
||||
class Container(Generic[T]):
|
||||
pass
|
||||
```
|
||||
@@ -193,6 +205,7 @@ from typing import Generic, TypeVar
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
|
||||
class Container(Generic[T]):
|
||||
pass
|
||||
```
|
||||
@@ -208,9 +221,11 @@ from typing import Protocol, TypeVar
|
||||
|
||||
T_co = TypeVar("T_co", covariant=True)
|
||||
|
||||
|
||||
class Iterator(Protocol[T_co]):
|
||||
def __nexxt__(self) -> T_co: ...
|
||||
|
||||
|
||||
def bad() -> Iterator[str]:
|
||||
raise NotImplementedError
|
||||
```
|
||||
@@ -220,6 +235,7 @@ def bad() -> Iterator[str]:
|
||||
```py
|
||||
from typing import Iterator
|
||||
|
||||
|
||||
def f() -> Iterator[str]:
|
||||
import bad
|
||||
|
||||
@@ -234,6 +250,7 @@ from typing import Protocol
|
||||
import proto_a
|
||||
import proto_b
|
||||
|
||||
|
||||
def _(drawable_b: proto_b.Drawable):
|
||||
# error: [invalid-assignment] "Object of type `proto_b.Drawable` is not assignable to `proto_a.Drawable`"
|
||||
drawable: proto_a.Drawable = drawable_b
|
||||
@@ -244,6 +261,7 @@ def _(drawable_b: proto_b.Drawable):
|
||||
```py
|
||||
from typing import Protocol
|
||||
|
||||
|
||||
class Drawable(Protocol):
|
||||
def draw(self) -> None: ...
|
||||
```
|
||||
@@ -253,6 +271,7 @@ class Drawable(Protocol):
|
||||
```py
|
||||
from typing import Protocol
|
||||
|
||||
|
||||
class Drawable(Protocol):
|
||||
def draw(self) -> int: ...
|
||||
```
|
||||
@@ -264,6 +283,7 @@ from typing import TypedDict
|
||||
import dict_a
|
||||
import dict_b
|
||||
|
||||
|
||||
def _(b_person: dict_b.Person):
|
||||
# error: [invalid-assignment] "Object of type `dict_b.Person` is not assignable to `dict_a.Person`"
|
||||
person_var: dict_a.Person = b_person
|
||||
@@ -274,6 +294,7 @@ def _(b_person: dict_b.Person):
|
||||
```py
|
||||
from typing import TypedDict
|
||||
|
||||
|
||||
class Person(TypedDict):
|
||||
name: str
|
||||
```
|
||||
@@ -283,6 +304,7 @@ class Person(TypedDict):
|
||||
```py
|
||||
from typing import TypedDict
|
||||
|
||||
|
||||
class Person(TypedDict):
|
||||
name: bytes
|
||||
```
|
||||
@@ -298,6 +320,7 @@ class Model: ...
|
||||
```py
|
||||
class Model: ...
|
||||
|
||||
|
||||
def get_models_tuple() -> tuple[Model]:
|
||||
from module import Model
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ python-version = "3.10"
|
||||
async def elements(n):
|
||||
yield n
|
||||
|
||||
|
||||
async def f():
|
||||
# error: 19 [invalid-syntax] "cannot use an asynchronous comprehension inside of a synchronous comprehension on Python 3.10 (syntax was added in 3.11)"
|
||||
return {n: [x async for x in elements(n)] for n in range(3)}
|
||||
@@ -38,9 +39,11 @@ correctly in the `SemanticSyntaxContext` trait:
|
||||
async def f():
|
||||
[x for x in [1]] and [x async for x in elements(1)]
|
||||
|
||||
|
||||
async def f():
|
||||
def g():
|
||||
pass
|
||||
|
||||
[x async for x in elements(1)]
|
||||
```
|
||||
|
||||
@@ -57,6 +60,7 @@ python-version = "3.11"
|
||||
async def elements(n):
|
||||
yield n
|
||||
|
||||
|
||||
async def f():
|
||||
return {n: [x async for x in elements(n)] for n in range(3)}
|
||||
```
|
||||
@@ -82,6 +86,7 @@ python-version = "3.12"
|
||||
```py
|
||||
from __future__ import annotations
|
||||
|
||||
|
||||
# error: [invalid-type-form] "Named expressions are not allowed in type expressions"
|
||||
# error: [invalid-syntax] "named expression cannot be used within a type annotation"
|
||||
def f() -> (y := 3): ...
|
||||
@@ -114,6 +119,7 @@ python-version = "3.10"
|
||||
class Point:
|
||||
pass
|
||||
|
||||
|
||||
obj = Point()
|
||||
match obj:
|
||||
# error: [invalid-syntax] "attribute name `x` repeated in class pattern"
|
||||
@@ -127,6 +133,7 @@ match obj:
|
||||
class C:
|
||||
def __await__(self): ...
|
||||
|
||||
|
||||
# error: [invalid-syntax] "`return` statement outside of a function"
|
||||
return
|
||||
|
||||
@@ -140,10 +147,12 @@ yield from []
|
||||
# error: [invalid-syntax] "`await` outside of an asynchronous function"
|
||||
await C()
|
||||
|
||||
|
||||
def f():
|
||||
# error: [invalid-syntax] "`await` outside of an asynchronous function"
|
||||
await C()
|
||||
|
||||
|
||||
(await cor async for cor in f()) # ok
|
||||
(await cor for cor in f()) # ok
|
||||
([await c for c in cor] async for cor in f()) # ok
|
||||
@@ -155,6 +164,7 @@ Generators are evaluated lazily, so `await` is allowed, even outside of a functi
|
||||
async def g():
|
||||
yield 1
|
||||
|
||||
|
||||
(x async for x in g())
|
||||
```
|
||||
|
||||
@@ -209,6 +219,7 @@ python-version = "3.12"
|
||||
class C[T, T]:
|
||||
pass
|
||||
|
||||
|
||||
# error: [invalid-syntax] "duplicate type parameter"
|
||||
def f[X, Y, X]():
|
||||
pass
|
||||
@@ -223,10 +234,12 @@ def func():
|
||||
# error: [invalid-syntax] "Starred expression cannot be used here"
|
||||
return *[1, 2, 3]
|
||||
|
||||
|
||||
def gen():
|
||||
# error: [invalid-syntax] "Starred expression cannot be used here"
|
||||
yield * [1, 2, 3]
|
||||
|
||||
|
||||
# error: [invalid-syntax] "Starred expression cannot be used here"
|
||||
for *x in range(10):
|
||||
pass
|
||||
@@ -287,10 +300,12 @@ python-version = "3.12"
|
||||
# error: [invalid-syntax] "cannot assign to `__debug__`"
|
||||
__debug__ = False
|
||||
|
||||
|
||||
# error: [invalid-syntax] "cannot assign to `__debug__`"
|
||||
def process(__debug__):
|
||||
pass
|
||||
|
||||
|
||||
# error: [invalid-syntax] "cannot assign to `__debug__`"
|
||||
class Generic[__debug__]:
|
||||
pass
|
||||
@@ -311,16 +326,19 @@ def _():
|
||||
# error: [invalid-syntax] "yield expression cannot be used within a TypeVar bound"
|
||||
type X[T: (yield 1)] = int
|
||||
|
||||
|
||||
def _():
|
||||
# error: [invalid-type-form] "`yield` expressions are not allowed in type expressions"
|
||||
# error: [invalid-syntax] "yield expression cannot be used within a type alias"
|
||||
type Y = (yield 1)
|
||||
|
||||
|
||||
# error: [invalid-type-form] "Named expressions are not allowed in type expressions"
|
||||
# error: [invalid-syntax] "named expression cannot be used within a generic definition"
|
||||
def f[T](x: int) -> (y := 3):
|
||||
return x
|
||||
|
||||
|
||||
def _():
|
||||
# error: [invalid-syntax] "yield expression cannot be used within a generic definition"
|
||||
class C[T]((yield from [object])):
|
||||
@@ -335,6 +353,7 @@ This error includes `await`, `async for`, `async with`, and `async` comprehensio
|
||||
async def elements(n):
|
||||
yield n
|
||||
|
||||
|
||||
def _():
|
||||
# error: [invalid-syntax] "`await` outside of an asynchronous function"
|
||||
await elements(1)
|
||||
@@ -354,6 +373,7 @@ def _():
|
||||
```py
|
||||
x: int
|
||||
|
||||
|
||||
def f():
|
||||
x = 1
|
||||
global x # error: [invalid-syntax] "name `x` is used prior to global declaration"
|
||||
@@ -388,32 +408,39 @@ for x in range(42):
|
||||
```py
|
||||
a = None
|
||||
|
||||
|
||||
def f(a):
|
||||
global a # error: [invalid-syntax]
|
||||
|
||||
|
||||
def g(a):
|
||||
if True:
|
||||
global a # error: [invalid-syntax]
|
||||
|
||||
|
||||
def h(a):
|
||||
def inner():
|
||||
global a
|
||||
|
||||
|
||||
def i(a):
|
||||
try:
|
||||
global a # error: [invalid-syntax]
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
def f(a):
|
||||
a = 1
|
||||
global a # error: [invalid-syntax]
|
||||
|
||||
|
||||
def f(a):
|
||||
a = 1
|
||||
a = 2
|
||||
global a # error: [invalid-syntax]
|
||||
|
||||
|
||||
def f(a):
|
||||
class Inner:
|
||||
global a # ok
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
```py
|
||||
class C: ...
|
||||
|
||||
|
||||
C = 1 # error: [invalid-assignment]
|
||||
```
|
||||
|
||||
@@ -15,5 +16,6 @@ C = 1 # error: [invalid-assignment]
|
||||
```py
|
||||
def f(): ...
|
||||
|
||||
|
||||
f = 1 # error: [invalid-assignment]
|
||||
```
|
||||
|
||||
@@ -7,6 +7,7 @@ from typing_extensions import Any, Final, LiteralString, Self
|
||||
|
||||
X = Any
|
||||
|
||||
|
||||
class Foo:
|
||||
X: Final = LiteralString
|
||||
a: int
|
||||
@@ -16,6 +17,7 @@ class Foo:
|
||||
def __init__(self):
|
||||
self.y: Final = LiteralString
|
||||
|
||||
|
||||
X.foo # error: [unresolved-attribute]
|
||||
X.aaaaooooooo # error: [unresolved-attribute]
|
||||
Foo.X.startswith # error: [unresolved-attribute]
|
||||
|
||||
@@ -13,6 +13,7 @@ too verbose for it to be worth it.
|
||||
def f(a, b=42): ...
|
||||
def g(a, b): ...
|
||||
|
||||
|
||||
class Foo:
|
||||
def method(self, a): ...
|
||||
```
|
||||
@@ -24,9 +25,11 @@ from module import f, g, Foo
|
||||
|
||||
f(1, 2, 3) # error: [too-many-positional-arguments]
|
||||
|
||||
|
||||
def coinflip() -> bool:
|
||||
return True
|
||||
|
||||
|
||||
h = f if coinflip() else g
|
||||
|
||||
# error: [too-many-positional-arguments]
|
||||
|
||||
@@ -13,9 +13,11 @@ python-version = "3.12"
|
||||
def f1() -> int:
|
||||
return 0
|
||||
|
||||
|
||||
def f2(name: str) -> int:
|
||||
return 0
|
||||
|
||||
|
||||
def _(flag: bool):
|
||||
if flag:
|
||||
f = f1
|
||||
@@ -37,9 +39,11 @@ the end user.)
|
||||
def f1(a: int) -> int:
|
||||
return 0
|
||||
|
||||
|
||||
def f2(name: str) -> int:
|
||||
return 0
|
||||
|
||||
|
||||
def _(flag: bool):
|
||||
if flag:
|
||||
f = f1
|
||||
@@ -61,18 +65,23 @@ just ensuring that we get test coverage for each of the possible diagnostic mess
|
||||
from inspect import getattr_static
|
||||
from typing import overload
|
||||
|
||||
|
||||
def f1() -> int:
|
||||
return 0
|
||||
|
||||
|
||||
def f2(name: str) -> int:
|
||||
return 0
|
||||
|
||||
|
||||
def f3(a: int, b: int) -> int:
|
||||
return 0
|
||||
|
||||
|
||||
def f4[T: str](x: T) -> int:
|
||||
return 0
|
||||
|
||||
|
||||
@overload
|
||||
def f5() -> None: ...
|
||||
@overload
|
||||
@@ -80,6 +89,7 @@ def f5(x: str) -> str: ...
|
||||
def f5(x: str | None = None) -> str | None:
|
||||
return x
|
||||
|
||||
|
||||
@overload
|
||||
def f6() -> None: ...
|
||||
@overload
|
||||
@@ -87,9 +97,11 @@ def f6(x: str, y: str) -> str: ...
|
||||
def f6(x: str | None = None, y: str | None = None) -> str | None:
|
||||
return x + y if x and y else None
|
||||
|
||||
|
||||
def _(n: int):
|
||||
class PossiblyNotCallable:
|
||||
if n == 0:
|
||||
|
||||
def __call__(self) -> int:
|
||||
return 0
|
||||
|
||||
@@ -126,9 +138,11 @@ def _(n: int):
|
||||
def any(*args, **kwargs) -> int:
|
||||
return 0
|
||||
|
||||
|
||||
def f1(name: str) -> int:
|
||||
return 0
|
||||
|
||||
|
||||
def _(n: int):
|
||||
if n == 0:
|
||||
f = f1
|
||||
@@ -147,16 +161,29 @@ therefore truncate the long expected union type to avoid overwhelming output.
|
||||
```py
|
||||
from typing import Literal, Union
|
||||
|
||||
|
||||
class A: ...
|
||||
|
||||
|
||||
class B: ...
|
||||
|
||||
|
||||
class C: ...
|
||||
|
||||
|
||||
class D: ...
|
||||
|
||||
|
||||
class E: ...
|
||||
|
||||
|
||||
class F: ...
|
||||
|
||||
|
||||
def f1(x: Union[Literal[1, 2, 3, 4, 5, 6, 7, 8], A, B, C, D, E, F]) -> int:
|
||||
return 0
|
||||
|
||||
|
||||
def _(n: int):
|
||||
x = n
|
||||
# error: [invalid-argument-type]
|
||||
|
||||
@@ -13,6 +13,7 @@ sub-diagnostic for each element would probably be too verbose for it to be worth
|
||||
def f(a, b, c=42): ...
|
||||
def g(a, b): ...
|
||||
|
||||
|
||||
class Foo:
|
||||
def method(self, a, b): ...
|
||||
```
|
||||
@@ -24,9 +25,11 @@ from module import f, g, Foo
|
||||
|
||||
f(a=1, b=2, c=3, d=42) # error: [unknown-argument]
|
||||
|
||||
|
||||
def coinflip() -> bool:
|
||||
return True
|
||||
|
||||
|
||||
h = f if coinflip() else g
|
||||
|
||||
# error: [unknown-argument]
|
||||
|
||||
@@ -9,6 +9,7 @@ class NotBoolable:
|
||||
def __bool__(self, foo):
|
||||
return False
|
||||
|
||||
|
||||
a = NotBoolable()
|
||||
|
||||
# error: [unsupported-bool-conversion]
|
||||
@@ -22,6 +23,7 @@ class NotBoolable:
|
||||
def __bool__(self) -> str:
|
||||
return "wat"
|
||||
|
||||
|
||||
a = NotBoolable()
|
||||
|
||||
# error: [unsupported-bool-conversion]
|
||||
@@ -34,6 +36,7 @@ a = NotBoolable()
|
||||
class NotBoolable:
|
||||
__bool__: int = 3
|
||||
|
||||
|
||||
a = NotBoolable()
|
||||
|
||||
# error: [unsupported-bool-conversion]
|
||||
@@ -47,15 +50,19 @@ class NotBoolable1:
|
||||
def __bool__(self) -> str:
|
||||
return "wat"
|
||||
|
||||
|
||||
class NotBoolable2:
|
||||
pass
|
||||
|
||||
|
||||
class NotBoolable3:
|
||||
__bool__: int = 3
|
||||
|
||||
|
||||
def get() -> NotBoolable1 | NotBoolable2 | NotBoolable3:
|
||||
return NotBoolable2()
|
||||
|
||||
|
||||
# error: [unsupported-bool-conversion]
|
||||
10 and get() and True
|
||||
```
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
from typing_extensions import assert_never, Never, Any
|
||||
from ty_extensions import Unknown
|
||||
|
||||
|
||||
def _(never: Never):
|
||||
assert_never(never) # fine
|
||||
```
|
||||
@@ -24,24 +25,31 @@ If it is not, a `type-assertion-failure` diagnostic is emitted.
|
||||
from typing_extensions import assert_never, Never, Any
|
||||
from ty_extensions import Unknown
|
||||
|
||||
|
||||
def _():
|
||||
assert_never(0) # error: [type-assertion-failure]
|
||||
|
||||
|
||||
def _():
|
||||
assert_never("") # error: [type-assertion-failure]
|
||||
|
||||
|
||||
def _():
|
||||
assert_never(None) # error: [type-assertion-failure]
|
||||
|
||||
|
||||
def _():
|
||||
assert_never(()) # error: [type-assertion-failure]
|
||||
|
||||
|
||||
def _(flag: bool, never: Never):
|
||||
assert_never(1 if flag else never) # error: [type-assertion-failure]
|
||||
|
||||
|
||||
def _(any_: Any):
|
||||
assert_never(any_) # error: [type-assertion-failure]
|
||||
|
||||
|
||||
def _(unknown: Unknown):
|
||||
assert_never(unknown) # error: [type-assertion-failure]
|
||||
```
|
||||
@@ -59,10 +67,16 @@ are handled in a series of `isinstance` checks or other narrowing patterns that
|
||||
```py
|
||||
from typing_extensions import assert_never, Literal
|
||||
|
||||
|
||||
class A: ...
|
||||
|
||||
|
||||
class B: ...
|
||||
|
||||
|
||||
class C: ...
|
||||
|
||||
|
||||
def if_else_isinstance_success(obj: A | B):
|
||||
if isinstance(obj, A):
|
||||
pass
|
||||
@@ -73,6 +87,7 @@ def if_else_isinstance_success(obj: A | B):
|
||||
else:
|
||||
assert_never(obj)
|
||||
|
||||
|
||||
def if_else_isinstance_error(obj: A | B):
|
||||
if isinstance(obj, A):
|
||||
pass
|
||||
@@ -83,6 +98,7 @@ def if_else_isinstance_error(obj: A | B):
|
||||
# error: [type-assertion-failure] "Type `B & ~A & ~C` is not equivalent to `Never`"
|
||||
assert_never(obj)
|
||||
|
||||
|
||||
def if_else_singletons_success(obj: Literal[1, "a"] | None):
|
||||
if obj == 1:
|
||||
pass
|
||||
@@ -93,6 +109,7 @@ def if_else_singletons_success(obj: Literal[1, "a"] | None):
|
||||
else:
|
||||
assert_never(obj)
|
||||
|
||||
|
||||
def if_else_singletons_error(obj: Literal[1, "a"] | None):
|
||||
if obj == 1:
|
||||
pass
|
||||
@@ -104,6 +121,7 @@ def if_else_singletons_error(obj: Literal[1, "a"] | None):
|
||||
# error: [type-assertion-failure] "Type `Literal["a"]` is not equivalent to `Never`"
|
||||
assert_never(obj)
|
||||
|
||||
|
||||
def match_singletons_success(obj: Literal[1, "a"] | None):
|
||||
match obj:
|
||||
case 1:
|
||||
@@ -115,6 +133,7 @@ def match_singletons_success(obj: Literal[1, "a"] | None):
|
||||
case _ as obj:
|
||||
assert_never(obj)
|
||||
|
||||
|
||||
def match_singletons_error(obj: Literal[1, "a"] | None):
|
||||
match obj:
|
||||
case 1:
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
```py
|
||||
from typing_extensions import assert_type
|
||||
|
||||
|
||||
def _(x: int, y: bool):
|
||||
assert_type(x, int) # fine
|
||||
assert_type(x, str) # error: [type-assertion-failure]
|
||||
@@ -26,6 +27,7 @@ python-version = "3.10"
|
||||
```py
|
||||
from typing_extensions import assert_type
|
||||
|
||||
|
||||
def _(x: int | str):
|
||||
if isinstance(x, int):
|
||||
reveal_type(x) # revealed: int
|
||||
@@ -40,14 +42,17 @@ The actual type must match the asserted type precisely.
|
||||
from typing import Any, Type, Union
|
||||
from typing_extensions import assert_type
|
||||
|
||||
|
||||
# Subtype does not count
|
||||
def _(x: bool):
|
||||
assert_type(x, int) # error: [type-assertion-failure] "Type `int` does not match asserted type `bool`"
|
||||
|
||||
|
||||
def _(a: type[int], b: type[Any]):
|
||||
assert_type(a, type[Any]) # error: [type-assertion-failure] "Type `type[Any]` does not match asserted type `type[int]`"
|
||||
assert_type(b, type[int]) # error: [type-assertion-failure] "Type `type[int]` does not match asserted type `type[Any]`"
|
||||
|
||||
|
||||
# The expression constructing the type is not taken into account
|
||||
def _(a: type[int]):
|
||||
assert_type(a, Type[int]) # fine
|
||||
@@ -61,6 +66,7 @@ from typing_extensions import Literal, assert_type
|
||||
|
||||
from ty_extensions import Unknown
|
||||
|
||||
|
||||
# Any and Unknown are considered equivalent
|
||||
def _(a: Unknown, b: Any):
|
||||
reveal_type(a) # revealed: Unknown
|
||||
@@ -69,6 +75,7 @@ def _(a: Unknown, b: Any):
|
||||
reveal_type(b) # revealed: Any
|
||||
assert_type(b, Unknown) # fine
|
||||
|
||||
|
||||
def _(a: type[Unknown], b: type[Any]):
|
||||
reveal_type(a) # revealed: type[Unknown]
|
||||
assert_type(a, type[Any]) # fine
|
||||
@@ -86,6 +93,7 @@ from typing_extensions import Any, assert_type
|
||||
|
||||
from ty_extensions import Unknown
|
||||
|
||||
|
||||
def _(a: tuple[int, str, bytes]):
|
||||
assert_type(a, tuple[int, str, bytes]) # fine
|
||||
|
||||
@@ -93,6 +101,7 @@ def _(a: tuple[int, str, bytes]):
|
||||
assert_type(a, tuple[int, str, bytes, None]) # error: [type-assertion-failure]
|
||||
assert_type(a, tuple[int, bytes, str]) # error: [type-assertion-failure]
|
||||
|
||||
|
||||
def _(a: tuple[Any, ...], b: tuple[Unknown, ...]):
|
||||
assert_type(a, tuple[Any, ...]) # fine
|
||||
assert_type(a, tuple[Unknown, ...]) # fine
|
||||
@@ -113,6 +122,7 @@ python-version = "3.10"
|
||||
```py
|
||||
from typing_extensions import assert_type
|
||||
|
||||
|
||||
def _(a: str | int):
|
||||
assert_type(a, str | int)
|
||||
assert_type(a, int | str)
|
||||
@@ -128,11 +138,19 @@ from typing_extensions import assert_type
|
||||
|
||||
from ty_extensions import Intersection, Not
|
||||
|
||||
|
||||
class A: ...
|
||||
|
||||
|
||||
class B: ...
|
||||
|
||||
|
||||
class C: ...
|
||||
|
||||
|
||||
class D: ...
|
||||
|
||||
|
||||
def _(a: A):
|
||||
if isinstance(a, B) and not isinstance(a, C) and not isinstance(a, D):
|
||||
reveal_type(a) # revealed: A & B & ~C & ~D
|
||||
|
||||
@@ -28,15 +28,19 @@ cast(str)
|
||||
# error: [too-many-positional-arguments] "Too many positional arguments to function `cast`: expected 2, got 3"
|
||||
cast(str, b"ar", "foo")
|
||||
|
||||
|
||||
def function_returning_int() -> int:
|
||||
return 10
|
||||
|
||||
|
||||
# error: [redundant-cast] "Value is already of type `int`"
|
||||
cast(int, function_returning_int())
|
||||
|
||||
|
||||
def function_returning_any() -> Any:
|
||||
return "blah"
|
||||
|
||||
|
||||
# error: [redundant-cast] "Value is already of type `Any`"
|
||||
cast(Any, function_returning_any())
|
||||
```
|
||||
@@ -47,6 +51,7 @@ diagnostics.
|
||||
```py
|
||||
from typing import Callable
|
||||
|
||||
|
||||
def f(x: Callable[[dict[str, int]], None], y: tuple[dict[str, int]]):
|
||||
a = cast(Callable[[list[bytes]], None], x)
|
||||
b = cast(tuple[list[bytes]], y)
|
||||
@@ -65,6 +70,7 @@ the gradual guarantee and leads to cascading errors when an object is inferred a
|
||||
```py
|
||||
from ty_extensions import Unknown
|
||||
|
||||
|
||||
def f(x: Any, y: Unknown, z: Any | str | int):
|
||||
a = cast(dict[str, Any], x)
|
||||
reveal_type(a) # revealed: dict[str, Any]
|
||||
|
||||
@@ -9,6 +9,7 @@ types for undeclared symbols. This is best illustrated with an example:
|
||||
class Wrapper:
|
||||
value = None
|
||||
|
||||
|
||||
wrapper = Wrapper()
|
||||
|
||||
reveal_type(wrapper.value) # revealed: Unknown | None
|
||||
@@ -37,6 +38,7 @@ where `wrapper.value` is used in a way that is incompatible with `None`:
|
||||
def accepts_int(i: int) -> None:
|
||||
pass
|
||||
|
||||
|
||||
def f(w: Wrapper) -> None:
|
||||
# This is fine
|
||||
v: int | None = w.value
|
||||
@@ -59,6 +61,7 @@ untyped module:
|
||||
class OptionalInt:
|
||||
value = 10
|
||||
|
||||
|
||||
def reset(o):
|
||||
o.value = None
|
||||
```
|
||||
@@ -91,6 +94,7 @@ class, this would probably be:
|
||||
class OptionalInt:
|
||||
value: int | None = 10
|
||||
|
||||
|
||||
o = OptionalInt()
|
||||
|
||||
# The following public type is now
|
||||
@@ -118,6 +122,7 @@ class Wrapper:
|
||||
# Type as seen from the same scope:
|
||||
reveal_type(value) # revealed: None
|
||||
|
||||
|
||||
# Type as seen from another scope:
|
||||
reveal_type(Wrapper.value) # revealed: Unknown | None
|
||||
```
|
||||
|
||||
@@ -6,11 +6,13 @@
|
||||
from enum import Enum
|
||||
from typing import Literal
|
||||
|
||||
|
||||
class Color(Enum):
|
||||
RED = 1
|
||||
GREEN = 2
|
||||
BLUE = 3
|
||||
|
||||
|
||||
reveal_type(Color.RED) # revealed: Literal[Color.RED]
|
||||
reveal_type(Color.RED.name) # revealed: Literal["RED"]
|
||||
reveal_type(Color.RED.value) # revealed: Literal[1]
|
||||
@@ -32,19 +34,23 @@ Simple enums with integer or string values:
|
||||
from enum import Enum
|
||||
from ty_extensions import enum_members
|
||||
|
||||
|
||||
class ColorInt(Enum):
|
||||
RED = 1
|
||||
GREEN = 2
|
||||
BLUE = 3
|
||||
|
||||
|
||||
# revealed: tuple[Literal["RED"], Literal["GREEN"], Literal["BLUE"]]
|
||||
reveal_type(enum_members(ColorInt))
|
||||
|
||||
|
||||
class ColorStr(Enum):
|
||||
RED = "red"
|
||||
GREEN = "green"
|
||||
BLUE = "blue"
|
||||
|
||||
|
||||
# revealed: tuple[Literal["RED"], Literal["GREEN"], Literal["BLUE"]]
|
||||
reveal_type(enum_members(ColorStr))
|
||||
```
|
||||
@@ -55,11 +61,13 @@ reveal_type(enum_members(ColorStr))
|
||||
from enum import IntEnum
|
||||
from ty_extensions import enum_members
|
||||
|
||||
|
||||
class ColorInt(IntEnum):
|
||||
RED = 1
|
||||
GREEN = 2
|
||||
BLUE = 3
|
||||
|
||||
|
||||
# revealed: tuple[Literal["RED"], Literal["GREEN"], Literal["BLUE"]]
|
||||
reveal_type(enum_members(ColorInt))
|
||||
```
|
||||
@@ -72,6 +80,7 @@ Attributes on the enum class that are declared are not considered members of the
|
||||
from enum import Enum
|
||||
from ty_extensions import enum_members
|
||||
|
||||
|
||||
class Answer(Enum):
|
||||
YES = 1
|
||||
NO = 2
|
||||
@@ -81,6 +90,7 @@ class Answer(Enum):
|
||||
# TODO: this could be considered an error:
|
||||
non_member_1: str = "some value"
|
||||
|
||||
|
||||
# revealed: tuple[Literal["YES"], Literal["NO"]]
|
||||
reveal_type(enum_members(Answer))
|
||||
```
|
||||
@@ -92,10 +102,12 @@ from enum import Enum
|
||||
from typing import Final
|
||||
from ty_extensions import enum_members
|
||||
|
||||
|
||||
class Answer(Enum):
|
||||
YES: Final = 1
|
||||
NO: Final = 2
|
||||
|
||||
|
||||
# revealed: tuple[Literal["YES"], Literal["NO"]]
|
||||
reveal_type(enum_members(Answer))
|
||||
```
|
||||
@@ -110,13 +122,16 @@ from enum import Enum
|
||||
from ty_extensions import enum_members
|
||||
from typing import Callable, Literal
|
||||
|
||||
|
||||
def identity(x) -> int:
|
||||
return x
|
||||
|
||||
|
||||
class Descriptor:
|
||||
def __get__(self, instance, owner):
|
||||
return 0
|
||||
|
||||
|
||||
class Answer(Enum):
|
||||
YES = 1
|
||||
NO = 2
|
||||
@@ -139,6 +154,7 @@ class Answer(Enum):
|
||||
|
||||
class NestedClass: ...
|
||||
|
||||
|
||||
# revealed: tuple[Literal["YES"], Literal["NO"]]
|
||||
reveal_type(enum_members(Answer))
|
||||
```
|
||||
@@ -157,6 +173,7 @@ from enum import Enum, property as enum_property
|
||||
from typing import Any
|
||||
from ty_extensions import enum_members
|
||||
|
||||
|
||||
class Answer(Enum):
|
||||
YES = 1
|
||||
NO = 2
|
||||
@@ -165,6 +182,7 @@ class Answer(Enum):
|
||||
def some_property(self) -> str:
|
||||
return "property value"
|
||||
|
||||
|
||||
# revealed: tuple[Literal["YES"], Literal["NO"]]
|
||||
reveal_type(enum_members(Answer))
|
||||
```
|
||||
@@ -174,6 +192,7 @@ Enum attributes defined using `enum.property` take precedence over generated att
|
||||
```py
|
||||
from enum import Enum, property as enum_property
|
||||
|
||||
|
||||
class Choices(Enum):
|
||||
A = 1
|
||||
B = 2
|
||||
@@ -181,6 +200,7 @@ class Choices(Enum):
|
||||
@enum_property
|
||||
def value(self) -> Any: ...
|
||||
|
||||
|
||||
# TODO: This should be `Any` - overridden by `@enum_property`
|
||||
reveal_type(Choices.A.value) # revealed: Literal[1]
|
||||
```
|
||||
@@ -194,6 +214,7 @@ from enum import Enum
|
||||
from ty_extensions import enum_members
|
||||
from types import DynamicClassAttribute
|
||||
|
||||
|
||||
class Answer(Enum):
|
||||
YES = 1
|
||||
NO = 2
|
||||
@@ -202,6 +223,7 @@ class Answer(Enum):
|
||||
def dynamic_property(self) -> str:
|
||||
return "dynamic value"
|
||||
|
||||
|
||||
# revealed: tuple[Literal["YES"], Literal["NO"]]
|
||||
reveal_type(enum_members(Answer))
|
||||
```
|
||||
@@ -232,12 +254,14 @@ Enum members can have aliases, which are not considered separate members:
|
||||
from enum import Enum
|
||||
from ty_extensions import enum_members
|
||||
|
||||
|
||||
class Answer(Enum):
|
||||
YES = 1
|
||||
NO = 2
|
||||
|
||||
DEFINITELY = YES
|
||||
|
||||
|
||||
# revealed: tuple[Literal["YES"], Literal["NO"]]
|
||||
reveal_type(enum_members(Answer))
|
||||
|
||||
@@ -249,6 +273,7 @@ If a value is duplicated, we also treat that as an alias:
|
||||
```py
|
||||
from enum import Enum
|
||||
|
||||
|
||||
class Color(Enum):
|
||||
RED = 1
|
||||
GREEN = 2
|
||||
@@ -256,6 +281,7 @@ class Color(Enum):
|
||||
red = 1
|
||||
green = 2
|
||||
|
||||
|
||||
# revealed: tuple[Literal["RED"], Literal["GREEN"]]
|
||||
reveal_type(enum_members(Color))
|
||||
|
||||
@@ -269,6 +295,7 @@ Multiple aliases to the same member are also supported. This is a regression tes
|
||||
```py
|
||||
from ty_extensions import enum_members
|
||||
|
||||
|
||||
class ManyAliases(Enum):
|
||||
real_member = "real_member"
|
||||
alias1 = "real_member"
|
||||
@@ -277,6 +304,7 @@ class ManyAliases(Enum):
|
||||
|
||||
other_member = "other_real_member"
|
||||
|
||||
|
||||
# revealed: tuple[Literal["real_member"], Literal["other_member"]]
|
||||
reveal_type(enum_members(ManyAliases))
|
||||
|
||||
@@ -309,19 +337,23 @@ python-version = "3.11"
|
||||
from enum import Enum, auto
|
||||
from ty_extensions import enum_members
|
||||
|
||||
|
||||
class Answer(Enum):
|
||||
YES = auto()
|
||||
NO = auto()
|
||||
|
||||
|
||||
# revealed: tuple[Literal["YES"], Literal["NO"]]
|
||||
reveal_type(enum_members(Answer))
|
||||
|
||||
reveal_type(Answer.YES.value) # revealed: Literal[1]
|
||||
reveal_type(Answer.NO.value) # revealed: Literal[2]
|
||||
|
||||
|
||||
class SingleMember(Enum):
|
||||
SINGLE = auto()
|
||||
|
||||
|
||||
reveal_type(SingleMember.SINGLE.value) # revealed: Literal[1]
|
||||
```
|
||||
|
||||
@@ -334,6 +366,7 @@ class Mixed(Enum):
|
||||
MANUAL_2 = -2
|
||||
AUTO_2 = auto()
|
||||
|
||||
|
||||
reveal_type(Mixed.MANUAL_1.value) # revealed: Literal[-1]
|
||||
reveal_type(Mixed.AUTO_1.value) # revealed: Literal[1]
|
||||
reveal_type(Mixed.MANUAL_2.value) # revealed: Literal[-2]
|
||||
@@ -345,16 +378,20 @@ When using `auto()` with `StrEnum`, the value is the lowercase name of the membe
|
||||
```py
|
||||
from enum import StrEnum, auto
|
||||
|
||||
|
||||
class Answer(StrEnum):
|
||||
YES = auto()
|
||||
NO = auto()
|
||||
|
||||
|
||||
reveal_type(Answer.YES.value) # revealed: Literal["yes"]
|
||||
reveal_type(Answer.NO.value) # revealed: Literal["no"]
|
||||
|
||||
|
||||
class SingleMember(StrEnum):
|
||||
SINGLE = auto()
|
||||
|
||||
|
||||
reveal_type(SingleMember.SINGLE.value) # revealed: Literal["single"]
|
||||
```
|
||||
|
||||
@@ -363,10 +400,12 @@ Using `auto()` with `IntEnum` also works as expected:
|
||||
```py
|
||||
from enum import IntEnum, auto
|
||||
|
||||
|
||||
class Answer(IntEnum):
|
||||
YES = auto()
|
||||
NO = auto()
|
||||
|
||||
|
||||
reveal_type(Answer.YES.value) # revealed: Literal[1]
|
||||
reveal_type(Answer.NO.value) # revealed: Literal[2]
|
||||
```
|
||||
@@ -376,10 +415,12 @@ As does using `auto()` for other enums that use `int` as a mixin:
|
||||
```py
|
||||
from enum import Enum, auto
|
||||
|
||||
|
||||
class Answer(int, Enum):
|
||||
YES = auto()
|
||||
NO = auto()
|
||||
|
||||
|
||||
reveal_type(Answer.YES.value) # revealed: Literal[1]
|
||||
reveal_type(Answer.NO.value) # revealed: Literal[2]
|
||||
```
|
||||
@@ -392,28 +433,36 @@ effect of using `auto()` will be for an arbitrary non-integer mixin, so for anyt
|
||||
```python
|
||||
from enum import Enum, auto
|
||||
|
||||
|
||||
class A(str, Enum):
|
||||
X = auto()
|
||||
Y = auto()
|
||||
|
||||
|
||||
reveal_type(A.X.value) # revealed: Any
|
||||
|
||||
|
||||
class B(bytes, Enum):
|
||||
X = auto()
|
||||
Y = auto()
|
||||
|
||||
|
||||
reveal_type(B.X.value) # revealed: Any
|
||||
|
||||
|
||||
class C(tuple, Enum):
|
||||
X = auto()
|
||||
Y = auto()
|
||||
|
||||
|
||||
reveal_type(C.X.value) # revealed: Any
|
||||
|
||||
|
||||
class D(float, Enum):
|
||||
X = auto()
|
||||
Y = auto()
|
||||
|
||||
|
||||
reveal_type(D.X.value) # revealed: Any
|
||||
```
|
||||
|
||||
@@ -422,12 +471,14 @@ Combining aliases with `auto()`:
|
||||
```py
|
||||
from enum import Enum, auto
|
||||
|
||||
|
||||
class Answer(Enum):
|
||||
YES = auto()
|
||||
NO = auto()
|
||||
|
||||
DEFINITELY = YES
|
||||
|
||||
|
||||
# TODO: This should ideally be `tuple[Literal["YES"], Literal["NO"]]`
|
||||
# revealed: tuple[Literal["YES"], Literal["NO"], Literal["DEFINITELY"]]
|
||||
reveal_type(enum_members(Answer))
|
||||
@@ -444,11 +495,13 @@ python-version = "3.11"
|
||||
from enum import Enum, auto, member, nonmember
|
||||
from ty_extensions import enum_members
|
||||
|
||||
|
||||
class Answer(Enum):
|
||||
YES = member(1)
|
||||
NO = member(2)
|
||||
OTHER = nonmember(17)
|
||||
|
||||
|
||||
# revealed: tuple[Literal["YES"], Literal["NO"]]
|
||||
reveal_type(enum_members(Answer))
|
||||
|
||||
@@ -463,6 +516,7 @@ reveal_type(Answer.OTHER)
|
||||
from enum import Enum, member
|
||||
from ty_extensions import enum_members
|
||||
|
||||
|
||||
class Answer(Enum):
|
||||
yes = member(1)
|
||||
no = member(2)
|
||||
@@ -471,6 +525,7 @@ class Answer(Enum):
|
||||
def maybe(self) -> None:
|
||||
return
|
||||
|
||||
|
||||
# revealed: tuple[Literal["yes"], Literal["no"], Literal["maybe"]]
|
||||
reveal_type(enum_members(Answer))
|
||||
```
|
||||
@@ -484,6 +539,7 @@ treated as a non-member:
|
||||
from enum import Enum
|
||||
from ty_extensions import enum_members
|
||||
|
||||
|
||||
class Answer(Enum):
|
||||
YES = 1
|
||||
NO = 2
|
||||
@@ -491,6 +547,7 @@ class Answer(Enum):
|
||||
__private_member = 3
|
||||
__maybe__ = 4
|
||||
|
||||
|
||||
# revealed: tuple[Literal["YES"], Literal["NO"], Literal["__maybe__"]]
|
||||
reveal_type(enum_members(Answer))
|
||||
```
|
||||
@@ -504,6 +561,7 @@ whitespace-delimited list of names:
|
||||
from enum import Enum
|
||||
from ty_extensions import enum_members
|
||||
|
||||
|
||||
class Answer(Enum):
|
||||
_ignore_ = "IGNORED _other_ignored also_ignored"
|
||||
|
||||
@@ -514,6 +572,7 @@ class Answer(Enum):
|
||||
_other_ignored = "test"
|
||||
also_ignored = "test2"
|
||||
|
||||
|
||||
# revealed: tuple[Literal["YES"], Literal["NO"]]
|
||||
reveal_type(enum_members(Answer))
|
||||
```
|
||||
@@ -530,6 +589,7 @@ class Answer2(Enum):
|
||||
MAYBE = 3
|
||||
_other = "test"
|
||||
|
||||
|
||||
# TODO: This should be `tuple[Literal["YES"], Literal["NO"]]`
|
||||
# revealed: tuple[Literal["YES"], Literal["NO"], Literal["MAYBE"], Literal["_other"]]
|
||||
reveal_type(enum_members(Answer2))
|
||||
@@ -544,10 +604,12 @@ conflicting with `Enum.name` and `Enum.value`):
|
||||
from enum import Enum
|
||||
from ty_extensions import enum_members
|
||||
|
||||
|
||||
class Answer(Enum):
|
||||
name = 1
|
||||
value = 2
|
||||
|
||||
|
||||
# revealed: tuple[Literal["name"], Literal["value"]]
|
||||
reveal_type(enum_members(Answer))
|
||||
|
||||
@@ -560,11 +622,13 @@ reveal_type(Answer.value) # revealed: Literal[Answer.value]
|
||||
```py
|
||||
from enum import Enum
|
||||
|
||||
|
||||
class Color(Enum):
|
||||
RED = 1
|
||||
GREEN = 2
|
||||
BLUE = 3
|
||||
|
||||
|
||||
for color in Color:
|
||||
reveal_type(color) # revealed: Color
|
||||
|
||||
@@ -579,25 +643,31 @@ Methods and non-member attributes defined in the enum class can be accessed on e
|
||||
```py
|
||||
from enum import Enum
|
||||
|
||||
|
||||
class Answer(Enum):
|
||||
YES = 1
|
||||
NO = 2
|
||||
|
||||
def is_yes(self) -> bool:
|
||||
return self == Answer.YES
|
||||
|
||||
constant: int = 1
|
||||
|
||||
|
||||
reveal_type(Answer.YES.is_yes()) # revealed: bool
|
||||
reveal_type(Answer.YES.constant) # revealed: int
|
||||
|
||||
|
||||
class MyEnum(Enum):
|
||||
def some_method(self) -> None:
|
||||
pass
|
||||
|
||||
|
||||
class MyAnswer(MyEnum):
|
||||
YES = 1
|
||||
NO = 2
|
||||
|
||||
|
||||
reveal_type(MyAnswer.YES.some_method()) # revealed: None
|
||||
```
|
||||
|
||||
@@ -606,10 +676,12 @@ reveal_type(MyAnswer.YES.some_method()) # revealed: None
|
||||
```py
|
||||
from enum import Enum
|
||||
|
||||
|
||||
class Answer(Enum):
|
||||
YES = 1
|
||||
NO = 2
|
||||
|
||||
|
||||
def _(answer: type[Answer]) -> None:
|
||||
reveal_type(answer.YES) # revealed: Literal[Answer.YES]
|
||||
reveal_type(answer.NO) # revealed: Literal[Answer.NO]
|
||||
@@ -622,6 +694,7 @@ from enum import Enum
|
||||
from typing import Callable
|
||||
import sys
|
||||
|
||||
|
||||
class Printer(Enum):
|
||||
STDOUT = 1
|
||||
STDERR = 2
|
||||
@@ -632,6 +705,7 @@ class Printer(Enum):
|
||||
elif self == Printer.STDERR:
|
||||
print(msg, file=sys.stderr)
|
||||
|
||||
|
||||
Printer.STDOUT("Hello, world!")
|
||||
Printer.STDERR("An error occurred!")
|
||||
|
||||
@@ -649,16 +723,20 @@ callable("Another error!")
|
||||
from enum import Enum
|
||||
from typing import Literal
|
||||
|
||||
|
||||
class Color(Enum):
|
||||
RED = 1
|
||||
GREEN = 2
|
||||
BLUE = 3
|
||||
|
||||
|
||||
reveal_type(Color.RED._name_) # revealed: Literal["RED"]
|
||||
|
||||
|
||||
def _(red_or_blue: Literal[Color.RED, Color.BLUE]):
|
||||
reveal_type(red_or_blue.name) # revealed: Literal["RED", "BLUE"]
|
||||
|
||||
|
||||
def _(any_color: Color):
|
||||
# TODO: Literal["RED", "GREEN", "BLUE"]
|
||||
reveal_type(any_color.name) # revealed: Any
|
||||
@@ -675,21 +753,25 @@ python-version = "3.11"
|
||||
from enum import Enum, StrEnum
|
||||
from typing import Literal
|
||||
|
||||
|
||||
class Color(Enum):
|
||||
RED = 1
|
||||
GREEN = 2
|
||||
BLUE = 3
|
||||
|
||||
|
||||
reveal_type(Color.RED.value) # revealed: Literal[1]
|
||||
reveal_type(Color.RED._value_) # revealed: Literal[1]
|
||||
|
||||
reveal_type(Color.GREEN.value) # revealed: Literal[2]
|
||||
reveal_type(Color.GREEN._value_) # revealed: Literal[2]
|
||||
|
||||
|
||||
class Answer(StrEnum):
|
||||
YES = "yes"
|
||||
NO = "no"
|
||||
|
||||
|
||||
reveal_type(Answer.YES.value) # revealed: Literal["yes"]
|
||||
reveal_type(Answer.YES._value_) # revealed: Literal["yes"]
|
||||
|
||||
@@ -706,15 +788,18 @@ An enum with one or more defined members cannot be subclassed. They are implicit
|
||||
```py
|
||||
from enum import Enum
|
||||
|
||||
|
||||
class Color(Enum):
|
||||
RED = 1
|
||||
GREEN = 2
|
||||
BLUE = 3
|
||||
|
||||
|
||||
# error: [subclass-of-final-class] "Class `ExtendedColor` cannot inherit from final class `Color`"
|
||||
class ExtendedColor(Color):
|
||||
YELLOW = 4
|
||||
|
||||
|
||||
def f(color: Color):
|
||||
if isinstance(color, int):
|
||||
reveal_type(color) # revealed: Never
|
||||
@@ -726,14 +811,17 @@ An `Enum` subclass without any defined members can be subclassed:
|
||||
from enum import Enum
|
||||
from ty_extensions import enum_members
|
||||
|
||||
|
||||
class MyEnum(Enum):
|
||||
def some_method(self) -> None:
|
||||
pass
|
||||
|
||||
|
||||
class Answer(MyEnum):
|
||||
YES = 1
|
||||
NO = 2
|
||||
|
||||
|
||||
# revealed: tuple[Literal["YES"], Literal["NO"]]
|
||||
reveal_type(enum_members(Answer))
|
||||
```
|
||||
@@ -743,14 +831,18 @@ reveal_type(enum_members(Answer))
|
||||
```py
|
||||
from enum import Enum
|
||||
|
||||
|
||||
class Answer(Enum):
|
||||
YES = 1
|
||||
NO = 2
|
||||
|
||||
|
||||
reveal_type(type(Answer.YES)) # revealed: <class 'Answer'>
|
||||
|
||||
|
||||
class NoMembers(Enum): ...
|
||||
|
||||
|
||||
def _(answer: Answer, no_members: NoMembers):
|
||||
reveal_type(type(answer)) # revealed: <class 'Answer'>
|
||||
reveal_type(type(no_members)) # revealed: type[NoMembers]
|
||||
@@ -763,6 +855,7 @@ from enum import Enum
|
||||
from typing import Literal
|
||||
from ty_extensions import enum_members
|
||||
|
||||
|
||||
class Answer(Enum):
|
||||
YES = 1
|
||||
NO = 2
|
||||
@@ -771,6 +864,7 @@ class Answer(Enum):
|
||||
def yes(cls) -> "Literal[Answer.YES]":
|
||||
return Answer.YES
|
||||
|
||||
|
||||
# revealed: tuple[Literal["YES"], Literal["NO"]]
|
||||
reveal_type(enum_members(Answer))
|
||||
```
|
||||
@@ -786,14 +880,17 @@ prior to Python 3.11.
|
||||
```py
|
||||
from enum import Enum, EnumMeta
|
||||
|
||||
|
||||
class CustomEnumSubclass(Enum):
|
||||
def custom_method(self) -> int:
|
||||
return 0
|
||||
|
||||
|
||||
class EnumWithCustomEnumSubclass(CustomEnumSubclass):
|
||||
NO = 0
|
||||
YES = 1
|
||||
|
||||
|
||||
reveal_type(EnumWithCustomEnumSubclass.NO) # revealed: Literal[EnumWithCustomEnumSubclass.NO]
|
||||
reveal_type(EnumWithCustomEnumSubclass.NO.custom_method()) # revealed: int
|
||||
```
|
||||
@@ -808,18 +905,23 @@ python-version = "3.9"
|
||||
```py
|
||||
from enum import Enum, EnumMeta
|
||||
|
||||
|
||||
class EnumWithEnumMetaMetaclass(metaclass=EnumMeta):
|
||||
NO = 0
|
||||
YES = 1
|
||||
|
||||
|
||||
reveal_type(EnumWithEnumMetaMetaclass.NO) # revealed: Literal[EnumWithEnumMetaMetaclass.NO]
|
||||
|
||||
|
||||
class SubclassOfEnumMeta(EnumMeta): ...
|
||||
|
||||
|
||||
class EnumWithSubclassOfEnumMetaMetaclass(metaclass=SubclassOfEnumMeta):
|
||||
NO = 0
|
||||
YES = 1
|
||||
|
||||
|
||||
reveal_type(EnumWithSubclassOfEnumMetaMetaclass.NO) # revealed: Literal[EnumWithSubclassOfEnumMetaMetaclass.NO]
|
||||
|
||||
# Attributes like `.value` can *not* be accessed on members of these enums:
|
||||
@@ -843,18 +945,23 @@ python-version = "3.11"
|
||||
```py
|
||||
from enum import Enum, EnumType
|
||||
|
||||
|
||||
class EnumWithEnumMetaMetaclass(metaclass=EnumType):
|
||||
NO = 0
|
||||
YES = 1
|
||||
|
||||
|
||||
reveal_type(EnumWithEnumMetaMetaclass.NO) # revealed: Literal[EnumWithEnumMetaMetaclass.NO]
|
||||
|
||||
|
||||
class SubclassOfEnumMeta(EnumType): ...
|
||||
|
||||
|
||||
class EnumWithSubclassOfEnumMetaMetaclass(metaclass=SubclassOfEnumMeta):
|
||||
NO = 0
|
||||
YES = 1
|
||||
|
||||
|
||||
reveal_type(EnumWithSubclassOfEnumMetaMetaclass.NO) # revealed: Literal[EnumWithSubclassOfEnumMetaMetaclass.NO]
|
||||
|
||||
# error: [unresolved-attribute]
|
||||
@@ -873,11 +980,13 @@ To do: <https://typing.python.org/en/latest/spec/enums.html#enum-definition>
|
||||
from enum import Enum
|
||||
from typing_extensions import assert_never
|
||||
|
||||
|
||||
class Color(Enum):
|
||||
RED = 1
|
||||
GREEN = 2
|
||||
BLUE = 3
|
||||
|
||||
|
||||
def color_name(color: Color) -> str:
|
||||
if color is Color.RED:
|
||||
return "Red"
|
||||
@@ -888,6 +997,7 @@ def color_name(color: Color) -> str:
|
||||
else:
|
||||
assert_never(color)
|
||||
|
||||
|
||||
# No `invalid-return-type` error here because the implicit `else` branch is detected as unreachable:
|
||||
def color_name_without_assertion(color: Color) -> str:
|
||||
if color is Color.RED:
|
||||
@@ -897,6 +1007,7 @@ def color_name_without_assertion(color: Color) -> str:
|
||||
elif color is Color.BLUE:
|
||||
return "Blue"
|
||||
|
||||
|
||||
def color_name_misses_one_variant(color: Color) -> str:
|
||||
if color is Color.RED:
|
||||
return "Red"
|
||||
@@ -905,9 +1016,11 @@ def color_name_misses_one_variant(color: Color) -> str:
|
||||
else:
|
||||
assert_never(color) # error: [type-assertion-failure] "Type `Literal[Color.BLUE]` is not equivalent to `Never`"
|
||||
|
||||
|
||||
class Singleton(Enum):
|
||||
VALUE = 1
|
||||
|
||||
|
||||
def singleton_check(value: Singleton) -> str:
|
||||
if value is Singleton.VALUE:
|
||||
return "Singleton value"
|
||||
@@ -926,11 +1039,13 @@ python-version = "3.10"
|
||||
from enum import Enum
|
||||
from typing_extensions import assert_never
|
||||
|
||||
|
||||
class Color(Enum):
|
||||
RED = 1
|
||||
GREEN = 2
|
||||
BLUE = 3
|
||||
|
||||
|
||||
def color_name(color: Color) -> str:
|
||||
match color:
|
||||
case Color.RED:
|
||||
@@ -942,6 +1057,7 @@ def color_name(color: Color) -> str:
|
||||
case _:
|
||||
assert_never(color)
|
||||
|
||||
|
||||
def color_name_without_assertion(color: Color) -> str:
|
||||
match color:
|
||||
case Color.RED:
|
||||
@@ -951,6 +1067,7 @@ def color_name_without_assertion(color: Color) -> str:
|
||||
case Color.BLUE:
|
||||
return "Blue"
|
||||
|
||||
|
||||
def color_name_misses_one_variant(color: Color) -> str:
|
||||
match color:
|
||||
case Color.RED:
|
||||
@@ -960,9 +1077,11 @@ def color_name_misses_one_variant(color: Color) -> str:
|
||||
case _:
|
||||
assert_never(color) # error: [type-assertion-failure] "Type `Literal[Color.BLUE]` is not equivalent to `Never`"
|
||||
|
||||
|
||||
class Singleton(Enum):
|
||||
VALUE = 1
|
||||
|
||||
|
||||
def singleton_check(value: Singleton) -> str:
|
||||
match value:
|
||||
case Singleton.VALUE:
|
||||
@@ -978,10 +1097,12 @@ def singleton_check(value: Singleton) -> str:
|
||||
```py
|
||||
from enum import Enum
|
||||
|
||||
|
||||
class Color(Enum):
|
||||
RED = 1
|
||||
GREEN = 2
|
||||
|
||||
|
||||
reveal_type(Color.RED == Color.RED) # revealed: Literal[True]
|
||||
reveal_type(Color.RED != Color.RED) # revealed: Literal[False]
|
||||
```
|
||||
@@ -991,6 +1112,7 @@ reveal_type(Color.RED != Color.RED) # revealed: Literal[False]
|
||||
```py
|
||||
from enum import Enum
|
||||
|
||||
|
||||
class Color(Enum):
|
||||
RED = 1
|
||||
GREEN = 2
|
||||
@@ -998,6 +1120,7 @@ class Color(Enum):
|
||||
def __eq__(self, other: object) -> bool:
|
||||
return False
|
||||
|
||||
|
||||
reveal_type(Color.RED == Color.RED) # revealed: bool
|
||||
```
|
||||
|
||||
@@ -1006,6 +1129,7 @@ reveal_type(Color.RED == Color.RED) # revealed: bool
|
||||
```py
|
||||
from enum import Enum
|
||||
|
||||
|
||||
class Color(Enum):
|
||||
RED = 1
|
||||
GREEN = 2
|
||||
@@ -1013,6 +1137,7 @@ class Color(Enum):
|
||||
def __ne__(self, other: object) -> bool:
|
||||
return False
|
||||
|
||||
|
||||
reveal_type(Color.RED != Color.RED) # revealed: bool
|
||||
```
|
||||
|
||||
@@ -1033,6 +1158,7 @@ python-version = "3.12"
|
||||
```py
|
||||
from enum import Enum
|
||||
|
||||
|
||||
# error: [invalid-generic-enum] "Enum class `E` cannot be generic"
|
||||
class E[T](Enum):
|
||||
A = 1
|
||||
@@ -1049,6 +1175,7 @@ from typing import Generic, TypeVar
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
|
||||
# error: [invalid-generic-enum] "Enum class `F` cannot be generic"
|
||||
class F(Enum, Generic[T]):
|
||||
A = 1
|
||||
@@ -1065,6 +1192,7 @@ from typing import Generic, TypeVar
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
|
||||
# error: [invalid-generic-enum] "Enum class `G` cannot be generic"
|
||||
class G(Generic[T], Enum):
|
||||
A = 1
|
||||
@@ -1086,10 +1214,12 @@ from typing import Generic, TypeVar
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
|
||||
# error: [invalid-generic-enum] "Enum class `MyIntEnum` cannot be generic"
|
||||
class MyIntEnum[T](IntEnum):
|
||||
A = 1
|
||||
|
||||
|
||||
# error: [invalid-generic-enum] "Enum class `MyFlagEnum` cannot be generic"
|
||||
class MyFlagEnum(IntEnum, Generic[T]):
|
||||
A = 1
|
||||
@@ -1110,9 +1240,11 @@ from typing import Generic, TypeVar
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
|
||||
class MyEnumBase(Enum):
|
||||
def some_method(self) -> None: ...
|
||||
|
||||
|
||||
# error: [invalid-generic-enum] "Enum class `MyEnum` cannot be generic"
|
||||
class MyEnum[T](MyEnumBase):
|
||||
A = 1
|
||||
|
||||
@@ -68,8 +68,10 @@ MRO, as the dynamic element in the MRO could materialize to some subclass of `Ba
|
||||
```py
|
||||
from compat import BASE_EXCEPTION_CLASS # error: [unresolved-import] "Cannot resolve imported module `compat`"
|
||||
|
||||
|
||||
class Error(BASE_EXCEPTION_CLASS): ...
|
||||
|
||||
|
||||
try:
|
||||
...
|
||||
except Error as err:
|
||||
@@ -95,6 +97,7 @@ python-version = "3.12"
|
||||
```py
|
||||
from typing import Callable
|
||||
|
||||
|
||||
def silence[T: type[BaseException]](
|
||||
func: Callable[[], None],
|
||||
exception_type: T,
|
||||
@@ -104,6 +107,7 @@ def silence[T: type[BaseException]](
|
||||
except exception_type as e:
|
||||
reveal_type(e) # revealed: T'instance@silence
|
||||
|
||||
|
||||
def silence2[
|
||||
T: (
|
||||
type[ValueError],
|
||||
@@ -133,6 +137,7 @@ try:
|
||||
except (ValueError, OSError, "foo", b"bar") as e:
|
||||
reveal_type(e) # revealed: ValueError | OSError | Unknown
|
||||
|
||||
|
||||
def foo(
|
||||
x: type[str],
|
||||
y: tuple[type[OSError], type[RuntimeError], int],
|
||||
@@ -150,6 +155,7 @@ def foo(
|
||||
except z as g:
|
||||
reveal_type(g) # revealed: Unknown
|
||||
|
||||
|
||||
try:
|
||||
{}.get("foo")
|
||||
# error: [invalid-exception-caught]
|
||||
@@ -180,9 +186,11 @@ try:
|
||||
except:
|
||||
...
|
||||
|
||||
|
||||
def _(e: Exception | type[Exception]):
|
||||
raise e # fine
|
||||
|
||||
|
||||
def _(e: Exception | type[Exception] | None):
|
||||
raise e # error: [invalid-raise]
|
||||
```
|
||||
@@ -196,30 +204,35 @@ def _():
|
||||
except:
|
||||
...
|
||||
|
||||
|
||||
def _():
|
||||
try:
|
||||
raise StopIteration from MemoryError() # fine
|
||||
except:
|
||||
...
|
||||
|
||||
|
||||
def _():
|
||||
try:
|
||||
raise BufferError() from None # fine
|
||||
except:
|
||||
...
|
||||
|
||||
|
||||
def _():
|
||||
try:
|
||||
raise ZeroDivisionError from False # error: [invalid-raise]
|
||||
except:
|
||||
...
|
||||
|
||||
|
||||
def _():
|
||||
try:
|
||||
raise SystemExit from bool() # error: [invalid-raise]
|
||||
except:
|
||||
...
|
||||
|
||||
|
||||
def _():
|
||||
try:
|
||||
raise
|
||||
@@ -227,6 +240,7 @@ def _():
|
||||
reveal_type(e) # revealed: KeyboardInterrupt
|
||||
raise LookupError from e # fine
|
||||
|
||||
|
||||
def _():
|
||||
try:
|
||||
raise
|
||||
@@ -234,12 +248,15 @@ def _():
|
||||
reveal_type(e) # revealed: Unknown
|
||||
raise KeyError from e
|
||||
|
||||
|
||||
def _(e: Exception | type[Exception]):
|
||||
raise ModuleNotFoundError from e # fine
|
||||
|
||||
|
||||
def _(e: Exception | type[Exception] | None):
|
||||
raise IndexError from e # fine
|
||||
|
||||
|
||||
def _(e: int | None):
|
||||
raise IndexError from e # error: [invalid-raise]
|
||||
```
|
||||
@@ -259,9 +276,11 @@ reveal_type(e) # revealed: Unknown
|
||||
|
||||
e = None
|
||||
|
||||
|
||||
def cond() -> bool:
|
||||
return True
|
||||
|
||||
|
||||
try:
|
||||
if cond():
|
||||
raise ValueError()
|
||||
@@ -270,6 +289,7 @@ except ValueError as e:
|
||||
# error: [possibly-unresolved-reference]
|
||||
reveal_type(e) # revealed: None
|
||||
|
||||
|
||||
def f(x: type[Exception]):
|
||||
e = None
|
||||
try:
|
||||
|
||||
@@ -33,6 +33,7 @@ completing. The type of `x` at the beginning of the `except` suite in this examp
|
||||
def could_raise_returns_str() -> str:
|
||||
return "foo"
|
||||
|
||||
|
||||
x = 1
|
||||
|
||||
try:
|
||||
@@ -74,6 +75,7 @@ control-flow analysis.
|
||||
def could_raise_returns_str() -> str:
|
||||
return "foo"
|
||||
|
||||
|
||||
x = 1
|
||||
|
||||
try:
|
||||
@@ -102,6 +104,7 @@ The inferred type of `x` at this point is the union of the types at the end of t
|
||||
def could_raise_returns_str() -> str:
|
||||
return "foo"
|
||||
|
||||
|
||||
x = 1
|
||||
|
||||
try:
|
||||
@@ -134,6 +137,7 @@ the `except` suite:
|
||||
def could_raise_returns_str() -> str:
|
||||
return "foo"
|
||||
|
||||
|
||||
x = 1
|
||||
|
||||
try:
|
||||
@@ -190,6 +194,7 @@ type of `x` at the end of the example is therefore `Literal[2]`:
|
||||
def could_raise_returns_str() -> str:
|
||||
return "foo"
|
||||
|
||||
|
||||
x = 1
|
||||
|
||||
try:
|
||||
@@ -242,18 +247,26 @@ suites:
|
||||
|
||||
```py
|
||||
class A: ...
|
||||
|
||||
|
||||
class B: ...
|
||||
|
||||
|
||||
class C: ...
|
||||
|
||||
|
||||
def could_raise_returns_A() -> A:
|
||||
return A()
|
||||
|
||||
|
||||
def could_raise_returns_B() -> B:
|
||||
return B()
|
||||
|
||||
|
||||
def could_raise_returns_C() -> C:
|
||||
return C()
|
||||
|
||||
|
||||
x = 1
|
||||
|
||||
try:
|
||||
@@ -305,14 +318,19 @@ An example with multiple `except` branches and a `finally` branch:
|
||||
|
||||
```py
|
||||
class D: ...
|
||||
|
||||
|
||||
class E: ...
|
||||
|
||||
|
||||
def could_raise_returns_D() -> D:
|
||||
return D()
|
||||
|
||||
|
||||
def could_raise_returns_E() -> E:
|
||||
return E()
|
||||
|
||||
|
||||
x = 1
|
||||
|
||||
try:
|
||||
@@ -346,26 +364,40 @@ an exception raised *there*.
|
||||
|
||||
```py
|
||||
class A: ...
|
||||
|
||||
|
||||
class B: ...
|
||||
|
||||
|
||||
class C: ...
|
||||
|
||||
|
||||
class D: ...
|
||||
|
||||
|
||||
class E: ...
|
||||
|
||||
|
||||
def could_raise_returns_A() -> A:
|
||||
return A()
|
||||
|
||||
|
||||
def could_raise_returns_B() -> B:
|
||||
return B()
|
||||
|
||||
|
||||
def could_raise_returns_C() -> C:
|
||||
return C()
|
||||
|
||||
|
||||
def could_raise_returns_D() -> D:
|
||||
return D()
|
||||
|
||||
|
||||
def could_raise_returns_E() -> E:
|
||||
return E()
|
||||
|
||||
|
||||
x = 1
|
||||
|
||||
try:
|
||||
@@ -395,14 +427,19 @@ The same again, this time with multiple `except` branches:
|
||||
|
||||
```py
|
||||
class F: ...
|
||||
|
||||
|
||||
class G: ...
|
||||
|
||||
|
||||
def could_raise_returns_F() -> F:
|
||||
return F()
|
||||
|
||||
|
||||
def could_raise_returns_G() -> G:
|
||||
return G()
|
||||
|
||||
|
||||
x = 1
|
||||
|
||||
try:
|
||||
@@ -446,50 +483,82 @@ jumping out of that suite prior to the suite running to completion.
|
||||
|
||||
```py
|
||||
class A: ...
|
||||
|
||||
|
||||
class B: ...
|
||||
|
||||
|
||||
class C: ...
|
||||
|
||||
|
||||
class D: ...
|
||||
|
||||
|
||||
class E: ...
|
||||
|
||||
|
||||
class F: ...
|
||||
|
||||
|
||||
class G: ...
|
||||
|
||||
|
||||
class H: ...
|
||||
|
||||
|
||||
class I: ...
|
||||
|
||||
|
||||
class J: ...
|
||||
|
||||
|
||||
class K: ...
|
||||
|
||||
|
||||
def could_raise_returns_A() -> A:
|
||||
return A()
|
||||
|
||||
|
||||
def could_raise_returns_B() -> B:
|
||||
return B()
|
||||
|
||||
|
||||
def could_raise_returns_C() -> C:
|
||||
return C()
|
||||
|
||||
|
||||
def could_raise_returns_D() -> D:
|
||||
return D()
|
||||
|
||||
|
||||
def could_raise_returns_E() -> E:
|
||||
return E()
|
||||
|
||||
|
||||
def could_raise_returns_F() -> F:
|
||||
return F()
|
||||
|
||||
|
||||
def could_raise_returns_G() -> G:
|
||||
return G()
|
||||
|
||||
|
||||
def could_raise_returns_H() -> H:
|
||||
return H()
|
||||
|
||||
|
||||
def could_raise_returns_I() -> I:
|
||||
return I()
|
||||
|
||||
|
||||
def could_raise_returns_J() -> J:
|
||||
return J()
|
||||
|
||||
|
||||
def could_raise_returns_K() -> K:
|
||||
return K()
|
||||
|
||||
|
||||
x = 1
|
||||
|
||||
try:
|
||||
@@ -549,26 +618,40 @@ in the outer scope:
|
||||
|
||||
```py
|
||||
class A: ...
|
||||
|
||||
|
||||
class B: ...
|
||||
|
||||
|
||||
class C: ...
|
||||
|
||||
|
||||
class D: ...
|
||||
|
||||
|
||||
class E: ...
|
||||
|
||||
|
||||
def could_raise_returns_A() -> A:
|
||||
return A()
|
||||
|
||||
|
||||
def could_raise_returns_B() -> B:
|
||||
return B()
|
||||
|
||||
|
||||
def could_raise_returns_C() -> C:
|
||||
return C()
|
||||
|
||||
|
||||
def could_raise_returns_D() -> D:
|
||||
return D()
|
||||
|
||||
|
||||
def could_raise_returns_E() -> E:
|
||||
return E()
|
||||
|
||||
|
||||
x = 1
|
||||
|
||||
try:
|
||||
@@ -590,6 +673,7 @@ try:
|
||||
# TODO: should be `A | B | C | D`
|
||||
reveal_type(x) # revealed: B | D
|
||||
reveal_type(x) # revealed: B | D
|
||||
|
||||
x = foo
|
||||
reveal_type(x) # revealed: def foo(param=...) -> Unknown
|
||||
except:
|
||||
|
||||
@@ -10,6 +10,7 @@ python-version = "3.11"
|
||||
```py
|
||||
from typing import Literal, assert_never
|
||||
|
||||
|
||||
def if_else_exhaustive(x: Literal[0, 1, "a"]):
|
||||
if x == 0:
|
||||
pass
|
||||
@@ -22,6 +23,7 @@ def if_else_exhaustive(x: Literal[0, 1, "a"]):
|
||||
|
||||
assert_never(x)
|
||||
|
||||
|
||||
def if_else_exhaustive_no_assertion(x: Literal[0, 1, "a"]) -> int:
|
||||
if x == 0:
|
||||
return 0
|
||||
@@ -30,6 +32,7 @@ def if_else_exhaustive_no_assertion(x: Literal[0, 1, "a"]) -> int:
|
||||
elif x == "a":
|
||||
return 2
|
||||
|
||||
|
||||
def if_else_non_exhaustive(x: Literal[0, 1, "a"]):
|
||||
if x == 0:
|
||||
pass
|
||||
@@ -41,6 +44,7 @@ def if_else_non_exhaustive(x: Literal[0, 1, "a"]):
|
||||
# this diagnostic is correct: the inferred type of `x` is `Literal[1]`
|
||||
assert_never(x) # error: [type-assertion-failure]
|
||||
|
||||
|
||||
def match_exhaustive(x: Literal[0, 1, "a"]):
|
||||
match x:
|
||||
case 0:
|
||||
@@ -54,6 +58,7 @@ def match_exhaustive(x: Literal[0, 1, "a"]):
|
||||
|
||||
assert_never(x)
|
||||
|
||||
|
||||
def match_exhaustive_no_assertion(x: Literal[0, 1, "a"]) -> int:
|
||||
match x:
|
||||
case 0:
|
||||
@@ -63,6 +68,7 @@ def match_exhaustive_no_assertion(x: Literal[0, 1, "a"]) -> int:
|
||||
case "a":
|
||||
return 2
|
||||
|
||||
|
||||
def match_non_exhaustive(x: Literal[0, 1, "a"]):
|
||||
match x:
|
||||
case 0:
|
||||
@@ -75,6 +81,7 @@ def match_non_exhaustive(x: Literal[0, 1, "a"]):
|
||||
# this diagnostic is correct: the inferred type of `x` is `Literal[1]`
|
||||
assert_never(x) # error: [type-assertion-failure]
|
||||
|
||||
|
||||
# This is based on real-world code:
|
||||
# https://github.com/scipy/scipy/blob/99c0ef6af161a4d8157cae5276a20c30b7677c6f/scipy/linalg/tests/test_lapack.py#L147-L171
|
||||
def exhaustiveness_using_containment_checks():
|
||||
@@ -98,11 +105,13 @@ def exhaustiveness_using_containment_checks():
|
||||
from enum import Enum
|
||||
from typing import assert_never
|
||||
|
||||
|
||||
class Color(Enum):
|
||||
RED = 1
|
||||
GREEN = 2
|
||||
BLUE = 3
|
||||
|
||||
|
||||
def if_else_exhaustive(x: Color):
|
||||
if x == Color.RED:
|
||||
pass
|
||||
@@ -115,6 +124,7 @@ def if_else_exhaustive(x: Color):
|
||||
|
||||
assert_never(x)
|
||||
|
||||
|
||||
def if_else_exhaustive_no_assertion(x: Color) -> int:
|
||||
if x == Color.RED:
|
||||
return 1
|
||||
@@ -123,6 +133,7 @@ def if_else_exhaustive_no_assertion(x: Color) -> int:
|
||||
elif x == Color.BLUE:
|
||||
return 3
|
||||
|
||||
|
||||
def if_else_non_exhaustive(x: Color):
|
||||
if x == Color.RED:
|
||||
pass
|
||||
@@ -134,6 +145,7 @@ def if_else_non_exhaustive(x: Color):
|
||||
# this diagnostic is correct: inferred type of `x` is `Literal[Color.GREEN]`
|
||||
assert_never(x) # error: [type-assertion-failure]
|
||||
|
||||
|
||||
def match_exhaustive(x: Color):
|
||||
match x:
|
||||
case Color.RED:
|
||||
@@ -147,6 +159,7 @@ def match_exhaustive(x: Color):
|
||||
|
||||
assert_never(x)
|
||||
|
||||
|
||||
def match_exhaustive_2(x: Color):
|
||||
match x:
|
||||
case Color.RED:
|
||||
@@ -158,6 +171,7 @@ def match_exhaustive_2(x: Color):
|
||||
|
||||
assert_never(x)
|
||||
|
||||
|
||||
def match_exhaustive_no_assertion(x: Color) -> int:
|
||||
match x:
|
||||
case Color.RED:
|
||||
@@ -167,6 +181,7 @@ def match_exhaustive_no_assertion(x: Color) -> int:
|
||||
case Color.BLUE:
|
||||
return 3
|
||||
|
||||
|
||||
def match_non_exhaustive(x: Color):
|
||||
match x:
|
||||
case Color.RED:
|
||||
@@ -190,13 +205,20 @@ python-version = "3.12"
|
||||
```py
|
||||
from typing import assert_never
|
||||
|
||||
|
||||
class A: ...
|
||||
|
||||
|
||||
class B: ...
|
||||
|
||||
|
||||
class C: ...
|
||||
|
||||
|
||||
class GenericClass[T]:
|
||||
x: T
|
||||
|
||||
|
||||
def if_else_exhaustive(x: A | B | C):
|
||||
if isinstance(x, A):
|
||||
pass
|
||||
@@ -209,6 +231,7 @@ def if_else_exhaustive(x: A | B | C):
|
||||
|
||||
assert_never(x)
|
||||
|
||||
|
||||
def if_else_exhaustive_no_assertion(x: A | B | C) -> int:
|
||||
if isinstance(x, A):
|
||||
return 0
|
||||
@@ -217,6 +240,7 @@ def if_else_exhaustive_no_assertion(x: A | B | C) -> int:
|
||||
elif isinstance(x, C):
|
||||
return 2
|
||||
|
||||
|
||||
def if_else_non_exhaustive(x: A | B | C):
|
||||
if isinstance(x, A):
|
||||
pass
|
||||
@@ -228,6 +252,7 @@ def if_else_non_exhaustive(x: A | B | C):
|
||||
# this diagnostic is correct: the inferred type of `x` is `B & ~A & ~C`
|
||||
assert_never(x) # error: [type-assertion-failure]
|
||||
|
||||
|
||||
def match_exhaustive(x: A | B | C):
|
||||
match x:
|
||||
case A():
|
||||
@@ -241,6 +266,7 @@ def match_exhaustive(x: A | B | C):
|
||||
|
||||
assert_never(x)
|
||||
|
||||
|
||||
def match_exhaustive_no_assertion(x: A | B | C) -> int:
|
||||
match x:
|
||||
case A():
|
||||
@@ -250,6 +276,7 @@ def match_exhaustive_no_assertion(x: A | B | C) -> int:
|
||||
case C():
|
||||
return 2
|
||||
|
||||
|
||||
def match_non_exhaustive(x: A | B | C):
|
||||
match x:
|
||||
case A():
|
||||
@@ -262,6 +289,7 @@ def match_non_exhaustive(x: A | B | C):
|
||||
# this diagnostic is correct: the inferred type of `x` is `B & ~A & ~C`
|
||||
assert_never(x) # error: [type-assertion-failure]
|
||||
|
||||
|
||||
# Note: no invalid-return-type diagnostic; the `match` is exhaustive
|
||||
def match_exhaustive_generic[T](obj: GenericClass[T]) -> GenericClass[T]:
|
||||
match obj:
|
||||
@@ -284,21 +312,31 @@ python-version = "3.12"
|
||||
```py
|
||||
from typing import assert_never
|
||||
|
||||
|
||||
class A[T]:
|
||||
value: T
|
||||
|
||||
|
||||
class ASub[T](A[T]): ...
|
||||
|
||||
|
||||
class B[T]:
|
||||
value: T
|
||||
|
||||
|
||||
class C[T]:
|
||||
value: T
|
||||
|
||||
|
||||
class D: ...
|
||||
|
||||
|
||||
class E: ...
|
||||
|
||||
|
||||
class F: ...
|
||||
|
||||
|
||||
def if_else_exhaustive(x: A[D] | B[E] | C[F]):
|
||||
if isinstance(x, A):
|
||||
pass
|
||||
@@ -310,6 +348,7 @@ def if_else_exhaustive(x: A[D] | B[E] | C[F]):
|
||||
no_diagnostic_here
|
||||
assert_never(x)
|
||||
|
||||
|
||||
def if_else_exhaustive_no_assertion(x: A[D] | B[E] | C[F]) -> int:
|
||||
if isinstance(x, A):
|
||||
return 0
|
||||
@@ -318,6 +357,7 @@ def if_else_exhaustive_no_assertion(x: A[D] | B[E] | C[F]) -> int:
|
||||
elif isinstance(x, C):
|
||||
return 2
|
||||
|
||||
|
||||
def if_else_non_exhaustive(x: A[D] | B[E] | C[F]):
|
||||
if isinstance(x, A):
|
||||
pass
|
||||
@@ -329,6 +369,7 @@ def if_else_non_exhaustive(x: A[D] | B[E] | C[F]):
|
||||
# this diagnostic is correct: the inferred type of `x` is `B[E] & ~A[D] & ~C[F]`
|
||||
assert_never(x) # error: [type-assertion-failure]
|
||||
|
||||
|
||||
def match_exhaustive(x: A[D] | B[E] | C[F]):
|
||||
match x:
|
||||
case A():
|
||||
@@ -341,6 +382,7 @@ def match_exhaustive(x: A[D] | B[E] | C[F]):
|
||||
no_diagnostic_here
|
||||
assert_never(x)
|
||||
|
||||
|
||||
def match_exhaustive_no_assertion(x: A[D] | B[E] | C[F]) -> int:
|
||||
match x:
|
||||
case A():
|
||||
@@ -350,6 +392,7 @@ def match_exhaustive_no_assertion(x: A[D] | B[E] | C[F]) -> int:
|
||||
case C():
|
||||
return 2
|
||||
|
||||
|
||||
def match_non_exhaustive(x: A[D] | B[E] | C[F]):
|
||||
match x:
|
||||
case A():
|
||||
@@ -362,6 +405,7 @@ def match_non_exhaustive(x: A[D] | B[E] | C[F]):
|
||||
# this diagnostic is correct: the inferred type of `x` is `B[E] & ~A[D] & ~C[F]`
|
||||
assert_never(x) # error: [type-assertion-failure]
|
||||
|
||||
|
||||
# This function might seem a bit silly, but it's a pattern that exists in real-world code!
|
||||
# see https://github.com/bokeh/bokeh/blob/adef0157284696ce86961b2089c75fddda53c15c/src/bokeh/core/property/container.py#L130-L140
|
||||
def no_invalid_return_diagnostic_here_either[T](x: A[T]) -> ASub[T]:
|
||||
@@ -384,6 +428,7 @@ def no_invalid_return_diagnostic_here_either[T](x: A[T]) -> ASub[T]:
|
||||
```py
|
||||
from typing import assert_never
|
||||
|
||||
|
||||
def as_pattern_exhaustive(subject: int | str):
|
||||
match subject:
|
||||
case int() as x:
|
||||
@@ -395,6 +440,7 @@ def as_pattern_exhaustive(subject: int | str):
|
||||
|
||||
assert_never(subject)
|
||||
|
||||
|
||||
def as_pattern_non_exhaustive(subject: int | str):
|
||||
match subject:
|
||||
case int() as x:
|
||||
@@ -411,6 +457,7 @@ def as_pattern_non_exhaustive(subject: int | str):
|
||||
```py
|
||||
from enum import Enum
|
||||
|
||||
|
||||
class Answer(Enum):
|
||||
YES = "yes"
|
||||
NO = "no"
|
||||
@@ -435,6 +482,7 @@ python-version = "3.12"
|
||||
```py
|
||||
from typing import assert_never, Literal
|
||||
|
||||
|
||||
def f[T: bool](x: T) -> T:
|
||||
match x:
|
||||
case True:
|
||||
@@ -445,6 +493,7 @@ def f[T: bool](x: T) -> T:
|
||||
reveal_type(x) # revealed: Never
|
||||
assert_never(x)
|
||||
|
||||
|
||||
def g[T: Literal["foo", "bar"]](x: T) -> T:
|
||||
match x:
|
||||
case "foo":
|
||||
@@ -455,6 +504,7 @@ def g[T: Literal["foo", "bar"]](x: T) -> T:
|
||||
reveal_type(x) # revealed: Never
|
||||
assert_never(x)
|
||||
|
||||
|
||||
def h[T: int | str](x: T) -> T:
|
||||
if isinstance(x, int):
|
||||
return x
|
||||
@@ -464,6 +514,7 @@ def h[T: int | str](x: T) -> T:
|
||||
reveal_type(x) # revealed: Never
|
||||
assert_never(x)
|
||||
|
||||
|
||||
def i[T: (int, str)](x: T) -> T:
|
||||
match x:
|
||||
case int():
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
class NotBoolable:
|
||||
__bool__: int = 3
|
||||
|
||||
|
||||
# error: [unsupported-bool-conversion] "Boolean conversion is not supported for type `NotBoolable`"
|
||||
assert NotBoolable()
|
||||
```
|
||||
|
||||
@@ -59,6 +59,7 @@ reveal_type("x" or "y" and "") # revealed: Literal["x"]
|
||||
```py
|
||||
redefined_builtin_bool: type[bool] = bool
|
||||
|
||||
|
||||
def my_bool(x) -> bool:
|
||||
return True
|
||||
```
|
||||
@@ -86,56 +87,73 @@ reveal_type(bool((0,))) # revealed: Literal[True]
|
||||
reveal_type(bool("NON EMPTY")) # revealed: Literal[True]
|
||||
reveal_type(bool(True)) # revealed: Literal[True]
|
||||
|
||||
|
||||
def foo(): ...
|
||||
|
||||
|
||||
reveal_type(bool(foo)) # revealed: Literal[True]
|
||||
|
||||
|
||||
class SingleElementTupleSubclass(tuple[int]): ...
|
||||
|
||||
|
||||
reveal_type(bool(SingleElementTupleSubclass((0,)))) # revealed: Literal[True]
|
||||
|
||||
|
||||
# Unknown length, but we know the length is guaranteed to be >=2
|
||||
class MixedTupleSubclass(tuple[int, *tuple[str, ...], bytes]): ...
|
||||
|
||||
|
||||
reveal_type(bool(MixedTupleSubclass((1, b"foo")))) # revealed: Literal[True]
|
||||
|
||||
|
||||
# Unknown length with an overridden `__bool__`:
|
||||
class VariadicTupleSubclassWithDunderBoolOverride(tuple[int, ...]):
|
||||
def __bool__(self) -> Literal[True]:
|
||||
return True
|
||||
|
||||
|
||||
reveal_type(bool(VariadicTupleSubclassWithDunderBoolOverride((1,)))) # revealed: Literal[True]
|
||||
|
||||
|
||||
# Same again but for a subclass of a fixed-length tuple:
|
||||
class EmptyTupleSubclassWithDunderBoolOverride(tuple[()]):
|
||||
# TODO: we should reject this override as a Liskov violation:
|
||||
def __bool__(self) -> Literal[True]:
|
||||
return True
|
||||
|
||||
|
||||
reveal_type(bool(EmptyTupleSubclassWithDunderBoolOverride(()))) # revealed: Literal[True]
|
||||
reveal_type(EmptyTupleSubclassWithDunderBoolOverride.__bool__) # revealed: def __bool__(self) -> Literal[True]
|
||||
|
||||
# revealed: bound method EmptyTupleSubclassWithDunderBoolOverride.__bool__() -> Literal[True]
|
||||
reveal_type(EmptyTupleSubclassWithDunderBoolOverride().__bool__)
|
||||
|
||||
|
||||
@final
|
||||
class FinalClassOverridingLenAndNotBool:
|
||||
def __len__(self) -> Literal[42]:
|
||||
return 42
|
||||
|
||||
|
||||
reveal_type(bool(FinalClassOverridingLenAndNotBool())) # revealed: Literal[True]
|
||||
|
||||
|
||||
@final
|
||||
class FinalClassWithNoLenOrBool: ...
|
||||
|
||||
|
||||
reveal_type(bool(FinalClassWithNoLenOrBool())) # revealed: Literal[True]
|
||||
|
||||
|
||||
class EnumWithMembers(enum.Enum):
|
||||
A = 1
|
||||
B = 2
|
||||
|
||||
|
||||
reveal_type(bool(EnumWithMembers.A)) # revealed: Literal[True]
|
||||
|
||||
|
||||
def f(x: SingleElementTupleSubclass | FinalClassOverridingLenAndNotBool | FinalClassWithNoLenOrBool | Literal[EnumWithMembers.A]):
|
||||
reveal_type(bool(x)) # revealed: Literal[True]
|
||||
```
|
||||
@@ -153,17 +171,22 @@ reveal_type(bool("")) # revealed: Literal[False]
|
||||
reveal_type(bool(False)) # revealed: Literal[False]
|
||||
reveal_type(bool()) # revealed: Literal[False]
|
||||
|
||||
|
||||
class EmptyTupleSubclass(tuple[()]): ...
|
||||
|
||||
|
||||
reveal_type(bool(EmptyTupleSubclass())) # revealed: Literal[False]
|
||||
|
||||
|
||||
@final
|
||||
class FinalClassOverridingLenAndNotBool:
|
||||
def __len__(self) -> Literal[0]:
|
||||
return 0
|
||||
|
||||
|
||||
reveal_type(bool(FinalClassOverridingLenAndNotBool())) # revealed: Literal[False]
|
||||
|
||||
|
||||
class EnumWithMembersOverridingBool(enum.Enum):
|
||||
A = 1
|
||||
B = 2
|
||||
@@ -171,8 +194,10 @@ class EnumWithMembersOverridingBool(enum.Enum):
|
||||
def __bool__(self) -> Literal[False]:
|
||||
return False
|
||||
|
||||
|
||||
reveal_type(bool(EnumWithMembersOverridingBool.A)) # revealed: Literal[False]
|
||||
|
||||
|
||||
def f(x: EmptyTupleSubclass | FinalClassOverridingLenAndNotBool | Literal[EnumWithMembersOverridingBool.A]):
|
||||
reveal_type(bool(x)) # revealed: Literal[False]
|
||||
```
|
||||
@@ -187,20 +212,25 @@ reveal_type(bool([])) # revealed: bool
|
||||
reveal_type(bool({})) # revealed: bool
|
||||
reveal_type(bool(set())) # revealed: bool
|
||||
|
||||
|
||||
class VariadicTupleSubclass(tuple[int, ...]): ...
|
||||
|
||||
|
||||
def f(x: tuple[int, ...], y: VariadicTupleSubclass):
|
||||
reveal_type(bool(x)) # revealed: bool
|
||||
|
||||
|
||||
class NonFinalOverridingLenAndNotBool:
|
||||
def __len__(self) -> Literal[42]:
|
||||
return 42
|
||||
|
||||
|
||||
# We cannot consider `__len__` for a non-`@final` type,
|
||||
# because a subclass might override `__bool__`,
|
||||
# and `__bool__` takes precedence over `__len__`
|
||||
reveal_type(bool(NonFinalOverridingLenAndNotBool())) # revealed: bool
|
||||
|
||||
|
||||
class EnumWithMembersOverridingBool(enum.Enum):
|
||||
A = 1
|
||||
B = 2
|
||||
@@ -208,6 +238,7 @@ class EnumWithMembersOverridingBool(enum.Enum):
|
||||
def __bool__(self) -> bool:
|
||||
return False
|
||||
|
||||
|
||||
reveal_type(bool(EnumWithMembersOverridingBool.A)) # revealed: bool
|
||||
```
|
||||
|
||||
@@ -216,10 +247,12 @@ reveal_type(bool(EnumWithMembersOverridingBool.A)) # revealed: bool
|
||||
```py
|
||||
from typing import NoReturn
|
||||
|
||||
|
||||
class NotBoolable:
|
||||
def __bool__(self) -> NoReturn:
|
||||
raise NotImplementedError("This object can't be converted to a boolean")
|
||||
|
||||
|
||||
# TODO: This should emit an error that `NotBoolable` can't be converted to a bool but it currently doesn't
|
||||
# because `Never` is assignable to `bool`. This probably requires dead code analysis to fix.
|
||||
if NotBoolable():
|
||||
@@ -232,6 +265,7 @@ if NotBoolable():
|
||||
class NotBoolable:
|
||||
__bool__: None = None
|
||||
|
||||
|
||||
# error: [unsupported-bool-conversion] "Boolean conversion is not supported for type `NotBoolable`"
|
||||
if NotBoolable():
|
||||
...
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user