diff --git a/crates/red_knot_python_semantic/resources/mdtest/type_alias.md b/crates/red_knot_python_semantic/resources/mdtest/type_alias.md new file mode 100644 index 0000000000..77a9441136 --- /dev/null +++ b/crates/red_knot_python_semantic/resources/mdtest/type_alias.md @@ -0,0 +1,71 @@ +# Type aliases + +## Basic + +```py +type IntOrStr = int | str + +reveal_type(IntOrStr) # revealed: typing.TypeAliasType +reveal_type(IntOrStr.__name__) # revealed: Literal["IntOrStr"] + +x: IntOrStr = 1 + +reveal_type(x) # revealed: Literal[1] + +def f() -> None: + reveal_type(x) # revealed: int | str +``` + +## `__value__` attribute + +```py +type IntOrStr = int | str + +# TODO: This should either fall back to the specified type from typeshed, +# which is `Any`, or be the actual type of the runtime value expression +# `int | str`, i.e. `types.UnionType`. +reveal_type(IntOrStr.__value__) # revealed: @Todo(instance attributes) +``` + +## Invalid assignment + +```py +type OptionalInt = int | None + +# error: [invalid-assignment] +x: OptionalInt = "1" +``` + +## Type aliases in type aliases + +```py +type IntOrStr = int | str +type IntOrStrOrBytes = IntOrStr | bytes + +x: IntOrStrOrBytes = 1 + +def f() -> None: + reveal_type(x) # revealed: int | str | bytes +``` + +## Aliased type aliases + +```py +type IntOrStr = int | str +MyIntOrStr = IntOrStr + +x: MyIntOrStr = 1 + +# error: [invalid-assignment] +y: MyIntOrStr = None +``` + +## Generic type aliases + +```py +type ListOrSet[T] = list[T] | set[T] + +# TODO: Should be `tuple[typing.TypeVar | typing.ParamSpec | typing.TypeVarTuple, ...]`, +# as specified in the `typeshed` stubs. +reveal_type(ListOrSet.__type_params__) # revealed: @Todo(instance attributes) +``` diff --git a/crates/red_knot_python_semantic/src/semantic_index/builder.rs b/crates/red_knot_python_semantic/src/semantic_index/builder.rs index 88f3d99a56..a1c7a5e0ed 100644 --- a/crates/red_knot_python_semantic/src/semantic_index/builder.rs +++ b/crates/red_knot_python_semantic/src/semantic_index/builder.rs @@ -131,7 +131,8 @@ impl<'db> SemanticIndexBuilder<'db> { let scope_id = ScopeId::new(self.db, self.file, file_scope_id, countme::Count::default()); self.scope_ids_by_scope.push(scope_id); - self.scopes_by_node.insert(node.node_key(), file_scope_id); + let previous = self.scopes_by_node.insert(node.node_key(), file_scope_id); + debug_assert_eq!(previous, None); debug_assert_eq!(ast_id_scope, file_scope_id); @@ -589,6 +590,27 @@ where }, ); } + ast::Stmt::TypeAlias(type_alias) => { + let symbol = self.add_symbol( + type_alias + .name + .as_name_expr() + .map(|name| name.id.clone()) + .unwrap_or("".into()), + ); + self.add_definition(symbol, type_alias); + self.visit_expr(&type_alias.name); + + self.with_type_params( + NodeWithScopeRef::TypeAliasTypeParameters(type_alias), + type_alias.type_params.as_ref(), + |builder| { + builder.push_scope(NodeWithScopeRef::TypeAlias(type_alias)); + builder.visit_expr(&type_alias.value); + builder.pop_scope() + }, + ); + } ast::Stmt::Import(node) => { for alias in &node.names { let symbol_name = if let Some(asname) = &alias.asname { diff --git a/crates/red_knot_python_semantic/src/semantic_index/definition.rs b/crates/red_knot_python_semantic/src/semantic_index/definition.rs index 11822e5d75..82f8cf7557 100644 --- a/crates/red_knot_python_semantic/src/semantic_index/definition.rs +++ b/crates/red_knot_python_semantic/src/semantic_index/definition.rs @@ -83,6 +83,7 @@ pub(crate) enum DefinitionNodeRef<'a> { For(ForStmtDefinitionNodeRef<'a>), Function(&'a ast::StmtFunctionDef), Class(&'a ast::StmtClassDef), + TypeAlias(&'a ast::StmtTypeAlias), NamedExpression(&'a ast::ExprNamed), Assignment(AssignmentDefinitionNodeRef<'a>), AnnotatedAssignment(&'a ast::StmtAnnAssign), @@ -109,6 +110,12 @@ impl<'a> From<&'a ast::StmtClassDef> for DefinitionNodeRef<'a> { } } +impl<'a> From<&'a ast::StmtTypeAlias> for DefinitionNodeRef<'a> { + fn from(node: &'a ast::StmtTypeAlias) -> Self { + Self::TypeAlias(node) + } +} + impl<'a> From<&'a ast::ExprNamed> for DefinitionNodeRef<'a> { fn from(node: &'a ast::ExprNamed) -> Self { Self::NamedExpression(node) @@ -265,6 +272,9 @@ impl<'db> DefinitionNodeRef<'db> { DefinitionNodeRef::Class(class) => { DefinitionKind::Class(AstNodeRef::new(parsed, class)) } + DefinitionNodeRef::TypeAlias(type_alias) => { + DefinitionKind::TypeAlias(AstNodeRef::new(parsed, type_alias)) + } DefinitionNodeRef::NamedExpression(named) => { DefinitionKind::NamedExpression(AstNodeRef::new(parsed, named)) } @@ -358,6 +368,7 @@ impl<'db> DefinitionNodeRef<'db> { } Self::Function(node) => node.into(), Self::Class(node) => node.into(), + Self::TypeAlias(node) => node.into(), Self::NamedExpression(node) => node.into(), Self::Assignment(AssignmentDefinitionNodeRef { value: _, @@ -434,6 +445,7 @@ pub enum DefinitionKind<'db> { ImportFrom(ImportFromDefinitionKind), Function(AstNodeRef), Class(AstNodeRef), + TypeAlias(AstNodeRef), NamedExpression(AstNodeRef), Assignment(AssignmentDefinitionKind<'db>), AnnotatedAssignment(AstNodeRef), @@ -456,6 +468,7 @@ impl DefinitionKind<'_> { // functions, classes, and imports always bind, and we consider them declarations DefinitionKind::Function(_) | DefinitionKind::Class(_) + | DefinitionKind::TypeAlias(_) | DefinitionKind::Import(_) | DefinitionKind::ImportFrom(_) | DefinitionKind::TypeVar(_) @@ -682,6 +695,12 @@ impl From<&ast::StmtClassDef> for DefinitionNodeKey { } } +impl From<&ast::StmtTypeAlias> for DefinitionNodeKey { + fn from(node: &ast::StmtTypeAlias) -> Self { + Self(NodeKey::from_node(node)) + } +} + impl From<&ast::ExprName> for DefinitionNodeKey { fn from(node: &ast::ExprName) -> Self { Self(NodeKey::from_node(node)) diff --git a/crates/red_knot_python_semantic/src/semantic_index/symbol.rs b/crates/red_knot_python_semantic/src/semantic_index/symbol.rs index 6866635d48..87dab330b8 100644 --- a/crates/red_knot_python_semantic/src/semantic_index/symbol.rs +++ b/crates/red_knot_python_semantic/src/semantic_index/symbol.rs @@ -116,14 +116,11 @@ impl<'db> ScopeId<'db> { // Type parameter scopes behave like function scopes in terms of name resolution; CPython // symbol table also uses the term "function-like" for these scopes. matches!( - self.node(db), - NodeWithScopeKind::ClassTypeParameters(_) - | NodeWithScopeKind::FunctionTypeParameters(_) - | NodeWithScopeKind::Function(_) - | NodeWithScopeKind::ListComprehension(_) - | NodeWithScopeKind::SetComprehension(_) - | NodeWithScopeKind::DictComprehension(_) - | NodeWithScopeKind::GeneratorExpression(_) + self.node(db).scope_kind(), + ScopeKind::Annotation + | ScopeKind::Function + | ScopeKind::TypeAlias + | ScopeKind::Comprehension ) } @@ -144,6 +141,12 @@ impl<'db> ScopeId<'db> { } NodeWithScopeKind::Function(function) | NodeWithScopeKind::FunctionTypeParameters(function) => function.name.as_str(), + NodeWithScopeKind::TypeAlias(type_alias) + | NodeWithScopeKind::TypeAliasTypeParameters(type_alias) => type_alias + .name + .as_name_expr() + .map(|name| name.id.as_str()) + .unwrap_or(""), NodeWithScopeKind::Lambda(_) => "", NodeWithScopeKind::ListComprehension(_) => "", NodeWithScopeKind::SetComprehension(_) => "", @@ -201,6 +204,7 @@ pub enum ScopeKind { Class, Function, Comprehension, + TypeAlias, } impl ScopeKind { @@ -326,6 +330,8 @@ pub(crate) enum NodeWithScopeRef<'a> { Lambda(&'a ast::ExprLambda), FunctionTypeParameters(&'a ast::StmtFunctionDef), ClassTypeParameters(&'a ast::StmtClassDef), + TypeAlias(&'a ast::StmtTypeAlias), + TypeAliasTypeParameters(&'a ast::StmtTypeAlias), ListComprehension(&'a ast::ExprListComp), SetComprehension(&'a ast::ExprSetComp), DictComprehension(&'a ast::ExprDictComp), @@ -347,6 +353,12 @@ impl NodeWithScopeRef<'_> { NodeWithScopeRef::Function(function) => { NodeWithScopeKind::Function(AstNodeRef::new(module, function)) } + NodeWithScopeRef::TypeAlias(type_alias) => { + NodeWithScopeKind::TypeAlias(AstNodeRef::new(module, type_alias)) + } + NodeWithScopeRef::TypeAliasTypeParameters(type_alias) => { + NodeWithScopeKind::TypeAliasTypeParameters(AstNodeRef::new(module, type_alias)) + } NodeWithScopeRef::Lambda(lambda) => { NodeWithScopeKind::Lambda(AstNodeRef::new(module, lambda)) } @@ -387,6 +399,12 @@ impl NodeWithScopeRef<'_> { NodeWithScopeRef::ClassTypeParameters(class) => { NodeWithScopeKey::ClassTypeParameters(NodeKey::from_node(class)) } + NodeWithScopeRef::TypeAlias(type_alias) => { + NodeWithScopeKey::TypeAlias(NodeKey::from_node(type_alias)) + } + NodeWithScopeRef::TypeAliasTypeParameters(type_alias) => { + NodeWithScopeKey::TypeAliasTypeParameters(NodeKey::from_node(type_alias)) + } NodeWithScopeRef::ListComprehension(comprehension) => { NodeWithScopeKey::ListComprehension(NodeKey::from_node(comprehension)) } @@ -411,6 +429,8 @@ pub enum NodeWithScopeKind { ClassTypeParameters(AstNodeRef), Function(AstNodeRef), FunctionTypeParameters(AstNodeRef), + TypeAliasTypeParameters(AstNodeRef), + TypeAlias(AstNodeRef), Lambda(AstNodeRef), ListComprehension(AstNodeRef), SetComprehension(AstNodeRef), @@ -423,9 +443,11 @@ impl NodeWithScopeKind { match self { Self::Module => ScopeKind::Module, Self::Class(_) => ScopeKind::Class, - Self::Function(_) => ScopeKind::Function, - Self::Lambda(_) => ScopeKind::Function, - Self::FunctionTypeParameters(_) | Self::ClassTypeParameters(_) => ScopeKind::Annotation, + Self::Function(_) | Self::Lambda(_) => ScopeKind::Function, + Self::FunctionTypeParameters(_) + | Self::ClassTypeParameters(_) + | Self::TypeAliasTypeParameters(_) => ScopeKind::Annotation, + Self::TypeAlias(_) => ScopeKind::TypeAlias, Self::ListComprehension(_) | Self::SetComprehension(_) | Self::DictComprehension(_) @@ -446,6 +468,13 @@ impl NodeWithScopeKind { _ => panic!("expected function"), } } + + pub fn expect_type_alias(&self) -> &ast::StmtTypeAlias { + match self { + Self::TypeAlias(type_alias) => type_alias.node(), + _ => panic!("expected type alias"), + } + } } #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] @@ -455,6 +484,8 @@ pub(crate) enum NodeWithScopeKey { ClassTypeParameters(NodeKey), Function(NodeKey), FunctionTypeParameters(NodeKey), + TypeAlias(NodeKey), + TypeAliasTypeParameters(NodeKey), Lambda(NodeKey), ListComprehension(NodeKey), SetComprehension(NodeKey), diff --git a/crates/red_knot_python_semantic/src/types.rs b/crates/red_knot_python_semantic/src/types.rs index b19d52b4c9..89aa8077a8 100644 --- a/crates/red_knot_python_semantic/src/types.rs +++ b/crates/red_knot_python_semantic/src/types.rs @@ -1065,7 +1065,10 @@ impl<'db> Type<'db> { Type::Instance(InstanceType { class }) => match class.known(db) { Some( - KnownClass::NoneType | KnownClass::NoDefaultType | KnownClass::VersionInfo, + KnownClass::NoneType + | KnownClass::NoDefaultType + | KnownClass::VersionInfo + | KnownClass::TypeAliasType, ) => true, Some( KnownClass::Bool @@ -1536,6 +1539,7 @@ impl<'db> Type<'db> { Type::Unknown => Type::Unknown, // TODO map this to a new `Type::TypeVar` variant Type::KnownInstance(KnownInstanceType::TypeVar(_)) => *self, + Type::KnownInstance(KnownInstanceType::TypeAliasType(alias)) => alias.value_ty(db), _ => todo_type!(), } } @@ -1700,6 +1704,7 @@ pub enum KnownClass { // Typing SpecialForm, TypeVar, + TypeAliasType, NoDefaultType, // sys VersionInfo, @@ -1726,6 +1731,7 @@ impl<'db> KnownClass { Self::NoneType => "NoneType", Self::SpecialForm => "_SpecialForm", Self::TypeVar => "TypeVar", + Self::TypeAliasType => "TypeAliasType", Self::NoDefaultType => "_NoDefaultType", // This is the name the type of `sys.version_info` has in typeshed, // which is different to what `type(sys.version_info).__name__` is at runtime. @@ -1764,7 +1770,7 @@ impl<'db> KnownClass { Self::VersionInfo => CoreStdlibModule::Sys, Self::GenericAlias | Self::ModuleType | Self::FunctionType => CoreStdlibModule::Types, Self::NoneType => CoreStdlibModule::Typeshed, - Self::SpecialForm | Self::TypeVar => CoreStdlibModule::Typing, + Self::SpecialForm | Self::TypeVar | Self::TypeAliasType => CoreStdlibModule::Typing, // TODO when we understand sys.version_info, we will need an explicit fallback here, // because typing_extensions has a 3.13+ re-export for the `typing.NoDefault` // singleton, but not for `typing._NoDefaultType` @@ -1778,7 +1784,7 @@ impl<'db> KnownClass { const fn is_singleton(self) -> bool { // TODO there are other singleton types (EllipsisType, NotImplementedType) match self { - Self::NoneType | Self::NoDefaultType | Self::VersionInfo => true, + Self::NoneType | Self::NoDefaultType | Self::VersionInfo | Self::TypeAliasType => true, Self::Bool | Self::Object | Self::Bytes @@ -1820,6 +1826,7 @@ impl<'db> KnownClass { "NoneType" => Self::NoneType, "ModuleType" => Self::ModuleType, "FunctionType" => Self::FunctionType, + "TypeAliasType" => Self::TypeAliasType, "_SpecialForm" => Self::SpecialForm, "_NoDefaultType" => Self::NoDefaultType, "_version_info" => Self::VersionInfo, @@ -1853,7 +1860,7 @@ impl<'db> KnownClass { | Self::VersionInfo | Self::FunctionType => module.name() == self.canonical_module().as_str(), Self::NoneType => matches!(module.name().as_str(), "_typeshed" | "types"), - Self::SpecialForm | Self::TypeVar | Self::NoDefaultType => { + Self::SpecialForm | Self::TypeVar | Self::TypeAliasType | Self::NoDefaultType => { matches!(module.name().as_str(), "typing" | "typing_extensions") } } @@ -1871,6 +1878,8 @@ pub enum KnownInstanceType<'db> { Union, /// A single instance of `typing.TypeVar` TypeVar(TypeVarInstance<'db>), + /// A single instance of `typing.TypeAliasType` (PEP 695 type alias) + TypeAliasType(TypeAliasType<'db>), // TODO: fill this enum out with more special forms, etc. } @@ -1881,15 +1890,18 @@ impl<'db> KnownInstanceType<'db> { KnownInstanceType::Optional => "Optional", KnownInstanceType::Union => "Union", KnownInstanceType::TypeVar(_) => "TypeVar", + KnownInstanceType::TypeAliasType(_) => "TypeAliasType", } } /// Evaluate the known instance in boolean context pub const fn bool(self) -> Truthiness { match self { - Self::Literal | Self::Optional | Self::TypeVar(_) | Self::Union => { - Truthiness::AlwaysTrue - } + Self::Literal + | Self::Optional + | Self::TypeVar(_) + | Self::Union + | Self::TypeAliasType(_) => Truthiness::AlwaysTrue, } } @@ -1900,6 +1912,7 @@ impl<'db> KnownInstanceType<'db> { Self::Optional => "typing.Optional", Self::Union => "typing.Union", Self::TypeVar(typevar) => typevar.name(db), + Self::TypeAliasType(_) => "typing.TypeAliasType", } } @@ -1910,6 +1923,7 @@ impl<'db> KnownInstanceType<'db> { Self::Optional => KnownClass::SpecialForm, Self::Union => KnownClass::SpecialForm, Self::TypeVar(_) => KnownClass::TypeVar, + Self::TypeAliasType(_) => KnownClass::TypeAliasType, } } @@ -1954,6 +1968,7 @@ impl<'db> KnownInstanceType<'db> { .default_ty(db) .map(|ty| ty.to_meta_type(db)) .unwrap_or_else(|| KnownClass::NoDefaultType.to_instance(db)), + (Self::TypeAliasType(alias), "__name__") => Type::string_literal(db, alias.name(db)), _ => return self.instance_fallback(db).member(db, name), }; ty.into() @@ -2779,6 +2794,27 @@ impl<'db> Class<'db> { } } +#[salsa::interned] +pub struct TypeAliasType<'db> { + #[return_ref] + pub name: ast::name::Name, + + rhs_scope: ScopeId<'db>, +} + +#[salsa::tracked] +impl<'db> TypeAliasType<'db> { + #[salsa::tracked] + pub fn value_ty(self, db: &'db dyn Db) -> Type<'db> { + let scope = self.rhs_scope(db); + + let type_alias_stmt_node = scope.node(db).expect_type_alias(); + let definition = semantic_index(db, scope.file(db)).definition(type_alias_stmt_node); + + definition_expression_ty(db, definition, &type_alias_stmt_node.value) + } +} + /// Either the explicit `metaclass=` keyword of the class, or the inferred metaclass of one of its base classes. #[derive(Debug, Clone, PartialEq, Eq)] pub(super) struct MetaclassCandidate<'db> { @@ -3630,6 +3666,35 @@ pub(crate) mod tests { Ok(()) } + #[test] + fn type_alias_types() -> anyhow::Result<()> { + let mut db = setup_db(); + + db.write_dedented( + "src/mod.py", + r#" + type Alias1 = int + type Alias2 = int + "#, + )?; + + let mod_py = system_path_to_file(&db, "src/mod.py")?; + let ty_alias1 = global_symbol(&db, mod_py, "Alias1").expect_type(); + let ty_alias2 = global_symbol(&db, mod_py, "Alias2").expect_type(); + + let Type::KnownInstance(KnownInstanceType::TypeAliasType(alias1)) = ty_alias1 else { + panic!("Expected TypeAliasType, got {ty_alias1:?}"); + }; + assert_eq!(alias1.name(&db), "Alias1"); + assert_eq!(alias1.value_ty(&db), KnownClass::Int.to_instance(&db)); + + // Two type aliases are distinct and disjoint, even if they refer to the same type + assert!(!ty_alias1.is_equivalent_to(&db, ty_alias2)); + assert!(ty_alias1.is_disjoint_from(&db, ty_alias2)); + + Ok(()) + } + /// All other tests also make sure that `Type::Todo` works as expected. This particular /// test makes sure that we handle `Todo` types correctly, even if they originate from /// different sources. diff --git a/crates/red_knot_python_semantic/src/types/infer.rs b/crates/red_knot_python_semantic/src/types/infer.rs index 42b4297dfd..853abbe261 100644 --- a/crates/red_knot_python_semantic/src/types/infer.rs +++ b/crates/red_knot_python_semantic/src/types/infer.rs @@ -56,8 +56,8 @@ use crate::types::{ typing_extensions_symbol, Boundness, Class, ClassLiteralType, FunctionType, InstanceType, IntersectionBuilder, IntersectionType, IterationOutcome, KnownClass, KnownFunction, KnownInstanceType, MetaclassCandidate, MetaclassErrorKind, SliceLiteralType, Symbol, - Truthiness, TupleType, Type, TypeArrayDisplay, TypeVarBoundOrConstraints, TypeVarInstance, - UnionBuilder, UnionType, + Truthiness, TupleType, Type, TypeAliasType, TypeArrayDisplay, TypeVarBoundOrConstraints, + TypeVarInstance, UnionBuilder, UnionType, }; use crate::unpack::Unpack; use crate::util::subscript::{PyIndex, PySlice}; @@ -439,6 +439,12 @@ impl<'db> TypeInferenceBuilder<'db> { NodeWithScopeKind::FunctionTypeParameters(function) => { self.infer_function_type_params(function.node()); } + NodeWithScopeKind::TypeAliasTypeParameters(type_alias) => { + self.infer_type_alias_type_params(type_alias.node()); + } + NodeWithScopeKind::TypeAlias(type_alias) => { + self.infer_type_alias(type_alias.node()); + } NodeWithScopeKind::ListComprehension(comprehension) => { self.infer_list_comprehension_expression_scope(comprehension.node()); } @@ -606,6 +612,9 @@ impl<'db> TypeInferenceBuilder<'db> { self.infer_function_definition(function.node(), definition); } DefinitionKind::Class(class) => self.infer_class_definition(class.node(), definition), + DefinitionKind::TypeAlias(type_alias) => { + self.infer_type_alias_definition(type_alias.node(), definition); + } DefinitionKind::Import(import) => { self.infer_import_definition(import.node(), definition); } @@ -848,6 +857,19 @@ impl<'db> TypeInferenceBuilder<'db> { self.infer_parameters(&function.parameters); } + fn infer_type_alias_type_params(&mut self, type_alias: &ast::StmtTypeAlias) { + let type_params = type_alias + .type_params + .as_ref() + .expect("type alias type params scope without type params"); + + self.infer_type_parameters(type_params); + } + + fn infer_type_alias(&mut self, type_alias: &ast::StmtTypeAlias) { + self.infer_annotation_expression(&type_alias.value, DeferredExpressionState::Deferred); + } + fn infer_function_body(&mut self, function: &ast::StmtFunctionDef) { self.infer_body(&function.body); } @@ -1108,6 +1130,33 @@ impl<'db> TypeInferenceBuilder<'db> { } } + fn infer_type_alias_definition( + &mut self, + type_alias: &ast::StmtTypeAlias, + definition: Definition<'db>, + ) { + self.infer_expression(&type_alias.name); + + let rhs_scope = self + .index + .node_scope(NodeWithScopeRef::TypeAlias(type_alias)) + .to_scope_id(self.db, self.file); + + let type_alias_ty = + Type::KnownInstance(KnownInstanceType::TypeAliasType(TypeAliasType::new( + self.db, + &type_alias.name.as_name_expr().unwrap().id, + rhs_scope, + ))); + + self.add_declaration_with_binding( + type_alias.into(), + definition, + type_alias_ty, + type_alias_ty, + ); + } + fn infer_if_statement(&mut self, if_statement: &ast::StmtIf) { let ast::StmtIf { range: _, @@ -1830,17 +1879,8 @@ impl<'db> TypeInferenceBuilder<'db> { self.infer_augmented_op(assignment, target_type, value_type) } - fn infer_type_alias_statement(&mut self, type_alias_statement: &ast::StmtTypeAlias) { - let ast::StmtTypeAlias { - range: _, - name, - type_params: _, - value, - } = type_alias_statement; - self.infer_expression(value); - self.infer_expression(name); - - // TODO: properly handle generic type aliases, which need their own annotation scope + fn infer_type_alias_statement(&mut self, node: &ast::StmtTypeAlias) { + self.infer_definition(node); } fn infer_for_statement(&mut self, for_statement: &ast::StmtFor) { @@ -4578,7 +4618,14 @@ impl<'db> TypeInferenceBuilder<'db> { } _ => self.infer_type_expression(parameters), }, - KnownInstanceType::TypeVar(_) => todo_type!(), + KnownInstanceType::TypeVar(_) => { + self.infer_type_expression(parameters); + todo_type!() + } + KnownInstanceType::TypeAliasType(_) => { + self.infer_type_expression(parameters); + todo_type!("generic type alias") + } } } diff --git a/crates/red_knot_python_semantic/src/types/mro.rs b/crates/red_knot_python_semantic/src/types/mro.rs index 5d37617fa8..e31d487bdf 100644 --- a/crates/red_knot_python_semantic/src/types/mro.rs +++ b/crates/red_knot_python_semantic/src/types/mro.rs @@ -372,6 +372,7 @@ impl<'db> ClassBase<'db> { | Type::SubclassOf(_) => None, Type::KnownInstance(known_instance) => match known_instance { KnownInstanceType::TypeVar(_) + | KnownInstanceType::TypeAliasType(_) | KnownInstanceType::Literal | KnownInstanceType::Union | KnownInstanceType::Optional => None, diff --git a/crates/red_knot_workspace/resources/test/corpus/89_type_alias_invalid_bound.py b/crates/red_knot_workspace/resources/test/corpus/89_type_alias_invalid_bound.py new file mode 120000 index 0000000000..5dfcadebb0 --- /dev/null +++ b/crates/red_knot_workspace/resources/test/corpus/89_type_alias_invalid_bound.py @@ -0,0 +1 @@ +../../../../ruff_python_parser/resources/inline/err/type_param_invalid_bound_expr.py \ No newline at end of file diff --git a/crates/red_knot_workspace/tests/check.rs b/crates/red_knot_workspace/tests/check.rs index 0a2ad2f426..ba90a0d1de 100644 --- a/crates/red_knot_workspace/tests/check.rs +++ b/crates/red_knot_workspace/tests/check.rs @@ -264,26 +264,16 @@ impl SourceOrderVisitor<'_> for PullTypesVisitor<'_> { } /// Whether or not the .py/.pyi version of this file is expected to fail +#[rustfmt::skip] const KNOWN_FAILURES: &[(&str, bool, bool)] = &[ - // Probably related to missing support for type aliases / type params: - ("crates/ruff_python_parser/resources/inline/err/type_param_invalid_bound_expr.py", true, true), - ("crates/ruff_python_parser/resources/inline/err/type_param_param_spec_invalid_default_expr.py", true, true), - ("crates/ruff_python_parser/resources/inline/err/type_param_type_var_invalid_default_expr.py", true, true), - ("crates/ruff_python_parser/resources/inline/err/type_param_type_var_missing_default.py", true, true), - ("crates/ruff_python_parser/resources/inline/err/type_param_type_var_tuple_invalid_default_expr.py", true, true), - ("crates/ruff_python_parser/resources/inline/ok/type_param_param_spec.py", true, true), - ("crates/ruff_python_parser/resources/inline/ok/type_param_type_var_tuple.py", true, true), - ("crates/ruff_python_parser/resources/inline/ok/type_param_type_var.py", true, true), - ("crates/ruff_python_parser/resources/valid/statement/type.py", true, true), - ("crates/ruff_linter/resources/test/fixtures/flake8_type_checking/TCH004_15.py", true, true), - ("crates/ruff_linter/resources/test/fixtures/pyflakes/F401_19.py", true, true), - ("crates/ruff_linter/resources/test/fixtures/pyflakes/F821_14.py", false, true), - ("crates/ruff_linter/resources/test/fixtures/pyflakes/F821_15.py", true, true), - ("crates/ruff_linter/resources/test/fixtures/pyflakes/F821_17.py", true, true), - ("crates/ruff_linter/resources/test/fixtures/pyflakes/F821_20.py", true, true), + // related to circular references in class definitions ("crates/ruff_linter/resources/test/fixtures/pyflakes/F821_26.py", true, false), - // Fails for unknown reasons: - ("crates/ruff_linter/resources/test/fixtures/pyflakes/F632.py", true, true), ("crates/ruff_linter/resources/test/fixtures/pyflakes/F811_19.py", true, false), ("crates/ruff_linter/resources/test/fixtures/pyupgrade/UP039.py", true, false), + // related to circular references in type aliases (salsa cycle panic): + ("crates/ruff_python_parser/resources/inline/err/type_alias_invalid_value_expr.py", true, true), + // related to string annotations (https://github.com/astral-sh/ruff/issues/14440) + ("crates/ruff_linter/resources/test/fixtures/pyflakes/F821_15.py", true, true), + ("crates/ruff_linter/resources/test/fixtures/pyflakes/F821_14.py", false, true), + ("crates/ruff_linter/resources/test/fixtures/pyflakes/F632.py", true, true), ];