Rename Red Knot (#17820)

This commit is contained in:
Micha Reiser 2025-05-03 19:49:15 +02:00 committed by GitHub
parent e6a798b962
commit b51c4f82ea
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
1564 changed files with 1598 additions and 1578 deletions

View file

@ -0,0 +1,92 @@
# `Annotated`
`Annotated` attaches arbitrary metadata to a given type.
## Usages
`Annotated[T, ...]` is equivalent to `T`: All metadata arguments are simply ignored.
```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]
```
## Parameterization
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
else:
X = bool
# error: [invalid-type-form] "`typing.Annotated` requires at least two arguments when used in a type expression"
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]
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,
# but we treat it the same as `T` to provide better diagnostics later on.
# The subscription itself is still reported, regardless.
# Same for the `(int,)` form below.
reveal_type(x) # revealed: int
# error: [invalid-type-form]
def _(x: Annotated[(int,)]):
reveal_type(x) # revealed: int
```
## Inheritance
### Correctly parameterized
Inheriting from `Annotated[T, ...]` is equivalent to inheriting from `T` itself.
```py
from typing_extensions import Annotated
class C(Annotated[int, "foo"]): ...
# TODO: Should be `tuple[Literal[C], Literal[int], Literal[object]]`
reveal_type(C.__mro__) # revealed: tuple[Literal[C], @Todo(Inference of subscript on special form), Literal[object]]
```
### Not parameterized
```py
from typing_extensions import Annotated
# At runtime, this is an error.
# error: [invalid-base]
class C(Annotated): ...
reveal_type(C.__mro__) # revealed: tuple[Literal[C], Unknown, Literal[object]]
```

View file

@ -0,0 +1,150 @@
# Any
## Annotation
`typing.Any` is a way to name the Any type.
```py
from typing import Any
x: Any = 1
x = "foo"
def f():
reveal_type(x) # revealed: Any
```
## Aliased to a different name
If you alias `typing.Any` to another name, we still recognize that as a spelling of the Any type.
```py
from typing import Any as RenamedAny
x: RenamedAny = 1
x = "foo"
def f():
reveal_type(x) # revealed: Any
```
## Shadowed class
If you define your own class named `Any`, using that in a type expression refers to your class, and
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]
```
## Subclasses of `Any`
The spec allows you to define subclasses of `Any`.
`SubclassOfAny` has an unknown superclass, which might be `int`. The assignment to `x` should not be
allowed, even when the unknown superclass is `int`. The assignment to `y` should be allowed, since
`Subclass` might have `int` as a superclass, and is therefore assignable to `int`.
```py
from typing import Any
class SubclassOfAny(Any): ...
reveal_type(SubclassOfAny.__mro__) # revealed: tuple[Literal[SubclassOfAny], Any, Literal[object]]
x: SubclassOfAny = 1 # error: [invalid-assignment]
y: int = SubclassOfAny()
```
`SubclassOfAny` should not be assignable to a final class though, because `SubclassOfAny` could not
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]
```
A subclass of `Any` can also be assigned to arbitrary `Callable` and `Protocol` types:
```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())
```
A subclass of `Any` cannot be assigned to literal types, since those can not be subclassed:
```py
from typing import Any, Literal
class MockAny(Any):
pass
x: Literal[1] = MockAny() # error: [invalid-assignment]
```
A use case where subclasses of `Any` come up is in mocking libraries, where the mock object should
be assignable to (almost) any type:
```py
from unittest.mock import MagicMock
x: int = MagicMock()
```
## Invalid
`Any` cannot be parameterized:
```py
from typing import Any
# error: [invalid-type-form] "Type `typing.Any` expected no type parameter"
def f(x: Any[int]):
reveal_type(x) # revealed: Unknown
```

View file

@ -0,0 +1,306 @@
# Callable
References:
- <https://typing.python.org/en/latest/spec/callables.html#callable>
Note that `typing.Callable` is deprecated at runtime, in favour of `collections.abc.Callable` (see:
<https://docs.python.org/3/library/typing.html#deprecated-aliases>). However, removal of
`typing.Callable` is not currently planned, and the canonical location of the stub for the symbol in
typeshed is still `typing.pyi`.
## Invalid forms
The `Callable` special form requires _exactly_ two arguments where the first argument is either a
parameter type list, parameter specification, `typing.Concatenate`, or `...` and the second argument
is the return type. Here, we explore various invalid forms.
### Empty
A bare `Callable` without any type arguments:
```py
from typing import Callable
def _(c: Callable):
reveal_type(c) # revealed: (...) -> Unknown
```
### Invalid parameter type argument
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
```
Or, when it's a literal type:
```py
# error: [invalid-type-form] "The first argument to `Callable` must be either a list of types, ParamSpec, Concatenate, or `...`"
def _(c: Callable[42, str]):
reveal_type(c) # revealed: (...) -> Unknown
```
Or, when one of the parameter type is invalid in the list:
```py
# error: [invalid-type-form] "Int literals are not allowed in this context in a type expression"
# error: [invalid-type-form] "Boolean literals are not allowed in this context in a type expression"
def _(c: Callable[[int, 42, str, False], None]):
# revealed: (int, Unknown, str, Unknown, /) -> None
reveal_type(c)
```
### Missing return type
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
```
Or, an ellipsis:
```py
# error: [invalid-type-form] "Special form `typing.Callable` expected exactly two arguments (parameter types and return type)"
def _(c: Callable[...]):
reveal_type(c) # revealed: (...) -> Unknown
```
Or something else that's invalid in a type expression generally:
```py
# fmt: off
def _(c: Callable[ # error: [invalid-type-form] "Special form `typing.Callable` expected exactly two arguments (parameter types and return type)"
{1, 2} # error: [invalid-type-form] "The first argument to `Callable` must be either a list of types, ParamSpec, Concatenate, or `...`"
]
):
reveal_type(c) # revealed: (...) -> Unknown
```
### More than two arguments
We can't reliably infer the callable type if there are more then 2 arguments because we don't know
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
```
### List as the second argument
```py
from typing import Callable
# fmt: off
def _(c: Callable[
int, # error: [invalid-type-form] "The first argument to `Callable` must be either a list of types, ParamSpec, Concatenate, or `...`"
[str] # error: [invalid-type-form] "List literals are not allowed in this context in a type expression"
]
):
reveal_type(c) # revealed: (...) -> Unknown
```
### List as both arguments
```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
```
### Three list arguments
```py
from typing import Callable
# fmt: off
def _(c: Callable[ # error: [invalid-type-form] "Special form `typing.Callable` expected exactly two arguments (parameter types and return type)"
[int],
[str], # error: [invalid-type-form] "List literals are not allowed in this context in a type expression"
[bytes] # error: [invalid-type-form] "List literals are not allowed in this context in a type expression"
]
):
reveal_type(c) # revealed: (...) -> Unknown
```
## Simple
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
```
## Union
```py
from typing import Callable, Union
def _(
c: Callable[[Union[int, str]], int] | None,
d: None | Callable[[Union[int, str]], int],
e: None | Callable[[Union[int, str]], int] | int,
):
reveal_type(c) # revealed: ((int | str, /) -> int) | None
reveal_type(d) # revealed: None | ((int | str, /) -> int)
reveal_type(e) # revealed: None | ((int | str, /) -> int) | int
```
## Intersection
```py
from typing import Callable, Union
from ty_extensions import Intersection, Not
def _(
c: Intersection[Callable[[Union[int, str]], int], int],
d: Intersection[int, Callable[[Union[int, str]], int]],
e: Intersection[int, Callable[[Union[int, str]], int], str],
f: Intersection[Not[Callable[[int, str], Intersection[int, str]]]],
):
reveal_type(c) # revealed: ((int | str, /) -> int) & int
reveal_type(d) # revealed: int & ((int | str, /) -> int)
reveal_type(e) # revealed: int & ((int | str, /) -> int) & str
reveal_type(f) # revealed: ~((int, str, /) -> int & str)
```
## Nested
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
```
And, as the return type:
```py
def _(c: Callable[[int, str], Callable[[int], int]]):
reveal_type(c) # revealed: (int, str, /) -> (int, /) -> int
```
## Gradual form
The `Callable` special form supports the use of `...` in place of the list of parameter types. This
is a [gradual form] indicating that the type is consistent with any input signature:
```py
from typing import Callable
def gradual_form(c: Callable[..., str]):
reveal_type(c) # revealed: (...) -> str
```
## Using `typing.Concatenate`
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
```
And, as one of the parameter types:
```py
def _(c: Callable[[Concatenate[int, str, ...], int], int]):
# TODO: Should reveal the correct signature
reveal_type(c) # revealed: (...) -> int
```
## Using `typing.ParamSpec`
```toml
[environment]
python-version = "3.12"
```
Using a `ParamSpec` in a `Callable` annotation:
```py
from typing_extensions import Callable
# TODO: Not an error; remove once `ParamSpec` is supported
# error: [invalid-type-form]
def _[**P1](c: Callable[P1, int]):
reveal_type(c) # revealed: (...) -> Unknown
```
And, using the legacy syntax:
```py
from typing_extensions import ParamSpec
P2 = ParamSpec("P2")
# TODO: Not an error; remove once `ParamSpec` is supported
# error: [invalid-type-form]
def _(c: Callable[P2, int]):
reveal_type(c) # revealed: (...) -> Unknown
```
## Using `typing.Unpack`
Using the unpack operator (`*`):
```py
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
```
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
```
## Member lookup
```py
from typing import Callable
def _(c: Callable[[int], int]):
reveal_type(c.__init__) # revealed: def __init__(self) -> None
reveal_type(c.__class__) # revealed: type
reveal_type(c.__call__) # revealed: (int, /) -> int
```
[gradual form]: https://typing.python.org/en/latest/spec/glossary.html#term-gradual-form

View file

@ -0,0 +1,179 @@
# Deferred annotations
## Deferred annotations in stubs always resolve
`mod.pyi`:
```pyi
def get_foo() -> Foo: ...
class Foo: ...
```
```py
from mod import get_foo
reveal_type(get_foo()) # revealed: Foo
```
## Deferred annotations in regular code fail
In (regular) source files, annotations are *not* deferred. This also tests that imports from
`__future__` that are not `annotations` are ignored.
```py
from __future__ import with_statement as annotations
# error: [unresolved-reference]
def get_foo() -> Foo: ...
class Foo: ...
reveal_type(get_foo()) # revealed: Unknown
```
## Deferred annotations in regular code with `__future__.annotations`
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
```
## Deferred self-reference annotations in a class definition
```toml
[environment]
python-version = "3.12"
```
```py
from __future__ import annotations
class Foo:
this: Foo
# error: [unresolved-reference]
_ = Foo()
# error: [unresolved-reference]
[Foo for _ in range(1)]
a = int
def f(self, x: Foo):
reveal_type(x) # revealed: Foo
def g(self) -> Foo:
_: Foo = self
return self
class Bar:
foo: Foo
b = int
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
class Baz[T: Foo]:
pass
# error: [unresolved-reference]
type S = a
type T = b
def h[T: Bar]():
# error: [unresolved-reference]
return Bar()
type Baz = Foo
```
## Non-deferred self-reference annotations in a class definition
```toml
[environment]
python-version = "3.12"
```
```py
class Foo:
# error: [unresolved-reference]
this: Foo
ok: "Foo"
# error: [unresolved-reference]
_ = Foo()
# error: [unresolved-reference]
[Foo for _ in range(1)]
a = int
# error: [unresolved-reference]
def f(self, x: Foo):
reveal_type(x) # revealed: Unknown
# error: [unresolved-reference]
def g(self) -> Foo:
_: Foo = self
return self
class Bar:
# error: [unresolved-reference]
foo: Foo
b = int
# 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
class Baz[T: Foo]:
pass
# error: [unresolved-reference]
type S = a
type T = b
def h[T: Bar]():
# error: [unresolved-reference]
return Bar()
type Qux = Foo
def _():
class C:
# error: [unresolved-reference]
def f(self) -> C:
return self
```
## Base class references
### Not deferred by __future__.annotations
```py
from __future__ import annotations
class A(B): # error: [unresolved-reference]
pass
class B:
pass
```
### Deferred in stub files
```pyi
class A(B): ...
class B: ...
```

View file

@ -0,0 +1,90 @@
# Special cases for int/float/complex in annotations
In order to support common use cases, an annotation of `float` actually means `int | float`, and an
annotation of `complex` actually means `int | float | complex`. See
[the specification](https://typing.python.org/en/latest/spec/special-types.html#special-cases-for-float-and-complex)
## float
An annotation of `float` means `int | float`, so `int` is assignable to it:
```py
def takes_float(x: float):
pass
def passes_int_to_float(x: int):
# no error!
takes_float(x)
```
It also applies to variable annotations:
```py
def assigns_int_to_float(x: int):
# no error!
y: float = x
```
It doesn't work the other way around:
```py
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
```
Unlike other type checkers, we choose not to obfuscate this special case by displaying `int | float`
as just `float`; we display the actual type:
```py
def f(x: float):
reveal_type(x) # revealed: int | float
```
## complex
An annotation of `complex` means `int | float | complex`, so `int` and `float` are both assignable
to it (but not the other way around):
```py
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
```

View file

@ -0,0 +1,115 @@
# Tests for invalid types in type expressions
## Invalid types are rejected
Many types are illegal in the context of a type expression:
```py
import typing
from ty_extensions import AlwaysTruthy, AlwaysFalsy
from typing_extensions import Literal, Never
class A: ...
def _(
a: type[int],
b: AlwaysTruthy,
c: AlwaysFalsy,
d: Literal[True],
e: Literal["bar"],
f: Literal[b"foo"],
g: tuple[int, str],
h: Never,
i: int,
j: A,
):
def foo(): ...
def invalid(
a_: a, # error: [invalid-type-form] "Variable of type `type[int]` is not allowed in a type expression"
b_: b, # error: [invalid-type-form]
c_: c, # error: [invalid-type-form]
d_: d, # error: [invalid-type-form]
e_: e, # error: [invalid-type-form]
f_: f, # error: [invalid-type-form]
g_: g, # error: [invalid-type-form]
h_: h, # error: [invalid-type-form]
i_: typing, # error: [invalid-type-form]
j_: foo, # error: [invalid-type-form]
k_: i, # error: [invalid-type-form] "Variable of type `int` is not allowed in a type expression"
l_: j, # error: [invalid-type-form] "Variable of type `A` is not allowed in a type expression"
):
reveal_type(a_) # revealed: Unknown
reveal_type(b_) # revealed: Unknown
reveal_type(c_) # revealed: Unknown
reveal_type(d_) # revealed: Unknown
reveal_type(e_) # revealed: Unknown
reveal_type(f_) # revealed: Unknown
reveal_type(g_) # revealed: Unknown
reveal_type(h_) # revealed: Unknown
reveal_type(i_) # revealed: Unknown
reveal_type(j_) # revealed: Unknown
```
## Invalid AST nodes
```py
def bar() -> None:
return None
async def outer(): # avoid unrelated syntax errors on yield, yield from, and await
def _(
a: 1, # error: [invalid-type-form] "Int literals are not allowed in this context in a type expression"
b: 2.3, # error: [invalid-type-form] "Float literals are not allowed in type expressions"
c: 4j, # error: [invalid-type-form] "Complex literals are not allowed in type expressions"
d: True, # error: [invalid-type-form] "Boolean literals are not allowed in this context in a type expression"
e: int | b"foo", # error: [invalid-type-form] "Bytes literals are not allowed in this context in a type expression"
f: 1 and 2, # error: [invalid-type-form] "Boolean operations are not allowed in type expressions"
g: 1 or 2, # error: [invalid-type-form] "Boolean operations are not allowed in type expressions"
h: (foo := 1), # error: [invalid-type-form] "Named expressions are not allowed in type expressions"
i: not 1, # error: [invalid-type-form] "Unary operations are not allowed in type expressions"
j: lambda: 1, # error: [invalid-type-form] "`lambda` expressions are not allowed in type expressions"
k: 1 if True else 2, # error: [invalid-type-form] "`if` expressions are not allowed in type expressions"
l: await 1, # error: [invalid-type-form] "`await` expressions are not allowed in type expressions"
m: (yield 1), # error: [invalid-type-form] "`yield` expressions are not allowed in type expressions"
n: (yield from [1]), # error: [invalid-type-form] "`yield from` expressions are not allowed in type expressions"
o: 1 < 2, # error: [invalid-type-form] "Comparison expressions are not allowed in type expressions"
p: bar(), # error: [invalid-type-form] "Function calls are not allowed in type expressions"
q: int | f"foo", # error: [invalid-type-form] "F-strings are not allowed in type expressions"
r: [1, 2, 3][1:2], # error: [invalid-type-form] "Slices are not allowed in type expressions"
):
reveal_type(a) # revealed: Unknown
reveal_type(b) # revealed: Unknown
reveal_type(c) # revealed: Unknown
reveal_type(d) # revealed: Unknown
reveal_type(e) # revealed: int | Unknown
reveal_type(f) # revealed: Unknown
reveal_type(g) # revealed: Unknown
reveal_type(h) # revealed: Unknown
reveal_type(i) # revealed: Unknown
reveal_type(j) # revealed: Unknown
reveal_type(k) # revealed: Unknown
reveal_type(p) # revealed: Unknown
reveal_type(q) # revealed: int | Unknown
reveal_type(r) # revealed: @Todo(unknown type subscript)
```
## Invalid Collection based AST nodes
```py
def _(
a: {1: 2}, # error: [invalid-type-form] "Dict literals are not allowed in type expressions"
b: {1, 2}, # error: [invalid-type-form] "Set literals are not allowed in type expressions"
c: {k: v for k, v in [(1, 2)]}, # error: [invalid-type-form] "Dict comprehensions are not allowed in type expressions"
d: [k for k in [1, 2]], # error: [invalid-type-form] "List comprehensions are not allowed in type expressions"
e: {k for k in [1, 2]}, # error: [invalid-type-form] "Set comprehensions are not allowed in type expressions"
f: (k for k in [1, 2]), # error: [invalid-type-form] "Generator expressions are not allowed in type expressions"
g: [int, str], # error: [invalid-type-form] "List literals are not allowed in this context in a type expression"
):
reveal_type(a) # revealed: Unknown
reveal_type(b) # revealed: Unknown
reveal_type(c) # revealed: Unknown
reveal_type(d) # revealed: Unknown
reveal_type(e) # revealed: Unknown
reveal_type(f) # revealed: Unknown
reveal_type(g) # revealed: Unknown
```

View file

@ -0,0 +1,162 @@
# Literal
<https://typing.python.org/en/latest/spec/literal.html#literals>
## Parameterization
```py
from typing import Literal
from enum import Enum
mode: Literal["w", "r"]
a1: Literal[26]
a2: Literal[0x1A]
a3: Literal[-4]
a4: Literal["hello world"]
a5: Literal[b"hello world"]
a6: Literal[True]
a7: Literal[None]
a8: Literal[Literal[1]]
class Color(Enum):
RED = 0
GREEN = 1
BLUE = 2
b1: Literal[Color.RED]
def f():
reveal_type(mode) # revealed: Literal["w", "r"]
reveal_type(a1) # revealed: Literal[26]
reveal_type(a2) # revealed: Literal[26]
reveal_type(a3) # revealed: Literal[-4]
reveal_type(a4) # revealed: Literal["hello world"]
reveal_type(a5) # revealed: Literal[b"hello world"]
reveal_type(a6) # revealed: Literal[True]
reveal_type(a7) # revealed: None
reveal_type(a8) # revealed: Literal[1]
# TODO: This should be Color.RED
reveal_type(b1) # revealed: @Todo(Attribute access on enum classes)
# error: [invalid-type-form]
invalid1: Literal[3 + 4]
# error: [invalid-type-form]
invalid2: Literal[4 + 3j]
# error: [invalid-type-form]
invalid3: Literal[(3, 4)]
hello = "hello"
invalid4: Literal[
1 + 2, # error: [invalid-type-form]
"foo",
hello, # error: [invalid-type-form]
(1, 2, 3), # error: [invalid-type-form]
]
```
## Shortening unions of literals
When a Literal is parameterized with more than one value, its treated as exactly to equivalent to
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"],
a3: Literal[Literal["w"], Literal["r"], Literal[Literal["w+"]]],
a4: Literal[True] | Literal[1, 2] | Literal["foo"],
):
reveal_type(a1) # revealed: Literal[1, 2, 3, 5, "foo"] | None
reveal_type(a2) # revealed: Literal["w", "r"]
reveal_type(a3) # revealed: Literal["w", "r", "w+"]
reveal_type(a4) # revealed: Literal[True, 1, 2, "foo"]
```
## Display of heterogeneous unions of literals
```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
# error: [unresolved-reference]
y,
Literal[-1],
Literal["A"],
Literal[b"A"],
Literal[b"\x00"],
Literal[b"\x07"],
Literal[0],
Literal[1],
Literal["B"],
Literal["foo"],
Literal["bar"],
Literal["B"],
Literal[True],
None,
],
):
reveal_type(x) # revealed: Unknown | Literal[-1, 0, 1, "A", "B", "foo", "bar", b"A", b"\x00", b"\x07", True] | None
```
## Detecting Literal outside typing and typing_extensions
Only Literal that is defined in typing and typing_extension modules is detected as the special
Literal.
`other.pyi`:
```pyi
from typing import _SpecialForm
Literal: _SpecialForm
```
```py
from other import Literal
# TODO: can we add a subdiagnostic here saying something like:
#
# `other.Literal` and `typing.Literal` have similar names, but are different symbols and don't have the same semantics
#
# ?
#
# error: [invalid-type-form] "Int literals are not allowed in this context in a type expression"
a1: Literal[26]
def f():
reveal_type(a1) # revealed: @Todo(unknown type subscript)
```
## Detecting typing_extensions.Literal
```py
from typing_extensions import Literal
a1: Literal[26]
def f():
reveal_type(a1) # revealed: Literal[26]
```
## Invalid
```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

@ -0,0 +1,152 @@
# `LiteralString`
`LiteralString` represents a string that is either defined directly within the source code or is
made up of such components.
Parts of the testcases defined here were adapted from [the specification's examples][1].
## Usages
### Valid places
It can be used anywhere a type is accepted:
```py
from typing_extensions import LiteralString
x: LiteralString
def f():
reveal_type(x) # revealed: LiteralString
```
### Within `Literal`
`LiteralString` cannot be used within `Literal`:
```py
from typing_extensions import Literal, LiteralString
bad_union: Literal["hello", LiteralString] # error: [invalid-type-form]
bad_nesting: Literal[LiteralString] # error: [invalid-type-form]
```
### Parameterized
`LiteralString` cannot be parameterized.
```py
from typing_extensions import LiteralString
# error: [invalid-type-form]
a: LiteralString[str]
# error: [invalid-type-form]
# error: [unresolved-reference] "Name `foo` used when not defined"
b: LiteralString["foo"]
```
### As a base class
Subclassing `LiteralString` leads to a runtime error.
```py
from typing_extensions import LiteralString
class C(LiteralString): ... # error: [invalid-base]
```
## Inference
### Common operations
```py
from typing_extensions import LiteralString
foo: LiteralString = "foo"
reveal_type(foo) # revealed: Literal["foo"]
bar: LiteralString = "bar"
reveal_type(foo + bar) # revealed: Literal["foobar"]
baz: LiteralString = "baz"
baz += foo
reveal_type(baz) # revealed: Literal["bazfoo"]
qux = (foo, bar)
reveal_type(qux) # revealed: tuple[Literal["foo"], Literal["bar"]]
reveal_type(foo.join(qux)) # revealed: LiteralString
template: LiteralString = "{}, {}"
reveal_type(template) # revealed: Literal["{}, {}"]
reveal_type(template.format(foo, bar)) # revealed: LiteralString
```
### Assignability
`Literal[""]` is assignable to `LiteralString`, and `LiteralString` is assignable to `str`, but not
vice versa.
```py
from typing_extensions import Literal, LiteralString
def _(flag: bool):
foo_1: Literal["foo"] = "foo"
bar_1: LiteralString = foo_1 # fine
foo_2 = "foo" if flag else "bar"
reveal_type(foo_2) # revealed: Literal["foo", "bar"]
bar_2: LiteralString = foo_2 # fine
foo_3: LiteralString = "foo" * 1_000_000_000
bar_3: str = foo_2 # fine
baz_1: str = repr(object())
qux_1: LiteralString = baz_1 # error: [invalid-assignment]
baz_2: LiteralString = "baz" * 1_000_000_000
qux_2: Literal["qux"] = baz_2 # error: [invalid-assignment]
baz_3 = "foo" if flag else 1
reveal_type(baz_3) # revealed: Literal["foo", 1]
qux_3: LiteralString = baz_3 # error: [invalid-assignment]
```
### Narrowing
```py
from typing_extensions import LiteralString
lorem: LiteralString = "lorem" * 1_000_000_000
reveal_type(lorem) # revealed: LiteralString
if lorem == "ipsum":
reveal_type(lorem) # revealed: Literal["ipsum"]
reveal_type(lorem) # revealed: LiteralString
if "" < lorem == "ipsum":
reveal_type(lorem) # revealed: Literal["ipsum"]
```
## `typing.LiteralString`
`typing.LiteralString` is only available in Python 3.11 and later:
```toml
[environment]
python-version = "3.11"
```
```py
from typing import LiteralString
x: LiteralString = "foo"
def f():
reveal_type(x) # revealed: LiteralString
```
[1]: https://typing.python.org/en/latest/spec/literal.html#literalstring

View file

@ -0,0 +1,75 @@
# NoReturn & Never
`NoReturn` is used to annotate the return type for functions that never return. `Never` is the
bottom type, representing the empty set of Python objects. These two annotations can be used
interchangeably.
## Function Return Type Annotation
```py
from typing import NoReturn
def stop() -> NoReturn:
raise RuntimeError("no way")
# revealed: Never
reveal_type(stop())
```
## Assignment
```py
from typing_extensions import NoReturn, Never, Any
# error: [invalid-type-form] "Type `typing.Never` expected no type parameter"
x: Never[int]
a1: NoReturn
a2: Never
b1: Any
b2: int
def f():
# revealed: Never
reveal_type(a1)
# revealed: Never
reveal_type(a2)
# Never is assignable to all types.
v1: int = a1
v2: str = a1
# Other types are not assignable to Never except for Never (and Any).
v3: Never = b1
v4: Never = a2
v5: Any = b2
# error: [invalid-assignment] "Object of type `Literal[1]` is not assignable to `Never`"
v6: Never = 1
```
## `typing.Never`
`typing.Never` is only available in Python 3.11 and later.
### Python 3.11
```toml
[environment]
python-version = "3.11"
```
```py
from typing import Never
reveal_type(Never) # revealed: typing.Never
```
### Python 3.10
```toml
[environment]
python-version = "3.10"
```
```py
# error: [unresolved-import]
from typing import Never
```

View file

@ -0,0 +1,24 @@
# NewType
Currently, ty doesn't support `typing.NewType` in type annotations.
## Valid forms
```py
from typing_extensions import NewType
from types import GenericAlias
X = GenericAlias(type, ())
A = NewType("A", int)
# TODO: typeshed for `typing.GenericAlias` uses `type` for the first argument. `NewType` should be special-cased
# to be compatible with `type`
# error: [invalid-argument-type] "Argument to this function is incorrect: Expected `type`, found `NewType`"
B = GenericAlias(A, ())
def _(
a: A,
b: B,
):
reveal_type(a) # revealed: @Todo(Support for `typing.NewType` instances in type expressions)
reveal_type(b) # revealed: @Todo(Support for `typing.GenericAlias` instances in type expressions)
```

View file

@ -0,0 +1,57 @@
# Optional
## Annotation
`typing.Optional` is equivalent to using the type with a None in a Union.
```py
from typing import Optional
a: Optional[int]
a1: Optional[bool]
a2: Optional[Optional[bool]]
a3: Optional[None]
def f():
# revealed: int | None
reveal_type(a)
# revealed: bool | None
reveal_type(a1)
# revealed: bool | None
reveal_type(a2)
# revealed: None
reveal_type(a3)
```
## Assignment
```py
from typing import Optional
a: Optional[int] = 1
a = None
# error: [invalid-assignment] "Object of type `Literal[""]` is not assignable to `int | None`"
a = ""
```
## Typing Extensions
```py
from typing_extensions import Optional
a: Optional[int]
def f():
# revealed: int | None
reveal_type(a)
```
## Invalid
```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

@ -0,0 +1,23 @@
# Starred expression annotations
```toml
[environment]
python-version = "3.11"
```
Type annotations for `*args` can be starred expressions themselves:
```py
from typing_extensions import TypeVarTuple
Ts = TypeVarTuple("Ts")
def append_int(*args: *Ts) -> tuple[*Ts, int]:
# TODO: tuple[*Ts]
reveal_type(args) # revealed: tuple
return (*args, 1)
# TODO should be tuple[Literal[True], Literal["a"], int]
reveal_type(append_int(True, "a")) # revealed: @Todo(full tuple[...] support)
```

View file

@ -0,0 +1,142 @@
# Typing-module aliases to other stdlib classes
The `typing` module has various aliases to other stdlib classes. These are a legacy feature, but
still need to be supported by a type checker.
## Correspondence
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],
dict_bare: typing.Dict,
dict_parametrized: typing.Dict[int, str],
set_bare: typing.Set,
set_parametrized: typing.Set[int],
frozen_set_bare: typing.FrozenSet,
frozen_set_parametrized: typing.FrozenSet[str],
chain_map_bare: typing.ChainMap,
chain_map_parametrized: typing.ChainMap[str, int],
counter_bare: typing.Counter,
counter_parametrized: typing.Counter[int],
default_dict_bare: typing.DefaultDict,
default_dict_parametrized: typing.DefaultDict[str, int],
deque_bare: typing.Deque,
deque_parametrized: typing.Deque[str],
ordered_dict_bare: typing.OrderedDict,
ordered_dict_parametrized: typing.OrderedDict[int, str],
):
# TODO: revealed: list[Unknown]
reveal_type(list_bare) # revealed: list
# TODO: revealed: list[int]
reveal_type(list_parametrized) # revealed: list
reveal_type(dict_bare) # revealed: dict[Unknown, Unknown]
# TODO: revealed: dict[int, str]
reveal_type(dict_parametrized) # revealed: dict[Unknown, Unknown]
# TODO: revealed: set[Unknown]
reveal_type(set_bare) # revealed: set
# TODO: revealed: set[int]
reveal_type(set_parametrized) # revealed: set
# TODO: revealed: frozenset[Unknown]
reveal_type(frozen_set_bare) # revealed: frozenset
# TODO: revealed: frozenset[str]
reveal_type(frozen_set_parametrized) # revealed: frozenset
reveal_type(chain_map_bare) # revealed: ChainMap[Unknown, Unknown]
# TODO: revealed: ChainMap[str, int]
reveal_type(chain_map_parametrized) # revealed: ChainMap[Unknown, Unknown]
reveal_type(counter_bare) # revealed: Counter[Unknown]
# TODO: revealed: Counter[int]
reveal_type(counter_parametrized) # revealed: Counter[Unknown]
reveal_type(default_dict_bare) # revealed: defaultdict[Unknown, Unknown]
# TODO: revealed: defaultdict[str, int]
reveal_type(default_dict_parametrized) # revealed: defaultdict[Unknown, Unknown]
# TODO: revealed: deque[Unknown]
reveal_type(deque_bare) # revealed: deque
# TODO: revealed: deque[str]
reveal_type(deque_parametrized) # revealed: deque
reveal_type(ordered_dict_bare) # revealed: OrderedDict[Unknown, Unknown]
# TODO: revealed: OrderedDict[int, str]
reveal_type(ordered_dict_parametrized) # revealed: OrderedDict[Unknown, Unknown]
```
## Inheritance
The aliases can be inherited from. Some of these are still partially or wholly TODOs.
```py
import typing
####################
### Built-ins
####################
class ListSubclass(typing.List): ...
# TODO: generic protocols
# revealed: tuple[Literal[ListSubclass], Literal[list], Literal[MutableSequence], Literal[Sequence], Literal[Reversible], Literal[Collection], Literal[Iterable], Literal[Container], @Todo(`Protocol[]` subscript), typing.Generic, Literal[object]]
reveal_type(ListSubclass.__mro__)
class DictSubclass(typing.Dict): ...
# TODO: generic protocols
# revealed: tuple[Literal[DictSubclass], Literal[dict[Unknown, Unknown]], Literal[MutableMapping[_KT, _VT]], Literal[Mapping[_KT, _VT]], Literal[Collection], Literal[Iterable], Literal[Container], @Todo(`Protocol[]` subscript), typing.Generic, typing.Generic[_KT, _VT_co], Literal[object]]
reveal_type(DictSubclass.__mro__)
class SetSubclass(typing.Set): ...
# TODO: generic protocols
# revealed: tuple[Literal[SetSubclass], Literal[set], Literal[MutableSet], Literal[AbstractSet], Literal[Collection], Literal[Iterable], Literal[Container], @Todo(`Protocol[]` subscript), typing.Generic, Literal[object]]
reveal_type(SetSubclass.__mro__)
class FrozenSetSubclass(typing.FrozenSet): ...
# TODO: should have `Generic`, should not have `Unknown`
# revealed: tuple[Literal[FrozenSetSubclass], Literal[frozenset], Unknown, Literal[object]]
reveal_type(FrozenSetSubclass.__mro__)
####################
### `collections`
####################
class ChainMapSubclass(typing.ChainMap): ...
# TODO: generic protocols
# revealed: tuple[Literal[ChainMapSubclass], Literal[ChainMap[Unknown, Unknown]], Literal[MutableMapping[_KT, _VT]], Literal[Mapping[_KT, _VT]], Literal[Collection], Literal[Iterable], Literal[Container], @Todo(`Protocol[]` subscript), typing.Generic, typing.Generic[_KT, _VT_co], Literal[object]]
reveal_type(ChainMapSubclass.__mro__)
class CounterSubclass(typing.Counter): ...
# TODO: Should be (CounterSubclass, Counter, dict, MutableMapping, Mapping, Collection, Sized, Iterable, Container, Generic, object)
# revealed: tuple[Literal[CounterSubclass], Literal[Counter[Unknown]], Literal[dict[_T, int]], Literal[MutableMapping[_KT, _VT]], Literal[Mapping[_KT, _VT]], Literal[Collection], Literal[Iterable], Literal[Container], @Todo(`Protocol[]` subscript), typing.Generic, typing.Generic[_KT, _VT_co], typing.Generic[_T], Literal[object]]
reveal_type(CounterSubclass.__mro__)
class DefaultDictSubclass(typing.DefaultDict): ...
# TODO: Should be (DefaultDictSubclass, defaultdict, dict, MutableMapping, Mapping, Collection, Sized, Iterable, Container, Generic, object)
# revealed: tuple[Literal[DefaultDictSubclass], Literal[defaultdict[Unknown, Unknown]], Literal[dict[_KT, _VT]], Literal[MutableMapping[_KT, _VT]], Literal[Mapping[_KT, _VT]], Literal[Collection], Literal[Iterable], Literal[Container], @Todo(`Protocol[]` subscript), typing.Generic, typing.Generic[_KT, _VT_co], Literal[object]]
reveal_type(DefaultDictSubclass.__mro__)
class DequeSubclass(typing.Deque): ...
# TODO: generic protocols
# revealed: tuple[Literal[DequeSubclass], Literal[deque], Literal[MutableSequence], Literal[Sequence], Literal[Reversible], Literal[Collection], Literal[Iterable], Literal[Container], @Todo(`Protocol[]` subscript), typing.Generic, Literal[object]]
reveal_type(DequeSubclass.__mro__)
class OrderedDictSubclass(typing.OrderedDict): ...
# TODO: Should be (OrderedDictSubclass, OrderedDict, dict, MutableMapping, Mapping, Collection, Sized, Iterable, Container, Generic, object)
# revealed: tuple[Literal[OrderedDictSubclass], Literal[OrderedDict[Unknown, Unknown]], Literal[dict[_KT, _VT]], Literal[MutableMapping[_KT, _VT]], Literal[Mapping[_KT, _VT]], Literal[Collection], Literal[Iterable], Literal[Container], @Todo(`Protocol[]` subscript), typing.Generic, typing.Generic[_KT, _VT_co], Literal[object]]
reveal_type(OrderedDictSubclass.__mro__)
```

View file

@ -0,0 +1,212 @@
# String annotations
## Simple
```py
def f(v: "int"):
reveal_type(v) # revealed: int
```
## Nested
```py
def f(v: "'int'"):
reveal_type(v) # revealed: int
```
## Type expression
```py
def f1(v: "int | str", w: "tuple[int, str]"):
reveal_type(v) # revealed: int | str
reveal_type(w) # revealed: tuple[int, str]
```
## Partial
```py
def f(v: tuple[int, "str"]):
reveal_type(v) # revealed: tuple[int, str]
```
## Deferred
```py
def f(v: "Foo"):
reveal_type(v) # revealed: Foo
class Foo: ...
```
## Deferred (undefined)
```py
# error: [unresolved-reference]
def f(v: "Foo"):
reveal_type(v) # revealed: Unknown
```
## Partial deferred
```py
def f(v: int | "Foo"):
reveal_type(v) # revealed: int | Foo
class Foo: ...
```
## `typing.Literal`
```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: ...
```
## Various string kinds
```py
def f1(
# error: [raw-string-type-annotation] "Type expressions cannot use raw string literal"
a: r"int",
# error: [fstring-type-annotation] "Type expressions cannot use f-strings"
b: f"int",
# error: [byte-string-type-annotation] "Type expressions cannot use bytes literal"
c: b"int",
d: "int",
# error: [implicit-concatenated-string-type-annotation] "Type expressions cannot span multiple string literals"
e: "in" "t",
# error: [escape-character-in-forward-annotation] "Type expressions cannot contain escape characters"
f: "\N{LATIN SMALL LETTER I}nt",
# error: [escape-character-in-forward-annotation] "Type expressions cannot contain escape characters"
g: "\x69nt",
h: """int""",
# error: [byte-string-type-annotation] "Type expressions cannot use bytes literal"
i: "b'int'",
):
reveal_type(a) # revealed: Unknown
reveal_type(b) # revealed: Unknown
reveal_type(c) # revealed: Unknown
reveal_type(d) # revealed: int
reveal_type(e) # revealed: Unknown
reveal_type(f) # revealed: Unknown
reveal_type(g) # revealed: Unknown
reveal_type(h) # revealed: int
reveal_type(i) # revealed: Unknown
```
## Various string kinds in `typing.Literal`
```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"]
```
## Class variables
```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
```
## Annotated assignment
```py
a: "int" = 1
b: "'int'" = 1
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]
reveal_type(b) # revealed: Literal[1]
reveal_type(c) # revealed: Foo
reveal_type(d) # revealed: Foo
```
## Parameter
TODO: Add tests once parameter inference is supported
## Invalid expressions
The expressions in these string annotations aren't valid expressions in this context but we
shouldn't panic.
```py
a: "1 or 2"
b: "(x := 1)"
c: "1 + 2"
d: "lambda x: x"
e: "x if True else y"
f: "{'a': 1, 'b': 2}"
g: "{1, 2}"
h: "[i for i in range(5)]"
i: "{i for i in range(5)}"
j: "{i: i for i in range(5)}"
k: "(i for i in range(5))"
l: "await 1"
# error: [invalid-syntax-in-forward-annotation]
m: "yield 1"
# error: [invalid-syntax-in-forward-annotation]
n: "yield from 1"
o: "1 < 2"
p: "call()"
r: "[1, 2]"
s: "(1, 2)"
```
## Multi line annotation
Quoted type annotations should be parsed as if surrounded by parentheses.
```py
def valid(
a1: """(
int |
str
)
""",
a2: """
int |
str
""",
):
reveal_type(a1) # revealed: int | str
reveal_type(a2) # revealed: int | str
def invalid(
# error: [invalid-syntax-in-forward-annotation]
a1: """
int |
str)
""",
# error: [invalid-syntax-in-forward-annotation]
a2: """
int) |
str
""",
# error: [invalid-syntax-in-forward-annotation]
a3: """
(int)) """,
):
pass
```

View file

@ -0,0 +1,88 @@
# Union
## Annotation
`typing.Union` can be used to construct union types in the same way as the `|` operator.
```py
from typing import Union
a: Union[int, str]
a1: Union[int, bool]
a2: Union[int, Union[bytes, str]]
a3: Union[int, None]
a4: Union[Union[bytes, str]]
a5: Union[int]
a6: Union[()]
def f():
# revealed: int | str
reveal_type(a)
# Since bool is a subtype of int we simplify to int here. But we do allow assigning boolean values (see below).
# revealed: int
reveal_type(a1)
# revealed: int | bytes | str
reveal_type(a2)
# revealed: int | None
reveal_type(a3)
# revealed: bytes | str
reveal_type(a4)
# revealed: int
reveal_type(a5)
# revealed: Never
reveal_type(a6)
```
## Assignment
```py
from typing import Union
a: Union[int, str]
a = 1
a = ""
a1: Union[int, bool]
a1 = 1
a1 = True
# error: [invalid-assignment] "Object of type `Literal[b""]` is not assignable to `int | str`"
a = b""
```
## Typing Extensions
```py
from typing_extensions import Union
a: Union[int, str]
def f():
# revealed: int | str
reveal_type(a)
```
## Invalid
```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
```
## Implicit type aliases using new-style unions
We don't recognise these as type aliases yet, but we also don't emit false-positive diagnostics if
you use them in type expressions:
```toml
[environment]
python-version = "3.10"
```
```py
X = int | str
def f(y: X):
reveal_type(y) # revealed: @Todo(Support for `types.UnionType` instances in type expressions)
```

View file

@ -0,0 +1,101 @@
# Unsupported special forms
## Not yet supported
Several special forms are unsupported by ty currently. However, we also don't emit false-positive
errors if you use one in an annotation:
```py
from typing_extensions import Self, TypeVarTuple, Unpack, TypeGuard, TypeIs, Concatenate, ParamSpec, TypeAlias, Callable, TypeVar
P = ParamSpec("P")
Ts = TypeVarTuple("Ts")
R_co = TypeVar("R_co", covariant=True)
Alias: TypeAlias = int
def f(*args: Unpack[Ts]) -> tuple[Unpack[Ts]]:
# TODO: should understand the annotation
reveal_type(args) # revealed: tuple
reveal_type(Alias) # revealed: @Todo(Support for `typing.TypeAlias`)
def g() -> TypeGuard[int]: ...
def h() -> TypeIs[int]: ...
def i(callback: Callable[Concatenate[int, P], R_co], *args: P.args, **kwargs: P.kwargs) -> R_co:
# TODO: should understand the annotation
reveal_type(args) # revealed: tuple
reveal_type(kwargs) # revealed: dict[str, @Todo(Support for `typing.ParamSpec`)]
return callback(42, *args, **kwargs)
class Foo:
def method(self, x: Self):
reveal_type(x) # revealed: @Todo(Support for `typing.Self`)
```
## Type expressions
One thing that is supported is error messages for using special forms in type expressions.
```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"
c: TypeIs, # error: [invalid-type-form] "`typing.TypeIs` requires exactly one argument when used in a type expression"
d: Concatenate, # error: [invalid-type-form] "`typing.Concatenate` requires at least two arguments when used in a type expression"
e: ParamSpec,
f: Generic, # error: [invalid-type-form] "`typing.Generic` is not allowed in type expressions"
) -> None:
reveal_type(a) # revealed: Unknown
reveal_type(b) # revealed: Unknown
reveal_type(c) # revealed: Unknown
reveal_type(d) # revealed: Unknown
def foo(a_: e) -> None:
reveal_type(a_) # revealed: @Todo(Support for `typing.ParamSpec`)
```
## Inheritance
You can't inherit from most of these. `typing.Callable` is an exception.
```py
from typing import Callable
from typing_extensions import Self, Unpack, TypeGuard, TypeIs, Concatenate, Generic
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_type(F.__mro__) # revealed: tuple[Literal[F], @Todo(Support for Callable as a base class), Literal[object]]
```
## Subscriptability
```toml
[environment]
python-version = "3.12"
```
Some of these are not subscriptable:
```py
from typing_extensions import Self, TypeAlias, TypeVar
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"
def method(self: Self[int]) -> Self[int]:
reveal_type(self) # revealed: Unknown
```

View file

@ -0,0 +1,59 @@
# Unsupported type qualifiers
## Not yet fully supported
Several type qualifiers are unsupported by ty currently. However, we also don't emit false-positive
errors if you use one in an annotation:
```py
from typing_extensions import Final, Required, NotRequired, ReadOnly, TypedDict
X: Final = 42
Y: Final[int] = 42
class Bar(TypedDict):
x: Required[int]
y: NotRequired[str]
z: ReadOnly[bytes]
```
## Type expressions
One thing that is supported is error messages for using type qualifiers in type expressions.
```py
from typing_extensions import Final, ClassVar, Required, NotRequired, ReadOnly
def _(
a: (
Final # error: [invalid-type-form] "Type qualifier `typing.Final` is not allowed in type expressions (only in annotation expressions)"
| int
),
b: (
ClassVar # error: [invalid-type-form] "Type qualifier `typing.ClassVar` is not allowed in type expressions (only in annotation expressions)"
| int
),
c: Required, # error: [invalid-type-form] "Type qualifier `typing.Required` is not allowed in type expressions (only in annotation expressions, and only with exactly one argument)"
d: NotRequired, # error: [invalid-type-form] "Type qualifier `typing.NotRequired` is not allowed in type expressions (only in annotation expressions, and only with exactly one argument)"
e: ReadOnly, # error: [invalid-type-form] "Type qualifier `typing.ReadOnly` is not allowed in type expressions (only in annotation expressions, and only with exactly one argument)"
) -> None:
reveal_type(a) # revealed: Unknown | int
reveal_type(b) # revealed: Unknown | int
reveal_type(c) # revealed: Unknown
reveal_type(d) # revealed: Unknown
reveal_type(e) # revealed: Unknown
```
## Inheritance
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(Required): ... # error: [invalid-base]
class D(NotRequired): ... # error: [invalid-base]
class E(ReadOnly): ... # error: [invalid-base]
```