Skip to content

Commit e94c708

Browse files
committed
check legacy vs inherited
1 parent 6dd9c6b commit e94c708

File tree

3 files changed

+30
-3
lines changed

3 files changed

+30
-3
lines changed

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,12 +62,12 @@ if you do, you have to mention all of the typevars that you use in your other ba
6262
```py
6363
class ExplicitInheritedGeneric(MultipleTypevars[T, S], Generic[T, S]): ...
6464

65-
# TODO: error
65+
# error: [invalid-generic-class] "`Generic` base class must include all type variables used in other base classes"
6666
class ExplicitInheritedGenericMissingTypevar(MultipleTypevars[T, S], Generic[T]): ...
6767
class ExplicitInheritedGenericPartiallySpecialized(MultipleTypevars[T, int], Generic[T]): ...
6868
class ExplicitInheritedGenericPartiallySpecializedExtraTypevar(MultipleTypevars[T, int], Generic[T, S]): ...
6969

70-
# TODO: error
70+
# error: [invalid-generic-class] "`Generic` base class must include all type variables used in other base classes"
7171
class ExplicitInheritedGenericPartiallySpecializedMissingTypevar(MultipleTypevars[T, int], Generic[S]): ...
7272

7373
reveal_type(generic_context(ExplicitInheritedGeneric)) # revealed: tuple[T, S]

crates/red_knot_python_semantic/src/types/generics.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use itertools::Itertools;
12
use ruff_python_ast as ast;
23
use rustc_hash::FxHashMap;
34

@@ -147,6 +148,15 @@ impl<'db> GenericContext<'db> {
147148
self.specialize(db, types.into())
148149
}
149150

151+
pub(crate) fn is_subset_of(self, db: &'db dyn Db, other: GenericContext<'db>) -> bool {
152+
for variable in self.variables(db) {
153+
if !other.variables(db).iter().contains(variable) {
154+
return false;
155+
}
156+
}
157+
true
158+
}
159+
150160
pub(crate) fn specialize(
151161
self,
152162
db: &'db dyn Db,

crates/red_knot_python_semantic/src/types/infer.rs

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -978,7 +978,8 @@ impl<'db> TypeInferenceBuilder<'db> {
978978
}
979979
}
980980

981-
// (5) Check that a generic class does not use both PEP-695 and legacy syntax.
981+
// (5) Check that a generic class does not have invalid or conflicting generic
982+
// contexts.
982983
if class.pep695_generic_context(self.db()).is_some()
983984
&& class.legacy_generic_context(self.db()).is_some()
984985
{
@@ -989,6 +990,22 @@ impl<'db> TypeInferenceBuilder<'db> {
989990
);
990991
}
991992
}
993+
994+
if let (Some(legacy), Some(inherited)) = (
995+
class.legacy_generic_context(self.db()),
996+
class.inherited_legacy_generic_context(self.db()),
997+
) {
998+
if !inherited.is_subset_of(self.db(), legacy) {
999+
if let Some(builder) =
1000+
self.context.report_lint(&INVALID_GENERIC_CLASS, class_node)
1001+
{
1002+
builder.into_diagnostic(
1003+
"`Generic` base class must include all type \
1004+
variables used in other base classes",
1005+
);
1006+
}
1007+
}
1008+
}
9921009
}
9931010
}
9941011

0 commit comments

Comments
 (0)