[ty] Report duplicate Protocol or Generic base classes with [duplicate-base], not [inconsistent-mro] (#17971)

This commit is contained in:
Alex Waygood 2025-05-08 23:41:22 +01:00 committed by GitHub
parent 4d81a41107
commit 9b694ada82
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 27 additions and 7 deletions

View file

@ -19,6 +19,16 @@ reveal_type(generic_context(SingleTypevar)) # revealed: tuple[T]
reveal_type(generic_context(MultipleTypevars)) # revealed: tuple[T, S]
```
Inheriting from `Generic` multiple times yields a `duplicate-base` diagnostic, just like any other
class:
```py
class Bad(Generic[T], Generic[T]): ... # error: [duplicate-base]
# TODO: should emit an error (fails at runtime)
class AlsoBad(Generic[T], Generic[S]): ...
```
You cannot use the same typevar more than once.
```py

View file

@ -35,7 +35,7 @@ Just like for any other class base, it is an error for `Protocol` to appear mult
class's bases:
```py
class Foo(Protocol, Protocol): ... # error: [inconsistent-mro]
class Foo(Protocol, Protocol): ... # error: [duplicate-base]
reveal_type(Foo.__mro__) # revealed: tuple[<class 'Foo'>, Unknown, <class 'object'>]
```

View file

@ -65,6 +65,19 @@ impl<'db> ClassBase<'db> {
Display { base: self, db }
}
pub(crate) fn name(self, db: &'db dyn Db) -> &'db str {
match self {
ClassBase::Class(class) => class.name(db),
ClassBase::Dynamic(DynamicType::Any) => "Any",
ClassBase::Dynamic(DynamicType::Unknown) => "Unknown",
ClassBase::Dynamic(DynamicType::Todo(_)) => "@Todo",
ClassBase::Protocol | ClassBase::Dynamic(DynamicType::SubscriptedProtocol) => {
"Protocol"
}
ClassBase::Generic(_) => "Generic",
}
}
/// Return a `ClassBase` representing the class `builtins.object`
pub(super) fn object(db: &'db dyn Db) -> Self {
KnownClass::Object

View file

@ -177,16 +177,13 @@ impl<'db> Mro<'db> {
continue;
}
match base {
ClassBase::Class(class) => {
ClassBase::Class(_) | ClassBase::Generic(_) | ClassBase::Protocol => {
errors.push(DuplicateBaseError {
duplicate_base: class.class_literal(db).0,
duplicate_base: base,
first_index: *first_index,
later_indices: later_indices.iter().copied().collect(),
});
}
// TODO these should also be reported as duplicate bases
// rather than using the less specific `inconsistent-mro` error
ClassBase::Generic(_) | ClassBase::Protocol => continue,
ClassBase::Dynamic(_) => duplicate_dynamic_bases = true,
}
}
@ -385,7 +382,7 @@ impl<'db> MroErrorKind<'db> {
#[derive(Debug, PartialEq, Eq, salsa::Update)]
pub(super) struct DuplicateBaseError<'db> {
/// The base that is duplicated in the class's bases list.
pub(super) duplicate_base: ClassLiteral<'db>,
pub(super) duplicate_base: ClassBase<'db>,
/// The index of the first occurrence of the base in the class's bases list.
pub(super) first_index: usize,
/// The indices of the base's later occurrences in the class's bases list.