[red-knot] PEP 695 type aliases (#14357)

## Summary

Add support for (non-generic) type aliases. The main motivation behind
this was to get rid of panics involving expressions in (generic) type
aliases. But it turned out the best way to fix it was to implement
(partial) support for type aliases.

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

## Test Plan

- Updated corpus test allow list to reflect that we don't panic anymore.
- Added Markdown-based test for type aliases (`type_alias.md`)
This commit is contained in:
David Peter 2024-11-22 08:47:14 +01:00 committed by GitHub
parent 8358ad8d25
commit a90e404c3f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 298 additions and 51 deletions

View file

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

View file

@ -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("<unknown>".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 {

View file

@ -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<ast::StmtFunctionDef>),
Class(AstNodeRef<ast::StmtClassDef>),
TypeAlias(AstNodeRef<ast::StmtTypeAlias>),
NamedExpression(AstNodeRef<ast::ExprNamed>),
Assignment(AssignmentDefinitionKind<'db>),
AnnotatedAssignment(AstNodeRef<ast::StmtAnnAssign>),
@ -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))

View file

@ -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("<type alias>"),
NodeWithScopeKind::Lambda(_) => "<lambda>",
NodeWithScopeKind::ListComprehension(_) => "<listcomp>",
NodeWithScopeKind::SetComprehension(_) => "<setcomp>",
@ -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<ast::StmtClassDef>),
Function(AstNodeRef<ast::StmtFunctionDef>),
FunctionTypeParameters(AstNodeRef<ast::StmtFunctionDef>),
TypeAliasTypeParameters(AstNodeRef<ast::StmtTypeAlias>),
TypeAlias(AstNodeRef<ast::StmtTypeAlias>),
Lambda(AstNodeRef<ast::ExprLambda>),
ListComprehension(AstNodeRef<ast::ExprListComp>),
SetComprehension(AstNodeRef<ast::ExprSetComp>),
@ -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),

View file

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

View file

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

View file

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

View file

@ -0,0 +1 @@
../../../../ruff_python_parser/resources/inline/err/type_param_invalid_bound_expr.py

View file

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