Compare commits

...

2 Commits

Author SHA1 Message Date
Micha Reiser
c5aa525d2a Reformat files 2026-01-14 10:48:16 +01:00
Micha Reiser
1566137d27 Disable pyi mode for mdtests 2026-01-14 10:47:57 +01:00
243 changed files with 8215 additions and 7 deletions

View File

@@ -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)^(

View File

@@ -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'>)
```

View File

@@ -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

View File

@@ -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))
```

View File

@@ -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
```

View File

@@ -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]
```

View File

@@ -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

View File

@@ -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]
```

View File

@@ -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

View File

@@ -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
```

View File

@@ -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)

View File

@@ -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]
```

View File

@@ -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

View File

@@ -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
```

View File

@@ -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), ...]
```

View File

@@ -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)
```

View File

@@ -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: """

View File

@@ -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
```

View File

@@ -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"

View File

@@ -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): ...
```

View File

@@ -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]
```

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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]'>

View File

@@ -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()
```

View File

@@ -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())

View File

@@ -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
```

View File

@@ -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, ...]

View File

@@ -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]

View File

@@ -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

View File

@@ -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

View File

@@ -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,

View File

@@ -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:

View File

@@ -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)

View File

@@ -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

View File

@@ -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)

View File

@@ -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
```

View File

@@ -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"]

View File

@@ -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],

View File

@@ -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

View File

@@ -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]
```

View File

@@ -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))
```

View File

@@ -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
```

View File

@@ -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

View File

@@ -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
```

View File

@@ -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

View File

@@ -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()

View File

@@ -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)
```

View File

@@ -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]

View File

@@ -3,6 +3,7 @@
```py
class A: ...
def _(a1: A, a2: A, o: object):
n1 = None
n2 = None

View File

@@ -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]

View File

@@ -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]

View File

@@ -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):

View File

@@ -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

View File

@@ -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
```

View File

@@ -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],

View File

@@ -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

View File

@@ -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"]]

View File

@@ -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
```

View File

@@ -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():
...

View File

@@ -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:

View File

@@ -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)
```

View File

@@ -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

View File

@@ -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
```

View File

@@ -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'>
```

View File

@@ -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)

View File

@@ -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"}

View File

@@ -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"
```

View File

@@ -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
```

View File

@@ -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 = ""

View File

@@ -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

View File

@@ -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]
```

View File

@@ -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],

View File

@@ -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())

View File

@@ -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]

View File

@@ -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]
```

View File

@@ -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

View File

@@ -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

View File

@@ -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]
```

View File

@@ -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]

View File

@@ -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]

View File

@@ -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]

View File

@@ -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]

View File

@@ -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
```

View File

@@ -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:

View File

@@ -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

View File

@@ -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]

View File

@@ -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
```

View File

@@ -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

View File

@@ -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:

View File

@@ -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:

View File

@@ -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():

View File

@@ -4,6 +4,7 @@
class NotBoolable:
__bool__: int = 3
# error: [unsupported-bool-conversion] "Boolean conversion is not supported for type `NotBoolable`"
assert NotBoolable()
```

View File

@@ -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