mirror of
https://github.com/astral-sh/ruff.git
synced 2025-08-04 02:38:25 +00:00
[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.
This commit is contained in:
parent
3fae176345
commit
b1b8ca3bcd
5 changed files with 8 additions and 11 deletions
|
@ -106,13 +106,13 @@ reveal_type(ChainMapSubclass.__mro__)
|
|||
class CounterSubclass(typing.Counter): ...
|
||||
|
||||
# TODO: Should be (CounterSubclass, Counter, dict, MutableMapping, Mapping, Collection, Sized, Iterable, Container, Generic, object)
|
||||
# revealed: tuple[Literal[CounterSubclass], Literal[Counter], Unknown, Literal[object]]
|
||||
# revealed: tuple[Literal[CounterSubclass], Literal[Counter], @Todo(GenericAlias instance), @Todo(`Generic[]` subscript), Literal[object]]
|
||||
reveal_type(CounterSubclass.__mro__)
|
||||
|
||||
class DefaultDictSubclass(typing.DefaultDict): ...
|
||||
|
||||
# TODO: Should be (DefaultDictSubclass, defaultdict, dict, MutableMapping, Mapping, Collection, Sized, Iterable, Container, Generic, object)
|
||||
# revealed: tuple[Literal[DefaultDictSubclass], Literal[defaultdict], Unknown, Literal[object]]
|
||||
# revealed: tuple[Literal[DefaultDictSubclass], Literal[defaultdict], @Todo(GenericAlias instance), Literal[object]]
|
||||
reveal_type(DefaultDictSubclass.__mro__)
|
||||
|
||||
class DequeSubclass(typing.Deque): ...
|
||||
|
@ -124,6 +124,6 @@ reveal_type(DequeSubclass.__mro__)
|
|||
class OrderedDictSubclass(typing.OrderedDict): ...
|
||||
|
||||
# TODO: Should be (OrderedDictSubclass, OrderedDict, dict, MutableMapping, Mapping, Collection, Sized, Iterable, Container, Generic, object)
|
||||
# revealed: tuple[Literal[OrderedDictSubclass], Literal[OrderedDict], Unknown, Literal[object]]
|
||||
# revealed: tuple[Literal[OrderedDictSubclass], Literal[OrderedDict], @Todo(GenericAlias instance), Literal[object]]
|
||||
reveal_type(OrderedDictSubclass.__mro__)
|
||||
```
|
||||
|
|
|
@ -326,8 +326,6 @@ class Sub(Base[Sub]): ...
|
|||
## Another cyclic case
|
||||
|
||||
```pyi
|
||||
# TODO no error (generics)
|
||||
# error: [invalid-base]
|
||||
class Derived[T](list[Derived[T]]): ...
|
||||
```
|
||||
|
||||
|
|
|
@ -81,13 +81,11 @@ python-version = "3.9"
|
|||
```
|
||||
|
||||
```py
|
||||
# TODO: `tuple[int, str]` is a valid base (generics)
|
||||
# error: [invalid-base] "Invalid class base with type `GenericAlias` (all bases must be a class, `Any`, `Unknown` or `Todo`)"
|
||||
class A(tuple[int, str]): ...
|
||||
|
||||
# Runtime value: `(A, tuple, object)`
|
||||
# TODO: Generics
|
||||
reveal_type(A.__mro__) # revealed: tuple[Literal[A], Unknown, Literal[object]]
|
||||
reveal_type(A.__mro__) # revealed: tuple[Literal[A], @Todo(GenericAlias instance), Literal[object]]
|
||||
```
|
||||
|
||||
## `typing.Tuple`
|
||||
|
|
|
@ -145,12 +145,10 @@ _: type[A, B]
|
|||
## As a base class
|
||||
|
||||
```py
|
||||
# TODO: this is a false positive
|
||||
# error: [invalid-base] "Invalid class base with type `GenericAlias` (all bases must be a class, `Any`, `Unknown` or `Todo`)"
|
||||
class Foo(type[int]): ...
|
||||
|
||||
# TODO: should be `tuple[Literal[Foo], Literal[type], Literal[object]]
|
||||
reveal_type(Foo.__mro__) # revealed: tuple[Literal[Foo], Unknown, Literal[object]]
|
||||
reveal_type(Foo.__mro__) # revealed: tuple[Literal[Foo], @Todo(GenericAlias instance), Literal[object]]
|
||||
```
|
||||
|
||||
## `@final` classes
|
||||
|
|
|
@ -78,6 +78,9 @@ impl<'db> ClassBase<'db> {
|
|||
Self::Class(literal.default_specialization(db))
|
||||
}),
|
||||
Type::GenericAlias(generic) => Some(Self::Class(ClassType::Generic(generic))),
|
||||
Type::Instance(instance) if instance.class().is_known(db, KnownClass::GenericAlias) => {
|
||||
Self::try_from_type(db, todo_type!("GenericAlias instance"))
|
||||
}
|
||||
Type::Union(_) => None, // TODO -- forces consideration of multiple possible MROs?
|
||||
Type::Intersection(_) => None, // TODO -- probably incorrect?
|
||||
Type::Instance(_) => None, // TODO -- handle `__mro_entries__`?
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue