Skip to content

Commit b1b8ca3

Browse files
authored
[red-knot] GenericAlias instances as a base class (#17575)
## Summary We currently emit a diagnostic for code like the following: ```py from typing import Any # error: Invalid class base with type `GenericAlias` (all bases must be a class, `Any`, `Unknown` or `Todo`) class C(tuple[Any, ...]): ... ``` The changeset here silences this diagnostic by recognizing instances of `GenericAlias` in `ClassBase::try_from_type`, and inferring a `@Todo` type for them. This is a change in preparation for #17557, because `C` previously had `Unknown` in its MRO … ```py reveal_type(C.__mro__) # tuple[Literal[C], Unknown, Literal[object]] ``` … which would cause us to think that `C` is assignable to everything. The changeset also removes some false positive `invalid-base` diagnostics across the ecosystem. ## Test Plan Updated Markdown tests.
1 parent 3fae176 commit b1b8ca3

File tree

5 files changed

+8
-11
lines changed

5 files changed

+8
-11
lines changed

crates/red_knot_python_semantic/resources/mdtest/annotations/stdlib_typing_aliases.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -106,13 +106,13 @@ reveal_type(ChainMapSubclass.__mro__)
106106
class CounterSubclass(typing.Counter): ...
107107

108108
# TODO: Should be (CounterSubclass, Counter, dict, MutableMapping, Mapping, Collection, Sized, Iterable, Container, Generic, object)
109-
# revealed: tuple[Literal[CounterSubclass], Literal[Counter], Unknown, Literal[object]]
109+
# revealed: tuple[Literal[CounterSubclass], Literal[Counter], @Todo(GenericAlias instance), @Todo(`Generic[]` subscript), Literal[object]]
110110
reveal_type(CounterSubclass.__mro__)
111111

112112
class DefaultDictSubclass(typing.DefaultDict): ...
113113

114114
# TODO: Should be (DefaultDictSubclass, defaultdict, dict, MutableMapping, Mapping, Collection, Sized, Iterable, Container, Generic, object)
115-
# revealed: tuple[Literal[DefaultDictSubclass], Literal[defaultdict], Unknown, Literal[object]]
115+
# revealed: tuple[Literal[DefaultDictSubclass], Literal[defaultdict], @Todo(GenericAlias instance), Literal[object]]
116116
reveal_type(DefaultDictSubclass.__mro__)
117117

118118
class DequeSubclass(typing.Deque): ...
@@ -124,6 +124,6 @@ reveal_type(DequeSubclass.__mro__)
124124
class OrderedDictSubclass(typing.OrderedDict): ...
125125

126126
# TODO: Should be (OrderedDictSubclass, OrderedDict, dict, MutableMapping, Mapping, Collection, Sized, Iterable, Container, Generic, object)
127-
# revealed: tuple[Literal[OrderedDictSubclass], Literal[OrderedDict], Unknown, Literal[object]]
127+
# revealed: tuple[Literal[OrderedDictSubclass], Literal[OrderedDict], @Todo(GenericAlias instance), Literal[object]]
128128
reveal_type(OrderedDictSubclass.__mro__)
129129
```

crates/red_knot_python_semantic/resources/mdtest/generics/classes.md

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -326,8 +326,6 @@ class Sub(Base[Sub]): ...
326326
## Another cyclic case
327327

328328
```pyi
329-
# TODO no error (generics)
330-
# error: [invalid-base]
331329
class Derived[T](list[Derived[T]]): ...
332330
```
333331

crates/red_knot_python_semantic/resources/mdtest/subscript/tuple.md

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -81,13 +81,11 @@ python-version = "3.9"
8181
```
8282

8383
```py
84-
# TODO: `tuple[int, str]` is a valid base (generics)
85-
# error: [invalid-base] "Invalid class base with type `GenericAlias` (all bases must be a class, `Any`, `Unknown` or `Todo`)"
8684
class A(tuple[int, str]): ...
8785

8886
# Runtime value: `(A, tuple, object)`
8987
# TODO: Generics
90-
reveal_type(A.__mro__) # revealed: tuple[Literal[A], Unknown, Literal[object]]
88+
reveal_type(A.__mro__) # revealed: tuple[Literal[A], @Todo(GenericAlias instance), Literal[object]]
9189
```
9290

9391
## `typing.Tuple`

crates/red_knot_python_semantic/resources/mdtest/type_of/basic.md

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -145,12 +145,10 @@ _: type[A, B]
145145
## As a base class
146146

147147
```py
148-
# TODO: this is a false positive
149-
# error: [invalid-base] "Invalid class base with type `GenericAlias` (all bases must be a class, `Any`, `Unknown` or `Todo`)"
150148
class Foo(type[int]): ...
151149

152150
# TODO: should be `tuple[Literal[Foo], Literal[type], Literal[object]]
153-
reveal_type(Foo.__mro__) # revealed: tuple[Literal[Foo], Unknown, Literal[object]]
151+
reveal_type(Foo.__mro__) # revealed: tuple[Literal[Foo], @Todo(GenericAlias instance), Literal[object]]
154152
```
155153

156154
## `@final` classes

crates/red_knot_python_semantic/src/types/class_base.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,9 @@ impl<'db> ClassBase<'db> {
7878
Self::Class(literal.default_specialization(db))
7979
}),
8080
Type::GenericAlias(generic) => Some(Self::Class(ClassType::Generic(generic))),
81+
Type::Instance(instance) if instance.class().is_known(db, KnownClass::GenericAlias) => {
82+
Self::try_from_type(db, todo_type!("GenericAlias instance"))
83+
}
8184
Type::Union(_) => None, // TODO -- forces consideration of multiple possible MROs?
8285
Type::Intersection(_) => None, // TODO -- probably incorrect?
8386
Type::Instance(_) => None, // TODO -- handle `__mro_entries__`?

0 commit comments

Comments
 (0)