Skip to content

Commit 2c0a65d

Browse files
committed
[ty] Support typing.TypeAliasType
1 parent b86960f commit 2c0a65d

File tree

5 files changed

+151
-15
lines changed

5 files changed

+151
-15
lines changed

crates/ty_project/tests/check.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -294,4 +294,8 @@ impl SourceOrderVisitor<'_> for PullTypesVisitor<'_> {
294294

295295
/// Whether or not the .py/.pyi version of this file is expected to fail
296296
#[rustfmt::skip]
297-
const KNOWN_FAILURES: &[(&str, bool, bool)] = &[];
297+
const KNOWN_FAILURES: &[(&str, bool, bool)] = &[
298+
// Fails with too-many-cycle-iterations due to a self-referential
299+
// type alias, see https://github.com/astral-sh/ty/issues/256
300+
("crates/ruff_linter/resources/test/fixtures/pyflakes/F401_34.py", true, true),
301+
];

crates/ty_python_semantic/resources/mdtest/pep695_type_aliases.md

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,3 +87,40 @@ type TypeAliasType2 = TypeOf[Alias2]
8787
static_assert(not is_equivalent_to(TypeAliasType1, TypeAliasType2))
8888
static_assert(is_disjoint_from(TypeAliasType1, TypeAliasType2))
8989
```
90+
91+
## Direct use of `TypeAliasType`
92+
93+
`TypeAliasType` can also be used directly. This is useful for versions of Python prior to 3.12.
94+
95+
```toml
96+
[environment]
97+
python-version = "3.9"
98+
```
99+
100+
### Basic example
101+
102+
```py
103+
from typing_extensions import TypeAliasType, Union
104+
105+
IntOrStr = TypeAliasType("IntOrStr", Union[int, str])
106+
107+
reveal_type(IntOrStr) # revealed: typing.TypeAliasType
108+
109+
reveal_type(IntOrStr.__name__) # revealed: Literal["IntOrStr"]
110+
111+
def f(x: IntOrStr) -> None:
112+
reveal_type(x) # revealed: int | str
113+
```
114+
115+
### Generic example
116+
117+
```py
118+
from typing_extensions import TypeAliasType, TypeVar
119+
120+
T = TypeVar("T")
121+
122+
IntAnd = TypeAliasType("IntAndT", tuple[int, T], type_params=(T,))
123+
124+
def f(x: IntAnd[str]) -> None:
125+
reveal_type(x) # revealed: @Todo(Generic PEP-695 type alias)
126+
```

crates/ty_python_semantic/src/types.rs

Lines changed: 82 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4012,6 +4012,45 @@ impl<'db> Type<'db> {
40124012
Signatures::single(signature)
40134013
}
40144014

4015+
Some(KnownClass::TypeAliasType) => {
4016+
// ```py
4017+
// def __new__(
4018+
// cls,
4019+
// name: str,
4020+
// value: Any,
4021+
// *,
4022+
// type_params: tuple[TypeVar | ParamSpec | TypeVarTuple, ...] = ()
4023+
// ) -> Self: ...
4024+
// ```
4025+
let signature = CallableSignature::single(
4026+
self,
4027+
Signature::new(
4028+
Parameters::new([
4029+
Parameter::positional_or_keyword(Name::new_static("name"))
4030+
.with_annotated_type(KnownClass::Str.to_instance(db)),
4031+
Parameter::positional_or_keyword(Name::new_static("value"))
4032+
.with_annotated_type(Type::any())
4033+
.type_form(),
4034+
Parameter::keyword_only(Name::new_static("type_params"))
4035+
.with_annotated_type(KnownClass::Tuple.to_specialized_instance(
4036+
db,
4037+
[UnionType::from_elements(
4038+
db,
4039+
[
4040+
KnownClass::TypeVar.to_instance(db),
4041+
KnownClass::ParamSpec.to_instance(db),
4042+
KnownClass::TypeVarTuple.to_instance(db),
4043+
],
4044+
)],
4045+
))
4046+
.with_default_type(TupleType::empty(db)),
4047+
]),
4048+
None,
4049+
),
4050+
);
4051+
Signatures::single(signature)
4052+
}
4053+
40154054
Some(KnownClass::Property) => {
40164055
let getter_signature = Signature::new(
40174056
Parameters::new([
@@ -5318,7 +5357,7 @@ impl<'db> Type<'db> {
53185357
Some(TypeDefinition::TypeVar(var.definition(db)))
53195358
}
53205359
KnownInstanceType::TypeAliasType(type_alias) => {
5321-
Some(TypeDefinition::TypeAlias(type_alias.definition(db)))
5360+
type_alias.definition(db).map(TypeDefinition::TypeAlias)
53225361
}
53235362
_ => None,
53245363
},
@@ -7467,15 +7506,15 @@ impl<'db> ModuleLiteralType<'db> {
74677506
}
74687507

74697508
#[salsa::interned(debug)]
7470-
pub struct TypeAliasType<'db> {
7509+
pub struct PEP695TypeAliasType<'db> {
74717510
#[returns(ref)]
74727511
pub name: ast::name::Name,
74737512

74747513
rhs_scope: ScopeId<'db>,
74757514
}
74767515

74777516
#[salsa::tracked]
7478-
impl<'db> TypeAliasType<'db> {
7517+
impl<'db> PEP695TypeAliasType<'db> {
74797518
pub(crate) fn definition(self, db: &'db dyn Db) -> Definition<'db> {
74807519
let scope = self.rhs_scope(db);
74817520
let type_alias_stmt_node = scope.node(db).expect_type_alias();
@@ -7492,6 +7531,42 @@ impl<'db> TypeAliasType<'db> {
74927531
}
74937532
}
74947533

7534+
#[salsa::interned(debug)]
7535+
pub struct BareTypeAliasType<'db> {
7536+
#[returns(ref)]
7537+
pub name: String,
7538+
pub value: Type<'db>,
7539+
}
7540+
7541+
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, salsa::Update)]
7542+
pub enum TypeAliasType<'db> {
7543+
PEP695(PEP695TypeAliasType<'db>),
7544+
Bare(BareTypeAliasType<'db>),
7545+
}
7546+
7547+
impl<'db> TypeAliasType<'db> {
7548+
pub(crate) fn name(self, db: &'db dyn Db) -> &'db str {
7549+
match self {
7550+
TypeAliasType::PEP695(type_alias) => type_alias.name(db),
7551+
TypeAliasType::Bare(type_alias) => type_alias.name(db).as_str(),
7552+
}
7553+
}
7554+
7555+
pub(crate) fn definition(self, db: &'db dyn Db) -> Option<Definition<'db>> {
7556+
match self {
7557+
TypeAliasType::PEP695(type_alias) => Some(type_alias.definition(db)),
7558+
TypeAliasType::Bare(_) => None,
7559+
}
7560+
}
7561+
7562+
pub(crate) fn value_type(self, db: &'db dyn Db) -> Type<'db> {
7563+
match self {
7564+
TypeAliasType::PEP695(type_alias) => type_alias.value_type(db),
7565+
TypeAliasType::Bare(type_alias) => type_alias.value(db),
7566+
}
7567+
}
7568+
}
7569+
74957570
/// Either the explicit `metaclass=` keyword of the class, or the inferred metaclass of one of its base classes.
74967571
#[derive(Debug, Clone, PartialEq, Eq, salsa::Update)]
74977572
pub(super) struct MetaclassCandidate<'db> {
@@ -8004,6 +8079,10 @@ impl<'db> TupleType<'db> {
80048079
.to_specialized_instance(db, [UnionType::from_elements(db, self.elements(db))])
80058080
}
80068081

8082+
pub(crate) fn empty(db: &'db dyn Db) -> Type<'db> {
8083+
Type::Tuple(TupleType::new(db, Box::<[Type<'db>]>::from([])))
8084+
}
8085+
80078086
pub(crate) fn from_elements<T: Into<Type<'db>>>(
80088087
db: &'db dyn Db,
80098088
types: impl IntoIterator<Item = T>,

crates/ty_python_semantic/src/types/call/bind.rs

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,10 @@ use crate::types::diagnostic::{
2020
use crate::types::generics::{Specialization, SpecializationBuilder, SpecializationError};
2121
use crate::types::signatures::{Parameter, ParameterForm};
2222
use crate::types::{
23-
BoundMethodType, DataclassParams, DataclassTransformerParams, FunctionDecorators, FunctionType,
24-
KnownClass, KnownFunction, KnownInstanceType, MethodWrapperKind, PropertyInstanceType,
25-
TupleType, UnionType, WrapperDescriptorKind, todo_type,
23+
BareTypeAliasType, BoundMethodType, DataclassParams, DataclassTransformerParams,
24+
FunctionDecorators, FunctionType, KnownClass, KnownFunction, KnownInstanceType,
25+
MethodWrapperKind, PropertyInstanceType, TupleType, TypeAliasType, UnionType,
26+
WrapperDescriptorKind, todo_type,
2627
};
2728
use ruff_db::diagnostic::{Annotation, Diagnostic, Severity, SubDiagnostic};
2829
use ruff_python_ast as ast;
@@ -908,6 +909,19 @@ impl<'db> Bindings<'db> {
908909
}
909910
}
910911

912+
Some(KnownClass::TypeAliasType) => {
913+
if let [Some(name), Some(value), ..] = overload.parameter_types() {
914+
// TODO: emit a diagnostic if the name is not a string literal
915+
if let Some(name) = name.into_string_literal() {
916+
overload.set_return_type(Type::KnownInstance(
917+
KnownInstanceType::TypeAliasType(TypeAliasType::Bare(
918+
BareTypeAliasType::new(db, name.value(db), value),
919+
)),
920+
));
921+
}
922+
}
923+
}
924+
911925
_ => {}
912926
},
913927

crates/ty_python_semantic/src/types/infer.rs

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -86,11 +86,11 @@ use crate::types::{
8686
CallDunderError, CallableSignature, CallableType, ClassLiteral, ClassType, DataclassParams,
8787
DynamicType, FunctionDecorators, FunctionType, GenericAlias, IntersectionBuilder,
8888
IntersectionType, KnownClass, KnownFunction, KnownInstanceType, MemberLookupPolicy,
89-
MetaclassCandidate, Parameter, ParameterForm, Parameters, Signature, Signatures,
90-
StringLiteralType, SubclassOfType, Symbol, SymbolAndQualifiers, Truthiness, TupleType, Type,
91-
TypeAliasType, TypeAndQualifiers, TypeArrayDisplay, TypeQualifiers, TypeVarBoundOrConstraints,
92-
TypeVarInstance, TypeVarKind, TypeVarVariance, UnionBuilder, UnionType, binding_type,
93-
todo_type,
89+
MetaclassCandidate, PEP695TypeAliasType, Parameter, ParameterForm, Parameters, Signature,
90+
Signatures, StringLiteralType, SubclassOfType, Symbol, SymbolAndQualifiers, Truthiness,
91+
TupleType, Type, TypeAliasType, TypeAndQualifiers, TypeArrayDisplay, TypeQualifiers,
92+
TypeVarBoundOrConstraints, TypeVarInstance, TypeVarKind, TypeVarVariance, UnionBuilder,
93+
UnionType, binding_type, todo_type,
9494
};
9595
use crate::unpack::{Unpack, UnpackPosition};
9696
use crate::util::subscript::{PyIndex, PySlice};
@@ -2374,12 +2374,13 @@ impl<'db> TypeInferenceBuilder<'db> {
23742374
.node_scope(NodeWithScopeRef::TypeAlias(type_alias))
23752375
.to_scope_id(self.db(), self.file());
23762376

2377-
let type_alias_ty =
2378-
Type::KnownInstance(KnownInstanceType::TypeAliasType(TypeAliasType::new(
2377+
let type_alias_ty = Type::KnownInstance(KnownInstanceType::TypeAliasType(
2378+
TypeAliasType::PEP695(PEP695TypeAliasType::new(
23792379
self.db(),
23802380
&type_alias.name.as_name_expr().unwrap().id,
23812381
rhs_scope,
2382-
)));
2382+
)),
2383+
));
23832384

23842385
self.add_declaration_with_binding(
23852386
type_alias.into(),
@@ -4860,6 +4861,7 @@ impl<'db> TypeInferenceBuilder<'db> {
48604861
| KnownClass::Super
48614862
| KnownClass::TypeVar
48624863
| KnownClass::NamedTuple
4864+
| KnownClass::TypeAliasType
48634865
)
48644866
)
48654867
// temporary special-casing for all subclasses of `enum.Enum`

0 commit comments

Comments
 (0)