Compare commits

...

2 Commits

Author SHA1 Message Date
Alex Waygood
79a1305bf7 fix assertion about the size of Type 2025-10-16 13:48:52 +01:00
Carl Meyer
546c7e43c0 [ty] support PEP 613 typing.TypeAlias 2025-10-16 13:15:53 +01:00
19 changed files with 494 additions and 96 deletions

View File

@@ -16,7 +16,7 @@ Alias: TypeAlias = int
def f(*args: Unpack[Ts]) -> tuple[Unpack[Ts]]:
reveal_type(args) # revealed: tuple[@Todo(`Unpack[]` special form), ...]
reveal_type(Alias) # revealed: @Todo(Support for `typing.TypeAlias`)
reveal_type(Alias) # revealed: Alias
return args
def g() -> TypeGuard[int]: ...

View File

@@ -2187,9 +2187,9 @@ reveal_type(False.real) # revealed: Literal[0]
All attribute access on literal `bytes` types is currently delegated to `builtins.bytes`:
```py
# revealed: bound method Literal[b"foo"].join(iterable_of_bytes: Iterable[@Todo(Support for `typing.TypeAlias`)], /) -> bytes
# revealed: bound method Literal[b"foo"].join(iterable_of_bytes: Iterable[Buffer], /) -> bytes
reveal_type(b"foo".join)
# revealed: bound method Literal[b"foo"].endswith(suffix: @Todo(Support for `typing.TypeAlias`) | tuple[@Todo(Support for `typing.TypeAlias`), ...], start: SupportsIndex | None = None, end: SupportsIndex | None = None, /) -> bool
# revealed: bound method Literal[b"foo"].endswith(suffix: Buffer | tuple[Buffer, ...], start: SupportsIndex | None = None, end: SupportsIndex | None = None, /) -> bool
reveal_type(b"foo".endswith)
```

View File

@@ -313,8 +313,7 @@ reveal_type(A() + "foo") # revealed: A
reveal_type("foo" + A()) # revealed: A
reveal_type(A() + b"foo") # revealed: A
# TODO should be `A` since `bytes.__add__` doesn't support `A` instances
reveal_type(b"foo" + A()) # revealed: bytes
reveal_type(b"foo" + A()) # revealed: A
reveal_type(A() + ()) # revealed: A
reveal_type(() + A()) # revealed: A

View File

@@ -54,10 +54,8 @@ reveal_type(2**largest_u32) # revealed: int
def variable(x: int):
reveal_type(x**2) # revealed: int
# TODO: should be `Any` (overload 5 on `__pow__`), requires correct overload matching
reveal_type(2**x) # revealed: int
# TODO: should be `Any` (overload 5 on `__pow__`), requires correct overload matching
reveal_type(x**x) # revealed: int
reveal_type(2**x) # revealed: Any
reveal_type(x**x) # revealed: Any
```
If the second argument is \<0, a `float` is returned at runtime. If the first argument is \<0 but

View File

@@ -127,7 +127,7 @@ x = lambda y: y
reveal_type(x.__code__) # revealed: CodeType
reveal_type(x.__name__) # revealed: str
reveal_type(x.__defaults__) # revealed: tuple[Any, ...] | None
reveal_type(x.__annotations__) # revealed: dict[str, @Todo(Support for `typing.TypeAlias`)]
reveal_type(x.__annotations__) # revealed: dict[str, AnnotationForm]
reveal_type(x.__dict__) # revealed: dict[str, Any]
reveal_type(x.__doc__) # revealed: str | None
reveal_type(x.__kwdefaults__) # revealed: dict[str, Any] | None

View File

@@ -146,13 +146,11 @@ def _(flag: bool):
def _(flag: bool):
x = 1 if flag else "a"
# TODO: this should cause us to emit a diagnostic during
# type checking
# error: [invalid-argument-type]
if isinstance(x, "a"):
reveal_type(x) # revealed: Literal[1, "a"]
# TODO: this should cause us to emit a diagnostic during
# type checking
# error: [invalid-argument-type]
if isinstance(x, "int"):
reveal_type(x) # revealed: Literal[1, "a"]
```

View File

@@ -214,20 +214,13 @@ def flag() -> bool:
t = int if flag() else str
# TODO: this should cause us to emit a diagnostic during
# type checking
# error: [invalid-argument-type]
if issubclass(t, "str"):
reveal_type(t) # revealed: <class 'int'> | <class 'str'>
# TODO: this should cause us to emit a diagnostic during
# type checking
# TODO error: [invalid-argument-type]
if issubclass(t, (bytes, "str")):
reveal_type(t) # revealed: <class 'int'> | <class 'str'>
# TODO: this should cause us to emit a diagnostic during
# type checking
if issubclass(t, Any):
reveal_type(t) # revealed: <class 'int'> | <class 'str'>
```
### Do not narrow if there are keyword arguments

View File

@@ -0,0 +1,272 @@
# PEP 613 explicit type aliases
```toml
[environment]
python-version = "3.10"
```
Explicit type aliases were introduced in PEP 613. They are defined using an annotated-assignment
statement, annotated with `typing.TypeAlias`:
## Basic
```py
from typing import TypeAlias
MyInt: TypeAlias = int
def f(x: MyInt):
reveal_type(x) # revealed: int
f(1)
```
## Union
For more complex type aliases, such as those involving unions or generics, the inferred value type
of the right-hand side is not a valid type for use in a type expression, and we need to infer it as
a type expression.
### Old syntax
```py
from typing import TypeAlias, Union
IntOrStr: TypeAlias = Union[int, str]
def f(x: IntOrStr):
reveal_type(x) # revealed: int | str
if isinstance(x, int):
reveal_type(x) # revealed: int
else:
reveal_type(x) # revealed: str
f(1)
f("foo")
```
### New syntax
```py
from typing import TypeAlias
IntOrStr: TypeAlias = int | str
def f(x: IntOrStr):
reveal_type(x) # revealed: int | str
if isinstance(x, int):
reveal_type(x) # revealed: int
else:
reveal_type(x) # revealed: str
f(1)
f("foo")
```
### Name resolution is not deferred
Unlike with a PEP 695 type alias, the right-hand side of a PEP 613 type alias is evaluated
immediately, name resolution is not deferred.
```py
from typing import TypeAlias
A: TypeAlias = B | None # error: [unresolved-reference]
B: TypeAlias = int
def _(a: A):
reveal_type(a) # revealed: Unknown | None
```
## Multiple layers of union aliases
```py
from typing import TypeAlias
class A: ...
class B: ...
class C: ...
class D: ...
W: TypeAlias = A | B
X: TypeAlias = C | D
Y: TypeAlias = W | X
from ty_extensions import is_equivalent_to, static_assert
static_assert(is_equivalent_to(Y, A | B | C | D))
```
## Cycles
We also support cyclic type aliases:
### Old syntax
```py
from typing import Union, TypeAlias
MiniJSON: TypeAlias = Union[int, str, list["MiniJSON"]]
def f(x: MiniJSON):
reveal_type(x) # revealed: int | str | list[MiniJSON]
if isinstance(x, int):
reveal_type(x) # revealed: int
elif isinstance(x, str):
reveal_type(x) # revealed: str
else:
reveal_type(x) # revealed: list[MiniJSON]
f(1)
f("foo")
f([1, "foo"])
```
### New syntax
```py
from typing import TypeAlias
MiniJSON: TypeAlias = int | str | list["MiniJSON"]
def f(x: MiniJSON):
reveal_type(x) # revealed: int | str | list[MiniJSON]
if isinstance(x, int):
reveal_type(x) # revealed: int
elif isinstance(x, str):
reveal_type(x) # revealed: str
else:
reveal_type(x) # revealed: list[MiniJSON]
f(1)
f("foo")
f([1, "foo"])
```
### Generic
```py
from typing import TypeAlias, Generic, TypeVar, Union
T = TypeVar("T")
Alias: TypeAlias = Union[list["Alias"], int]
class A(Generic[T]):
pass
class B(A[Alias]):
pass
```
### Mutually recursive
```py
from typing import TypeAlias
A: TypeAlias = tuple["B"] | None
B: TypeAlias = tuple[A] | None
def f(x: A):
if x is not None:
reveal_type(x) # revealed: tuple[B]
y = x[0]
if y is not None:
reveal_type(y) # revealed: tuple[A]
def g(x: A | B):
reveal_type(x) # revealed: tuple[B] | None
from ty_extensions import Intersection
def h(x: Intersection[A, B]):
reveal_type(x) # revealed: tuple[B] | None
```
### Self-recursive callable type
```py
from typing import Callable, TypeAlias
C: TypeAlias = Callable[[], "C" | None]
def _(x: C):
reveal_type(x) # revealed: () -> C | None
```
### Union inside generic
#### With old-style union
```py
from typing import Union, TypeAlias
A: TypeAlias = list[Union["A", str]]
def f(x: A):
reveal_type(x) # revealed: list[A | str]
for item in x:
reveal_type(item) # revealed: list[A | str] | str
```
#### With new-style union
```py
from typing import TypeAlias
A: TypeAlias = list["A" | str]
def f(x: A):
reveal_type(x) # revealed: list[A | str]
for item in x:
reveal_type(item) # revealed: list[A | str] | str
```
#### With Optional
```py
from typing import Optional, Union, TypeAlias
A: TypeAlias = list[Optional[Union["A", str]]]
def f(x: A):
reveal_type(x) # revealed: list[A | str | None]
for item in x:
reveal_type(item) # revealed: list[A | str | None] | str | None
```
### Invalid examples
#### No value
```py
from typing import TypeAlias
# TODO: error
Bad: TypeAlias
# Nested function so we don't emit unresolved-reference for `Bad`:
def _():
def f(x: Bad):
reveal_type(x) # revealed: Unknown
```
#### No value, in stub
`stub.pyi`:
```pyi
from typing import TypeAlias
# TODO: error
Bad: TypeAlias
```
`main.py`:
```py
from stub import Bad
def f(x: Bad):
reveal_type(x) # revealed: Unknown
```

View File

@@ -28,7 +28,7 @@ def f() -> None:
```py
type IntOrStr = int | str
reveal_type(IntOrStr.__value__) # revealed: @Todo(Support for `typing.TypeAlias`)
reveal_type(IntOrStr.__value__) # revealed: Any
```
## Invalid assignment

View File

@@ -147,8 +147,8 @@ But perhaps the most commonly used tuple subclass instance is the singleton `sys
```py
import sys
# revealed: Overload[(self, index: Literal[-5, 0], /) -> Literal[3], (self, index: Literal[-4, 1], /) -> Literal[11], (self, index: Literal[-3, -1, 2, 4], /) -> int, (self, index: Literal[-2, 3], /) -> Literal["alpha", "beta", "candidate", "final"], (self, index: SupportsIndex, /) -> int | Literal["alpha", "beta", "candidate", "final"], (self, index: slice[Any, Any, Any], /) -> tuple[int | Literal["alpha", "beta", "candidate", "final"], ...]]
reveal_type(type(sys.version_info).__getitem__)
# TODO revealed: Overload[(self, index: Literal[-5, 0], /) -> Literal[3], (self, index: Literal[-4, 1], /) -> Literal[11], (self, index: Literal[-3, -1, 2, 4], /) -> int, (self, index: Literal[-2, 3], /) -> Literal["alpha", "beta", "candidate", "final"], (self, index: SupportsIndex, /) -> int | Literal["alpha", "beta", "candidate", "final"], (self, index: slice[Any, Any, Any], /) -> tuple[int | Literal["alpha", "beta", "candidate", "final"], ...]]
reveal_type(type(sys.version_info).__getitem__) # revealed: Unknown
```
Because of the synthesized `__getitem__` overloads we synthesize for tuples and tuple subclasses,

View File

@@ -122,7 +122,7 @@ properties on instance types:
```py
reveal_type(sys.version_info.micro) # revealed: int
reveal_type(sys.version_info.releaselevel) # revealed: @Todo(Support for `typing.TypeAlias`)
reveal_type(sys.version_info.releaselevel) # revealed: Literal["alpha", "beta", "candidate", "final"]
reveal_type(sys.version_info.serial) # revealed: int
```

View File

@@ -693,6 +693,15 @@ impl DefinitionKind<'_> {
}
}
pub(crate) const fn as_annotated_assignment(
&self,
) -> Option<&AnnotatedAssignmentDefinitionKind> {
match self {
DefinitionKind::AnnotatedAssignment(annotated_assignment) => Some(annotated_assignment),
_ => None,
}
}
pub(crate) fn is_import(&self) -> bool {
matches!(
self,

View File

@@ -6257,7 +6257,9 @@ impl<'db> Type<'db> {
}),
Type::KnownInstance(known_instance) => match known_instance {
KnownInstanceType::TypeAliasType(alias) => Ok(Type::TypeAlias(*alias)),
KnownInstanceType::TypeAliasType(alias) | KnownInstanceType::TypeAlias(alias) => {
Ok(Type::TypeAlias(*alias))
}
KnownInstanceType::TypeVar(typevar) => {
let index = semantic_index(db, scope_id.file(db));
Ok(bind_typevar(
@@ -6353,7 +6355,7 @@ impl<'db> Type<'db> {
)
.unwrap_or(*self))
}
SpecialFormType::TypeAlias => Ok(Type::Dynamic(DynamicType::TodoTypeAlias)),
SpecialFormType::TypeAlias => Ok(*self),
SpecialFormType::TypedDict => Err(InvalidTypeExpressionError {
invalid_expressions: smallvec::smallvec_inline![
InvalidTypeExpression::TypedDict
@@ -7490,6 +7492,10 @@ pub enum KnownInstanceType<'db> {
/// A single instance of `typing.TypeAliasType` (PEP 695 type alias)
TypeAliasType(TypeAliasType<'db>),
/// A single instance of a PEP 613 type alias (in other words, an arbitrary type form at
/// runtime.)
TypeAlias(TypeAliasType<'db>),
/// A single instance of `warnings.deprecated` or `typing_extensions.deprecated`
Deprecated(DeprecatedInstance<'db>),
@@ -7514,7 +7520,7 @@ fn walk_known_instance_type<'db, V: visitor::TypeVisitor<'db> + ?Sized>(
KnownInstanceType::TypeVar(typevar) => {
visitor.visit_type_var_type(db, typevar);
}
KnownInstanceType::TypeAliasType(type_alias) => {
KnownInstanceType::TypeAliasType(type_alias) | KnownInstanceType::TypeAlias(type_alias) => {
visitor.visit_type_alias_type(db, type_alias);
}
KnownInstanceType::Deprecated(_) | KnownInstanceType::ConstraintSet(_) => {
@@ -7538,7 +7544,7 @@ impl<'db> KnownInstanceType<'db> {
Self::SubscriptedGeneric(context.normalized_impl(db, visitor))
}
Self::TypeVar(typevar) => Self::TypeVar(typevar.normalized_impl(db, visitor)),
Self::TypeAliasType(type_alias) => {
Self::TypeAliasType(type_alias) | Self::TypeAlias(type_alias) => {
Self::TypeAliasType(type_alias.normalized_impl(db, visitor))
}
Self::Deprecated(deprecated) => {
@@ -7561,6 +7567,8 @@ impl<'db> KnownInstanceType<'db> {
KnownClass::GenericAlias
}
Self::TypeAliasType(_) => KnownClass::TypeAliasType,
// A PEP 613 type alias could be any type form object at runtime:
Self::TypeAlias(_) => KnownClass::Object,
Self::Deprecated(_) => KnownClass::Deprecated,
Self::Field(_) => KnownClass::Field,
Self::ConstraintSet(_) => KnownClass::ConstraintSet,
@@ -7617,6 +7625,7 @@ impl<'db> KnownInstanceType<'db> {
f.write_str("typing.TypeAliasType")
}
}
KnownInstanceType::TypeAlias(alias) => f.write_str(alias.name(self.db)),
// This is a legacy `TypeVar` _outside_ of any generic class or function, so we render
// it as an instance of `typing.TypeVar`. Inside of a generic class or function, we'll
// have a `Type::TypeVar(_)`, which is rendered as the typevar's name.
@@ -7684,9 +7693,6 @@ pub enum DynamicType<'db> {
/// A special Todo-variant for PEP-695 `ParamSpec` types. A temporary variant to detect and special-
/// case the handling of these types in `Callable` annotations.
TodoPEP695ParamSpec,
/// A special Todo-variant for type aliases declared using `typing.TypeAlias`.
/// A temporary variant to detect and special-case the handling of these aliases in autocomplete suggestions.
TodoTypeAlias,
/// A special Todo-variant for `Unpack[Ts]`, so that we can treat it specially in `Generic[Unpack[Ts]]`
TodoUnpack,
/// A type that is determined to be divergent during type inference for a recursive function.
@@ -7725,13 +7731,6 @@ impl std::fmt::Display for DynamicType<'_> {
f.write_str("@Todo")
}
}
DynamicType::TodoTypeAlias => {
if cfg!(debug_assertions) {
f.write_str("@Todo(Support for `typing.TypeAlias`)")
} else {
f.write_str("@Todo")
}
}
DynamicType::Divergent(_) => f.write_str("Divergent"),
}
}
@@ -10779,12 +10778,14 @@ impl<'db> ModuleLiteralType<'db> {
}
}
/// A PEP 695 type alias, created by the `type` statement.
///
/// # Ordering
/// Ordering is based on the type alias's salsa-assigned id and not on its values.
/// The id may change between runs, or when the alias was garbage collected and recreated.
#[salsa::interned(debug, heap_size=ruff_memory_usage::heap_size)]
#[derive(PartialOrd, Ord)]
pub struct PEP695TypeAliasType<'db> {
pub struct PEP695TypeAlias<'db> {
#[returns(ref)]
pub name: ast::name::Name,
@@ -10794,25 +10795,25 @@ pub struct PEP695TypeAliasType<'db> {
}
// The Salsa heap is tracked separately.
impl get_size2::GetSize for PEP695TypeAliasType<'_> {}
impl get_size2::GetSize for PEP695TypeAlias<'_> {}
fn walk_pep_695_type_alias<'db, V: visitor::TypeVisitor<'db> + ?Sized>(
db: &'db dyn Db,
type_alias: PEP695TypeAliasType<'db>,
type_alias: PEP695TypeAlias<'db>,
visitor: &V,
) {
visitor.visit_type(db, type_alias.value_type(db));
}
#[salsa::tracked]
impl<'db> PEP695TypeAliasType<'db> {
impl<'db> PEP695TypeAlias<'db> {
pub(crate) fn definition(self, db: &'db dyn Db) -> Definition<'db> {
let scope = self.rhs_scope(db);
let type_alias_stmt_node = scope.node(db).expect_type_alias();
semantic_index(db, scope.file(db)).expect_single_definition(type_alias_stmt_node)
}
#[salsa::tracked(cycle_fn=value_type_cycle_recover, cycle_initial=value_type_cycle_initial, heap_size=ruff_memory_usage::heap_size)]
#[salsa::tracked(cycle_fn=pep695_alias_value_type_cycle_recover, cycle_initial=pep695_alias_value_type_cycle_initial, heap_size=ruff_memory_usage::heap_size)]
pub(crate) fn value_type(self, db: &'db dyn Db) -> Type<'db> {
let scope = self.rhs_scope(db);
let module = parsed_module(db, scope.file(db)).load(db);
@@ -10836,7 +10837,7 @@ impl<'db> PEP695TypeAliasType<'db> {
self,
db: &'db dyn Db,
f: impl FnOnce(GenericContext<'db>) -> Specialization<'db>,
) -> PEP695TypeAliasType<'db> {
) -> PEP695TypeAlias<'db> {
match self.generic_context(db) {
None => self,
@@ -10847,12 +10848,7 @@ impl<'db> PEP695TypeAliasType<'db> {
// `typing.TypeAliasType` internally, and pass the specialization through to the value type,
// except when resolving to an instance of the type alias, or its display representation.
let specialization = f(generic_context);
PEP695TypeAliasType::new(
db,
self.name(db),
self.rhs_scope(db),
Some(specialization),
)
PEP695TypeAlias::new(db, self.name(db), self.rhs_scope(db), Some(specialization))
}
}
}
@@ -10889,28 +10885,31 @@ fn generic_context_cycle_recover<'db>(
_db: &'db dyn Db,
_value: &Option<GenericContext<'db>>,
_count: u32,
_self: PEP695TypeAliasType<'db>,
_self: PEP695TypeAlias<'db>,
) -> salsa::CycleRecoveryAction<Option<GenericContext<'db>>> {
salsa::CycleRecoveryAction::Iterate
}
fn generic_context_cycle_initial<'db>(
_db: &'db dyn Db,
_self: PEP695TypeAliasType<'db>,
_self: PEP695TypeAlias<'db>,
) -> Option<GenericContext<'db>> {
None
}
fn value_type_cycle_recover<'db>(
fn pep695_alias_value_type_cycle_recover<'db>(
_db: &'db dyn Db,
_value: &Type<'db>,
_count: u32,
_self: PEP695TypeAliasType<'db>,
_self: PEP695TypeAlias<'db>,
) -> salsa::CycleRecoveryAction<Type<'db>> {
salsa::CycleRecoveryAction::Iterate
}
fn value_type_cycle_initial<'db>(_db: &'db dyn Db, _self: PEP695TypeAliasType<'db>) -> Type<'db> {
fn pep695_alias_value_type_cycle_initial<'db>(
_db: &'db dyn Db,
_self: PEP695TypeAlias<'db>,
) -> Type<'db> {
Type::Never
}
@@ -10921,7 +10920,7 @@ fn value_type_cycle_initial<'db>(_db: &'db dyn Db, _self: PEP695TypeAliasType<'d
/// The id may change between runs, or when the alias was garbage collected and recreated.
#[salsa::interned(debug, heap_size=ruff_memory_usage::heap_size)]
#[derive(PartialOrd, Ord)]
pub struct ManualPEP695TypeAliasType<'db> {
pub struct ManualPEP695TypeAlias<'db> {
#[returns(ref)]
pub name: ast::name::Name,
pub definition: Option<Definition<'db>>,
@@ -10929,17 +10928,17 @@ pub struct ManualPEP695TypeAliasType<'db> {
}
// The Salsa heap is tracked separately.
impl get_size2::GetSize for ManualPEP695TypeAliasType<'_> {}
impl get_size2::GetSize for ManualPEP695TypeAlias<'_> {}
fn walk_manual_pep_695_type_alias<'db, V: visitor::TypeVisitor<'db> + ?Sized>(
db: &'db dyn Db,
type_alias: ManualPEP695TypeAliasType<'db>,
type_alias: ManualPEP695TypeAlias<'db>,
visitor: &V,
) {
visitor.visit_type(db, type_alias.value(db));
}
impl<'db> ManualPEP695TypeAliasType<'db> {
impl<'db> ManualPEP695TypeAlias<'db> {
fn normalized_impl(self, db: &'db dyn Db, visitor: &NormalizedVisitor<'db>) -> Self {
Self::new(
db,
@@ -10950,14 +10949,89 @@ impl<'db> ManualPEP695TypeAliasType<'db> {
}
}
/// A PEP 613 type alias, annotated with `: typing.TypeAlias`.
///
/// # Ordering
/// Ordering is based on the type alias's salsa-assigned id and not on its values.
/// The id may change between runs, or when the alias was garbage collected and recreated.
#[salsa::interned(debug, heap_size=ruff_memory_usage::heap_size)]
#[derive(PartialOrd, Ord)]
pub struct PEP613TypeAlias<'db> {
#[returns(ref)]
pub name: ast::name::Name,
/// The definition that created this type alias.
pub definition: Definition<'db>,
}
// The Salsa heap is tracked separately.
impl get_size2::GetSize for PEP613TypeAlias<'_> {}
fn walk_pep_613_type_alias<'db, V: visitor::TypeVisitor<'db> + ?Sized>(
db: &'db dyn Db,
type_alias: PEP613TypeAlias<'db>,
visitor: &V,
) {
visitor.visit_type(db, type_alias.value_type(db));
}
#[salsa::tracked]
impl<'db> PEP613TypeAlias<'db> {
/// The type that this type alias refers to.
#[salsa::tracked(cycle_fn=pep613_alias_value_type_cycle_recover, cycle_initial=pep613_alias_value_type_cycle_initial, heap_size=ruff_memory_usage::heap_size)]
pub(crate) fn value_type(self, db: &'db dyn Db) -> Type<'db> {
let definition = self.definition(db);
let module = parsed_module(db, definition.file(db)).load(db);
let value_node = self
.definition(db)
.kind(db)
.as_annotated_assignment()
// SAFETY: type inference won't create a PEP 613 type alias for any definition other
// than an annotated assignment.
.unwrap()
.value(&module)
// SAFETY: type inference won't create a PEP 613 type alias for an annotated assignment
// with no right-hand side.
.unwrap();
definition_expression_type(db, definition, value_node)
}
fn normalized_impl(self, _db: &'db dyn Db, _visitor: &NormalizedVisitor<'db>) -> Self {
self
}
}
fn pep613_alias_value_type_cycle_recover<'db>(
_db: &'db dyn Db,
_value: &Type<'db>,
_count: u32,
_self: PEP613TypeAlias<'db>,
) -> salsa::CycleRecoveryAction<Type<'db>> {
salsa::CycleRecoveryAction::Iterate
}
fn pep613_alias_value_type_cycle_initial<'db>(
_db: &'db dyn Db,
_self: PEP613TypeAlias<'db>,
) -> Type<'db> {
Type::Never
}
/// Represents a type alias, whether PEP 695 or PEP 613.
///
/// PEP 613 type aliases are annotated with `: typing.TypeAlias`. PEP 695 type aliases are created
/// via instances of `types.TypeAliasType`, whether manually instantiated or via the `type`
/// statement.
#[derive(
Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, salsa::Update, get_size2::GetSize,
)]
pub enum TypeAliasType<'db> {
/// A type alias defined using the PEP 695 `type` statement.
PEP695(PEP695TypeAliasType<'db>),
PEP695(PEP695TypeAlias<'db>),
/// A type alias defined by manually instantiating the PEP 695 `types.TypeAliasType`.
ManualPEP695(ManualPEP695TypeAliasType<'db>),
ManualPEP695(ManualPEP695TypeAlias<'db>),
/// A type alias defined with an annotation of the PEP 613 `typing.TypeAlias` type.
PEP613(PEP613TypeAlias<'db>),
}
fn walk_type_alias_type<'db, V: visitor::TypeVisitor<'db> + ?Sized>(
@@ -10975,6 +11049,9 @@ fn walk_type_alias_type<'db, V: visitor::TypeVisitor<'db> + ?Sized>(
TypeAliasType::ManualPEP695(type_alias) => {
walk_manual_pep_695_type_alias(db, type_alias, visitor);
}
TypeAliasType::PEP613(type_alias) => {
walk_pep_613_type_alias(db, type_alias, visitor);
}
}
}
@@ -10987,6 +11064,9 @@ impl<'db> TypeAliasType<'db> {
TypeAliasType::ManualPEP695(type_alias) => {
TypeAliasType::ManualPEP695(type_alias.normalized_impl(db, visitor))
}
TypeAliasType::PEP613(type_alias) => {
TypeAliasType::PEP613(type_alias.normalized_impl(db, visitor))
}
}
}
@@ -10994,6 +11074,7 @@ impl<'db> TypeAliasType<'db> {
match self {
TypeAliasType::PEP695(type_alias) => type_alias.name(db),
TypeAliasType::ManualPEP695(type_alias) => type_alias.name(db),
TypeAliasType::PEP613(type_alias) => type_alias.name(db),
}
}
@@ -11001,6 +11082,7 @@ impl<'db> TypeAliasType<'db> {
match self {
TypeAliasType::PEP695(type_alias) => Some(type_alias.definition(db)),
TypeAliasType::ManualPEP695(type_alias) => type_alias.definition(db),
TypeAliasType::PEP613(type_alias) => Some(type_alias.definition(db)),
}
}
@@ -11008,13 +11090,14 @@ impl<'db> TypeAliasType<'db> {
match self {
TypeAliasType::PEP695(type_alias) => type_alias.value_type(db),
TypeAliasType::ManualPEP695(type_alias) => type_alias.value(db),
TypeAliasType::PEP613(type_alias) => type_alias.value_type(db),
}
}
pub(crate) fn as_pep_695_type_alias(self) -> Option<PEP695TypeAliasType<'db>> {
pub(crate) fn as_pep_695_type_alias(self) -> Option<PEP695TypeAlias<'db>> {
match self {
TypeAliasType::PEP695(type_alias) => Some(type_alias),
TypeAliasType::ManualPEP695(_) => None,
_ => None,
}
}
@@ -11022,14 +11105,14 @@ impl<'db> TypeAliasType<'db> {
// TODO: Add support for generic non-PEP695 type aliases.
match self {
TypeAliasType::PEP695(type_alias) => type_alias.generic_context(db),
TypeAliasType::ManualPEP695(_) => None,
TypeAliasType::ManualPEP695(_) | TypeAliasType::PEP613(_) => None,
}
}
pub(crate) fn specialization(self, db: &'db dyn Db) -> Option<Specialization<'db>> {
match self {
TypeAliasType::PEP695(type_alias) => type_alias.specialization(db),
TypeAliasType::ManualPEP695(_) => None,
TypeAliasType::ManualPEP695(_) | TypeAliasType::PEP613(_) => None,
}
}
@@ -11042,7 +11125,7 @@ impl<'db> TypeAliasType<'db> {
TypeAliasType::PEP695(type_alias) => {
TypeAliasType::PEP695(type_alias.apply_specialization(db, f))
}
TypeAliasType::ManualPEP695(_) => self,
TypeAliasType::ManualPEP695(_) | TypeAliasType::PEP613(_) => self,
}
}
}
@@ -11702,7 +11785,7 @@ pub(super) fn determine_upper_bound<'db>(
// Make sure that the `Type` enum does not grow unexpectedly.
#[cfg(not(debug_assertions))]
#[cfg(target_pointer_width = "64")]
static_assertions::assert_eq_size!(Type, [u8; 16]);
static_assertions::assert_eq_size!(Type, [u8; 24]);
#[cfg(test)]
pub(crate) mod tests {

View File

@@ -34,7 +34,7 @@ use crate::types::visitor::{NonAtomicType, TypeKind, TypeVisitor, walk_non_atomi
use crate::types::{
ApplyTypeMappingVisitor, Binding, BoundSuperType, CallableType, DataclassParams,
DeprecatedInstance, FindLegacyTypeVarsVisitor, HasRelationToVisitor, IsDisjointVisitor,
IsEquivalentVisitor, KnownInstanceType, ManualPEP695TypeAliasType, MaterializationKind,
IsEquivalentVisitor, KnownInstanceType, ManualPEP695TypeAlias, MaterializationKind,
NormalizedVisitor, PropertyInstanceType, StringLiteralType, TypeAliasType, TypeContext,
TypeMapping, TypeRelation, TypedDictParams, UnionBuilder, VarianceInferable, declaration_type,
determine_upper_bound, infer_definition_types,
@@ -5328,7 +5328,7 @@ impl KnownClass {
return;
};
overload.set_return_type(Type::KnownInstance(KnownInstanceType::TypeAliasType(
TypeAliasType::ManualPEP695(ManualPEP695TypeAliasType::new(
TypeAliasType::ManualPEP695(ManualPEP695TypeAlias::new(
db,
ast::name::Name::new(name.value(db)),
containing_assignment,

View File

@@ -49,10 +49,7 @@ impl<'db> ClassBase<'db> {
ClassBase::Dynamic(DynamicType::Any) => "Any",
ClassBase::Dynamic(DynamicType::Unknown) => "Unknown",
ClassBase::Dynamic(
DynamicType::Todo(_)
| DynamicType::TodoPEP695ParamSpec
| DynamicType::TodoTypeAlias
| DynamicType::TodoUnpack,
DynamicType::Todo(_) | DynamicType::TodoPEP695ParamSpec | DynamicType::TodoUnpack,
) => "@Todo",
ClassBase::Dynamic(DynamicType::Divergent(_)) => "Divergent",
ClassBase::Protocol => "Protocol",
@@ -168,6 +165,7 @@ impl<'db> ClassBase<'db> {
KnownInstanceType::SubscriptedGeneric(_) => Some(Self::Generic),
KnownInstanceType::SubscriptedProtocol(_) => Some(Self::Protocol),
KnownInstanceType::TypeAliasType(_)
| KnownInstanceType::TypeAlias(_)
| KnownInstanceType::TypeVar(_)
| KnownInstanceType::Deprecated(_)
| KnownInstanceType::Field(_)

View File

@@ -13,8 +13,7 @@ use crate::semantic_index::{
use crate::types::call::{CallArguments, MatchedArgument};
use crate::types::signatures::Signature;
use crate::types::{
ClassBase, ClassLiteral, DynamicType, KnownClass, KnownInstanceType, Type,
class::CodeGeneratorKind,
ClassBase, ClassLiteral, KnownClass, KnownInstanceType, Type, class::CodeGeneratorKind,
};
use crate::{Db, HasType, NameKind, SemanticModel};
use ruff_db::files::{File, FileRange};
@@ -268,9 +267,10 @@ impl<'db> AllMembers<'db> {
}
Type::ClassLiteral(class) if class.is_protocol(db) => continue,
Type::KnownInstance(
KnownInstanceType::TypeVar(_) | KnownInstanceType::TypeAliasType(_),
KnownInstanceType::TypeVar(_)
| KnownInstanceType::TypeAliasType(_)
| KnownInstanceType::TypeAlias(_),
) => continue,
Type::Dynamic(DynamicType::TodoTypeAlias) => continue,
_ => {}
}
}

View File

@@ -93,9 +93,9 @@ use crate::types::visitor::any_over_type;
use crate::types::{
CallDunderError, CallableBinding, CallableType, ClassLiteral, ClassType, DataclassParams,
DynamicType, IntersectionBuilder, IntersectionType, KnownClass, KnownInstanceType,
MemberLookupPolicy, MetaclassCandidate, PEP695TypeAliasType, Parameter, ParameterForm,
Parameters, SpecialFormType, SubclassOfType, TrackedConstraintSet, Truthiness, Type,
TypeAliasType, TypeAndQualifiers, TypeContext, TypeQualifiers,
MemberLookupPolicy, MetaclassCandidate, PEP613TypeAlias, PEP695TypeAlias, Parameter,
ParameterForm, Parameters, SpecialFormType, SubclassOfType, TrackedConstraintSet, Truthiness,
Type, TypeAliasType, TypeAndQualifiers, TypeContext, TypeQualifiers,
TypeVarBoundOrConstraintsEvaluation, TypeVarDefaultEvaluation, TypeVarIdentity,
TypeVarInstance, TypeVarKind, TypeVarVariance, TypedDictType, UnionBuilder, UnionType,
binding_type, todo_type,
@@ -1281,6 +1281,9 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
DefinitionKind::Assignment(assignment) => {
self.infer_assignment_deferred(assignment.value(self.module()));
}
DefinitionKind::AnnotatedAssignment(annotated_assignment) => {
self.infer_annotated_assignment_deferred(annotated_assignment);
}
_ => {}
}
}
@@ -2709,7 +2712,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
.to_scope_id(self.db(), self.file());
let type_alias_ty = Type::KnownInstance(KnownInstanceType::TypeAliasType(
TypeAliasType::PEP695(PEP695TypeAliasType::new(
TypeAliasType::PEP695(PEP695TypeAlias::new(
self.db(),
&type_alias.name.as_name_expr().unwrap().id,
rhs_scope,
@@ -4474,6 +4477,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
}
}
// Handle assignments to `TYPE_CHECKING`.
if target
.as_name_expr()
.is_some_and(|name| &name.id == "TYPE_CHECKING")
@@ -4502,6 +4506,43 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
declared.inner = Type::BooleanLiteral(true);
}
// If the target of an assignment is not one of the place expressions we support,
// then they are not definitions, so we can only be here if the target is in a form supported as a place expression.
// In this case, we can simply store types in `target` below, instead of calling `infer_expression` (which would return `Never`).
debug_assert!(PlaceExpr::try_from_expr(target).is_some());
// Handle PEP 613 type aliases.
if matches!(
declared.inner,
Type::SpecialForm(SpecialFormType::TypeAlias)
) {
let ty = if let Some(name_expr) = target.as_name_expr() {
if value.is_some() {
self.deferred.insert(definition);
Type::KnownInstance(KnownInstanceType::TypeAlias(TypeAliasType::PEP613(
PEP613TypeAlias::new(self.db(), name_expr.id.clone(), definition),
)))
} else {
// TODO diagnostic: no RHS
Type::unknown()
}
} else {
// TODO diagnostic: target is not a name
Type::unknown()
};
// TODO diagnostic if qualifiers are not empty
if value.is_some() || self.in_stub() {
self.add_declaration_with_binding(
target.into(),
definition,
&DeclaredAndInferredType::AreTheSame(TypeAndQualifiers::declared(ty)),
);
} else {
self.add_declaration(target.into(), definition, TypeAndQualifiers::declared(ty));
}
return;
}
// Handle various singletons.
if let Some(name_expr) = target.as_name_expr() {
if let Some(special_form) =
@@ -4511,11 +4552,6 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
}
}
// If the target of an assignment is not one of the place expressions we support,
// then they are not definitions, so we can only be here if the target is in a form supported as a place expression.
// In this case, we can simply store types in `target` below, instead of calling `infer_expression` (which would return `Never`).
debug_assert!(PlaceExpr::try_from_expr(target).is_some());
if let Some(value) = value {
let inferred_ty = self.infer_maybe_standalone_expression(
value,
@@ -4557,6 +4593,16 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
}
}
fn infer_annotated_assignment_deferred(
&mut self,
assignment: &'db AnnotatedAssignmentDefinitionKind,
) {
// Annotated assignments defer the entire value expression in case of a PEP 613 type alias.
// SAFETY `infer_annotated_assignment_definition` does not defer it there is no RHS
let value_node = assignment.value(self.module()).unwrap();
self.infer_type_expression(value_node);
}
fn infer_augmented_assignment_statement(&mut self, assignment: &ast::StmtAugAssign) {
if assignment.target.is_name_expr() {
self.infer_definition(assignment);
@@ -7783,8 +7829,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
todo @ Type::Dynamic(
DynamicType::Todo(_)
| DynamicType::TodoPEP695ParamSpec
| DynamicType::TodoUnpack
| DynamicType::TodoTypeAlias,
| DynamicType::TodoUnpack,
),
_,
_,
@@ -7794,8 +7839,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
todo @ Type::Dynamic(
DynamicType::Todo(_)
| DynamicType::TodoPEP695ParamSpec
| DynamicType::TodoUnpack
| DynamicType::TodoTypeAlias,
| DynamicType::TodoUnpack,
),
_,
) => Some(todo),

View File

@@ -811,6 +811,13 @@ impl<'db> TypeInferenceBuilder<'db, '_> {
self.infer_type_expression(slice);
todo_type!("Generic manual PEP-695 type alias")
}
KnownInstanceType::TypeAliasType(TypeAliasType::PEP613(_)) => {
unreachable!("PEP 613 type aliases are not KnownInstance::TypeAliasType");
}
KnownInstanceType::TypeAlias(_) => {
self.infer_type_expression(&subscript.slice);
todo_type!("Generic PEP-613 type alias")
}
},
Type::Dynamic(DynamicType::Todo(_)) => {
self.infer_type_expression(slice);

View File

@@ -272,9 +272,6 @@ fn dynamic_elements_ordering(left: DynamicType, right: DynamicType) -> Ordering
(DynamicType::TodoUnpack, _) => Ordering::Less,
(_, DynamicType::TodoUnpack) => Ordering::Greater,
(DynamicType::TodoTypeAlias, _) => Ordering::Less,
(_, DynamicType::TodoTypeAlias) => Ordering::Greater,
(DynamicType::Divergent(left), DynamicType::Divergent(right)) => {
left.scope.cmp(&right.scope)
}