Skip to content

Commit

Permalink
Support type[a.X] with qualified class names (#14825)
Browse files Browse the repository at this point in the history
This adds support for `type[a.X]`, where the `type` special form is
applied to a qualified name that resolves to a class literal. This works
for both nested classes and classes imported from another module.

Closes #14545
  • Loading branch information
dcreager authored Dec 6, 2024
1 parent 3017b3b commit 8fdd880
Show file tree
Hide file tree
Showing 2 changed files with 93 additions and 4 deletions.
89 changes: 89 additions & 0 deletions crates/red_knot_python_semantic/resources/mdtest/type_of/basic.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
# type special form

## Class literal

```py
class A: ...

def f() -> type[A]:
return A

reveal_type(f()) # revealed: type[A]
```

## Nested class literal

```py
class A:
class B: ...

def f() -> type[A.B]:
return A.B

reveal_type(f()) # revealed: type[B]
```

## Deeply nested class literal

```py
class A:
class B:
class C: ...

def f() -> type[A.B.C]:
return A.B.C

reveal_type(f()) # revealed: type[C]
```

## Class literal from another module

```py
from a import A

def f() -> type[A]:
return A

reveal_type(f()) # revealed: type[A]
```

```py path=a.py
class A: ...
```

## Qualified class literal from another module

```py
import a

def f() -> type[a.B]:
return a.B

reveal_type(f()) # revealed: type[B]
```

```py path=a.py
class B: ...
```

## Deeply qualified class literal from another module

```py path=a/test.py
import a.b

# TODO: no diagnostic
# error: [unresolved-attribute]
def f() -> type[a.b.C]:
# TODO: no diagnostic
# error: [unresolved-attribute]
return a.b.C

reveal_type(f()) # revealed: @Todo(unsupported type[X] special form)
```

```py path=a/__init__.py
```

```py path=a/b.py
class C: ...
```
8 changes: 4 additions & 4 deletions crates/red_knot_python_semantic/src/types/infer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4655,18 +4655,18 @@ impl<'db> TypeInferenceBuilder<'db> {
/// Given the slice of a `type[]` annotation, return the type that the annotation represents
fn infer_subclass_of_type_expression(&mut self, slice: &ast::Expr) -> Type<'db> {
match slice {
ast::Expr::Name(_) => {
ast::Expr::Name(_) | ast::Expr::Attribute(_) => {
let name_ty = self.infer_expression(slice);
if let Some(ClassLiteralType { class }) = name_ty.into_class_literal() {
Type::subclass_of(class)
} else {
todo_type!()
todo_type!("unsupported type[X] special form")
}
}
// TODO: attributes, unions, subscripts, etc.
// TODO: unions, subscripts, etc.
_ => {
self.infer_type_expression(slice);
todo_type!()
todo_type!("unsupported type[X] special form")
}
}
}
Expand Down

0 comments on commit 8fdd880

Please sign in to comment.