diff --git a/crates/ty_python_semantic/resources/mdtest/generics/legacy/classes.md b/crates/ty_python_semantic/resources/mdtest/generics/legacy/classes.md index 84f89e1811..184a7b49a5 100644 --- a/crates/ty_python_semantic/resources/mdtest/generics/legacy/classes.md +++ b/crates/ty_python_semantic/resources/mdtest/generics/legacy/classes.md @@ -71,6 +71,26 @@ reveal_type(generic_context(InheritedGenericPartiallySpecialized)) reveal_type(generic_context(InheritedGenericFullySpecialized)) ``` +In a nested class, references to typevars in an enclosing class are not allowed, but if they are +present, they are not included in the class's generic context. + +```py +class OuterClass(Generic[T]): + # error: [invalid-generic-class] "Generic class `InnerClass` must not reference type variables bound in an enclosing scope" + class InnerClass(list[T]): ... + # revealed: None + reveal_type(generic_context(InnerClass)) + + def method(self): + # error: [invalid-generic-class] "Generic class `InnerClassInMethod` must not reference type variables bound in an enclosing scope" + class InnerClassInMethod(list[T]): ... + # revealed: None + reveal_type(generic_context(InnerClassInMethod)) + +# revealed: tuple[T@OuterClass] +reveal_type(generic_context(OuterClass)) +``` + If you don't specialize a generic base class, we use the default specialization, which maps each typevar to its default value or `Any`. Since that base class is fully specialized, it does not make the inheriting class generic. diff --git a/crates/ty_python_semantic/resources/mdtest/generics/scoping.md b/crates/ty_python_semantic/resources/mdtest/generics/scoping.md index fb8b74428c..308092f4d1 100644 --- a/crates/ty_python_semantic/resources/mdtest/generics/scoping.md +++ b/crates/ty_python_semantic/resources/mdtest/generics/scoping.md @@ -260,27 +260,31 @@ class C[T]: ### Generic class within generic function + + ```py from typing import Iterable def f[T](x: T, y: T) -> None: class Ok[S]: ... - # TODO: error for reuse of typevar + # error: [invalid-generic-class] class Bad1[T]: ... - # TODO: error for reuse of typevar + # error: [invalid-generic-class] class Bad2(Iterable[T]): ... ``` ### Generic class within generic class + + ```py from typing import Iterable class C[T]: class Ok1[S]: ... - # TODO: error for reuse of typevar + # error: [invalid-generic-class] class Bad1[T]: ... - # TODO: error for reuse of typevar + # error: [invalid-generic-class] class Bad2(Iterable[T]): ... ``` diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/scoping.md_-_Scoping_rules_for_ty…_-_Nested_formal_typeva…_-_Generic_class_within…_(3259718bf20b45a2).snap b/crates/ty_python_semantic/resources/mdtest/snapshots/scoping.md_-_Scoping_rules_for_ty…_-_Nested_formal_typeva…_-_Generic_class_within…_(3259718bf20b45a2).snap new file mode 100644 index 0000000000..5a02751545 --- /dev/null +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/scoping.md_-_Scoping_rules_for_ty…_-_Nested_formal_typeva…_-_Generic_class_within…_(3259718bf20b45a2).snap @@ -0,0 +1,63 @@ +--- +source: crates/ty_test/src/lib.rs +expression: snapshot +--- +--- +mdtest name: scoping.md - Scoping rules for type variables - Nested formal typevars must be distinct - Generic class within generic function +mdtest path: crates/ty_python_semantic/resources/mdtest/generics/scoping.md +--- + +# Python source files + +## mdtest_snippet.py + +``` +1 | from typing import Iterable +2 | +3 | def f[T](x: T, y: T) -> None: +4 | class Ok[S]: ... +5 | # error: [invalid-generic-class] +6 | class Bad1[T]: ... +7 | # error: [invalid-generic-class] +8 | class Bad2(Iterable[T]): ... +``` + +# Diagnostics + +``` +error[invalid-generic-class]: Generic class `Bad1` must not reference type variables bound in an enclosing scope + --> src/mdtest_snippet.py:3:5 + | +1 | from typing import Iterable +2 | +3 | def f[T](x: T, y: T) -> None: + | ------------------------ Type variable `T` is bound in this enclosing scope +4 | class Ok[S]: ... +5 | # error: [invalid-generic-class] +6 | class Bad1[T]: ... + | ^^^^ `T` referenced in class definition here +7 | # error: [invalid-generic-class] +8 | class Bad2(Iterable[T]): ... + | +info: rule `invalid-generic-class` is enabled by default + +``` + +``` +error[invalid-generic-class]: Generic class `Bad2` must not reference type variables bound in an enclosing scope + --> src/mdtest_snippet.py:3:5 + | +1 | from typing import Iterable +2 | +3 | def f[T](x: T, y: T) -> None: + | ------------------------ Type variable `T` is bound in this enclosing scope +4 | class Ok[S]: ... +5 | # error: [invalid-generic-class] +6 | class Bad1[T]: ... +7 | # error: [invalid-generic-class] +8 | class Bad2(Iterable[T]): ... + | ^^^^^^^^^^^^^^^^^ `T` referenced in class definition here + | +info: rule `invalid-generic-class` is enabled by default + +``` diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/scoping.md_-_Scoping_rules_for_ty…_-_Nested_formal_typeva…_-_Generic_class_within…_(711fb86287c4d87b).snap b/crates/ty_python_semantic/resources/mdtest/snapshots/scoping.md_-_Scoping_rules_for_ty…_-_Nested_formal_typeva…_-_Generic_class_within…_(711fb86287c4d87b).snap new file mode 100644 index 0000000000..064c0f5344 --- /dev/null +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/scoping.md_-_Scoping_rules_for_ty…_-_Nested_formal_typeva…_-_Generic_class_within…_(711fb86287c4d87b).snap @@ -0,0 +1,63 @@ +--- +source: crates/ty_test/src/lib.rs +expression: snapshot +--- +--- +mdtest name: scoping.md - Scoping rules for type variables - Nested formal typevars must be distinct - Generic class within generic class +mdtest path: crates/ty_python_semantic/resources/mdtest/generics/scoping.md +--- + +# Python source files + +## mdtest_snippet.py + +``` +1 | from typing import Iterable +2 | +3 | class C[T]: +4 | class Ok1[S]: ... +5 | # error: [invalid-generic-class] +6 | class Bad1[T]: ... +7 | # error: [invalid-generic-class] +8 | class Bad2(Iterable[T]): ... +``` + +# Diagnostics + +``` +error[invalid-generic-class]: Generic class `Bad1` must not reference type variables bound in an enclosing scope + --> src/mdtest_snippet.py:3:7 + | +1 | from typing import Iterable +2 | +3 | class C[T]: + | - Type variable `T` is bound in this enclosing scope +4 | class Ok1[S]: ... +5 | # error: [invalid-generic-class] +6 | class Bad1[T]: ... + | ^^^^ `T` referenced in class definition here +7 | # error: [invalid-generic-class] +8 | class Bad2(Iterable[T]): ... + | +info: rule `invalid-generic-class` is enabled by default + +``` + +``` +error[invalid-generic-class]: Generic class `Bad2` must not reference type variables bound in an enclosing scope + --> src/mdtest_snippet.py:3:7 + | +1 | from typing import Iterable +2 | +3 | class C[T]: + | - Type variable `T` is bound in this enclosing scope +4 | class Ok1[S]: ... +5 | # error: [invalid-generic-class] +6 | class Bad1[T]: ... +7 | # error: [invalid-generic-class] +8 | class Bad2(Iterable[T]): ... + | ^^^^^^^^^^^^^^^^^ `T` referenced in class definition here + | +info: rule `invalid-generic-class` is enabled by default + +``` diff --git a/crates/ty_python_semantic/src/types.rs b/crates/ty_python_semantic/src/types.rs index cc9021719a..67a2486b77 100644 --- a/crates/ty_python_semantic/src/types.rs +++ b/crates/ty_python_semantic/src/types.rs @@ -8210,12 +8210,16 @@ impl<'db> From> for BindingContext<'db> { } impl<'db> BindingContext<'db> { - fn name(self, db: &'db dyn Db) -> Option { + pub(crate) fn definition(self) -> Option> { match self { - BindingContext::Definition(definition) => definition.name(db), + BindingContext::Definition(definition) => Some(definition), BindingContext::Synthetic => None, } } + + fn name(self, db: &'db dyn Db) -> Option { + self.definition().and_then(|definition| definition.name(db)) + } } /// A type variable that has been bound to a generic context, and which can be specialized to a diff --git a/crates/ty_python_semantic/src/types/class.rs b/crates/ty_python_semantic/src/types/class.rs index c28a132abd..201e382fdd 100644 --- a/crates/ty_python_semantic/src/types/class.rs +++ b/crates/ty_python_semantic/src/types/class.rs @@ -1,3 +1,4 @@ +use std::cell::RefCell; use std::sync::{LazyLock, Mutex}; use super::TypeVarVariance; @@ -20,12 +21,15 @@ use crate::types::context::InferContext; use crate::types::diagnostic::INVALID_TYPE_ALIAS_TYPE; use crate::types::enums::enum_metadata; use crate::types::function::{DataclassTransformerParams, KnownFunction}; -use crate::types::generics::{GenericContext, Specialization, walk_specialization}; +use crate::types::generics::{ + GenericContext, Specialization, walk_generic_context, walk_specialization, +}; use crate::types::infer::nearest_enclosing_class; use crate::types::member::{Member, class_member}; use crate::types::signatures::{CallableSignature, Parameter, Parameters, Signature}; use crate::types::tuple::{TupleSpec, TupleType}; use crate::types::typed_dict::typed_dict_params_from_class_def; +use crate::types::visitor::{NonAtomicType, TypeKind, TypeVisitor, walk_non_atomic_type}; use crate::types::{ ApplyTypeMappingVisitor, Binding, BoundSuperType, CallableType, DataclassParams, DeprecatedInstance, FindLegacyTypeVarsVisitor, HasRelationToVisitor, IsDisjointVisitor, @@ -35,7 +39,7 @@ use crate::types::{ determine_upper_bound, infer_definition_types, }; use crate::{ - Db, FxIndexMap, FxOrderSet, Program, + Db, FxIndexMap, FxIndexSet, FxOrderSet, Program, module_resolver::file_to_module, place::{ Boundness, LookupError, LookupResult, Place, PlaceAndQualifiers, known_module_symbol, @@ -1382,7 +1386,7 @@ impl get_size2::GetSize for ClassLiteral<'_> {} #[expect(clippy::ref_option)] #[allow(clippy::trivially_copy_pass_by_ref)] -fn pep695_generic_context_cycle_recover<'db>( +fn generic_context_cycle_recover<'db>( _db: &'db dyn Db, _value: &Option>, _count: u32, @@ -1391,7 +1395,7 @@ fn pep695_generic_context_cycle_recover<'db>( salsa::CycleRecoveryAction::Iterate } -fn pep695_generic_context_cycle_initial<'db>( +fn generic_context_cycle_initial<'db>( _db: &'db dyn Db, _self: ClassLiteral<'db>, ) -> Option> { @@ -1431,7 +1435,11 @@ impl<'db> ClassLiteral<'db> { self.pep695_generic_context(db).is_some() } - #[salsa::tracked(cycle_fn=pep695_generic_context_cycle_recover, cycle_initial=pep695_generic_context_cycle_initial, heap_size=ruff_memory_usage::heap_size)] + #[salsa::tracked( + cycle_fn=generic_context_cycle_recover, + cycle_initial=generic_context_cycle_initial, + heap_size=ruff_memory_usage::heap_size, + )] pub(crate) fn pep695_generic_context(self, db: &'db dyn Db) -> Option> { let scope = self.body_scope(db); let file = scope.file(db); @@ -1454,12 +1462,18 @@ impl<'db> ClassLiteral<'db> { }) } + #[salsa::tracked( + cycle_fn=generic_context_cycle_recover, + cycle_initial=generic_context_cycle_initial, + heap_size=ruff_memory_usage::heap_size, + )] pub(crate) fn inherited_legacy_generic_context( self, db: &'db dyn Db, ) -> Option> { GenericContext::from_base_classes( db, + self.definition(db), self.explicit_bases(db) .iter() .copied() @@ -1467,6 +1481,57 @@ impl<'db> ClassLiteral<'db> { ) } + /// Returns all of the typevars that are referenced in this class's definition. This includes + /// any typevars bound in its generic context, as well as any typevars mentioned in its base + /// class list. (This is used to ensure that classes do not bind or reference typevars from + /// enclosing generic contexts.) + pub(crate) fn typevars_referenced_in_definition( + self, + db: &'db dyn Db, + ) -> FxIndexSet> { + #[derive(Default)] + struct CollectTypeVars<'db> { + typevars: RefCell>>, + seen_types: RefCell>>, + } + + impl<'db> TypeVisitor<'db> for CollectTypeVars<'db> { + fn should_visit_lazy_type_attributes(&self) -> bool { + false + } + + fn visit_bound_type_var_type( + &self, + _db: &'db dyn Db, + bound_typevar: BoundTypeVarInstance<'db>, + ) { + self.typevars.borrow_mut().insert(bound_typevar); + } + + fn visit_type(&self, db: &'db dyn Db, ty: Type<'db>) { + match TypeKind::from(ty) { + TypeKind::Atomic => {} + TypeKind::NonAtomic(non_atomic_type) => { + if !self.seen_types.borrow_mut().insert(non_atomic_type) { + // If we have already seen this type, we can skip it. + return; + } + walk_non_atomic_type(db, non_atomic_type, self); + } + } + } + } + + let visitor = CollectTypeVars::default(); + if let Some(generic_context) = self.generic_context(db) { + walk_generic_context(db, generic_context, &visitor); + } + for base in self.explicit_bases(db) { + visitor.visit_type(db, *base); + } + visitor.typevars.into_inner() + } + /// Returns the generic context that should be inherited by any constructor methods of this /// class. /// diff --git a/crates/ty_python_semantic/src/types/context.rs b/crates/ty_python_semantic/src/types/context.rs index c3901ecbad..d4ac8fd898 100644 --- a/crates/ty_python_semantic/src/types/context.rs +++ b/crates/ty_python_semantic/src/types/context.rs @@ -100,6 +100,10 @@ impl<'db, 'ast> InferContext<'db, 'ast> { self.diagnostics.get_mut().extend(other); } + pub(super) fn is_lint_enabled(&self, lint: &'static LintMetadata) -> bool { + LintDiagnosticGuardBuilder::severity_and_source(self, lint).is_some() + } + /// Optionally return a builder for a lint diagnostic guard. /// /// If the current context believes a diagnostic should be reported for @@ -396,11 +400,10 @@ pub(super) struct LintDiagnosticGuardBuilder<'db, 'ctx> { } impl<'db, 'ctx> LintDiagnosticGuardBuilder<'db, 'ctx> { - fn new( + fn severity_and_source( ctx: &'ctx InferContext<'db, 'ctx>, lint: &'static LintMetadata, - range: TextRange, - ) -> Option> { + ) -> Option<(Severity, LintSource)> { // The comment below was copied from the original // implementation of diagnostic reporting. The code // has been refactored, but this still kind of looked @@ -429,14 +432,25 @@ impl<'db, 'ctx> LintDiagnosticGuardBuilder<'db, 'ctx> { if ctx.is_in_multi_inference() { return None; } - let id = DiagnosticId::Lint(lint.name()); + + Some((severity, source)) + } + + fn new( + ctx: &'ctx InferContext<'db, 'ctx>, + lint: &'static LintMetadata, + range: TextRange, + ) -> Option> { + let (severity, source) = Self::severity_and_source(ctx, lint)?; let suppressions = suppressions(ctx.db(), ctx.file()); + let lint_id = LintId::of(lint); if let Some(suppression) = suppressions.find_suppression(range, lint_id) { ctx.diagnostics.borrow_mut().mark_used(suppression.id()); return None; } + let id = DiagnosticId::Lint(lint.name()); let primary_span = Span::from(ctx.file()).with_range(range); Some(LintDiagnosticGuardBuilder { ctx, diff --git a/crates/ty_python_semantic/src/types/diagnostic.rs b/crates/ty_python_semantic/src/types/diagnostic.rs index f208ae3f5d..982c76672d 100644 --- a/crates/ty_python_semantic/src/types/diagnostic.rs +++ b/crates/ty_python_semantic/src/types/diagnostic.rs @@ -18,10 +18,10 @@ use crate::types::string_annotation::{ RAW_STRING_TYPE_ANNOTATION, }; use crate::types::{ - ClassType, DynamicType, LintDiagnosticGuard, Protocol, ProtocolInstanceType, SubclassOfInner, - TypeContext, binding_type, infer_isolated_expression, + BoundTypeVarInstance, ClassType, DynamicType, LintDiagnosticGuard, Protocol, + ProtocolInstanceType, SpecialFormType, SubclassOfInner, Type, TypeContext, binding_type, + infer_isolated_expression, protocol_class::ProtocolClass, }; -use crate::types::{SpecialFormType, Type, protocol_class::ProtocolClass}; use crate::util::diagnostics::format_enumeration; use crate::{ Db, DisplaySettings, FxIndexMap, FxOrderMap, Module, ModuleName, Program, declare_lint, @@ -3055,6 +3055,39 @@ pub(crate) fn report_cannot_pop_required_field_on_typed_dict<'db>( } } +pub(crate) fn report_rebound_typevar<'db>( + context: &InferContext<'db, '_>, + typevar_name: &ast::name::Name, + class: ClassLiteral<'db>, + class_node: &ast::StmtClassDef, + other_typevar: BoundTypeVarInstance<'db>, +) { + let db = context.db(); + let Some(builder) = context.report_lint(&INVALID_GENERIC_CLASS, class.header_range(db)) else { + return; + }; + let mut diagnostic = builder.into_diagnostic(format_args!( + "Generic class `{}` must not reference type variables bound in an enclosing scope", + class_node.name, + )); + diagnostic.set_primary_message(format_args!( + "`{typevar_name}` referenced in class definition here" + )); + let Some(other_definition) = other_typevar.binding_context(db).definition() else { + return; + }; + let span = match binding_type(db, other_definition) { + Type::ClassLiteral(class) => Some(class.header_span(db)), + Type::FunctionLiteral(function) => function.spans(db).map(|spans| spans.signature), + _ => return, + }; + if let Some(span) = span { + diagnostic.annotate(Annotation::secondary(span).message(format_args!( + "Type variable `{typevar_name}` is bound in this enclosing scope", + ))); + } +} + /// This function receives an unresolved `from foo import bar` import, /// where `foo` can be resolved to a module but that module does not /// have a `bar` member or submodule. diff --git a/crates/ty_python_semantic/src/types/generics.rs b/crates/ty_python_semantic/src/types/generics.rs index 436638ae40..0db16268a3 100644 --- a/crates/ty_python_semantic/src/types/generics.rs +++ b/crates/ty_python_semantic/src/types/generics.rs @@ -24,7 +24,7 @@ use crate::{Db, FxOrderMap, FxOrderSet}; /// Returns an iterator of any generic context introduced by the given scope or any enclosing /// scope. -fn enclosing_generic_contexts<'db>( +pub(crate) fn enclosing_generic_contexts<'db>( db: &'db dyn Db, index: &SemanticIndex<'db>, scope: FileScopeId, @@ -317,11 +317,12 @@ impl<'db> GenericContext<'db> { /// list. pub(crate) fn from_base_classes( db: &'db dyn Db, + definition: Definition<'db>, bases: impl Iterator>, ) -> Option { let mut variables = FxOrderSet::default(); for base in bases { - base.find_legacy_typevars(db, None, &mut variables); + base.find_legacy_typevars(db, Some(definition), &mut variables); } if variables.is_empty() { return None; @@ -413,6 +414,15 @@ impl<'db> GenericContext<'db> { .all(|bound_typevar| other_variables.contains_key(&bound_typevar)) } + pub(crate) fn binds_named_typevar( + self, + db: &'db dyn Db, + name: &'db ast::name::Name, + ) -> Option> { + self.variables(db) + .find(|self_bound_typevar| self_bound_typevar.typevar(db).name(db) == name) + } + pub(crate) fn binds_typevar( self, db: &'db dyn Db, diff --git a/crates/ty_python_semantic/src/types/infer/builder.rs b/crates/ty_python_semantic/src/types/infer/builder.rs index fcca0f18e5..bb1898f905 100644 --- a/crates/ty_python_semantic/src/types/infer/builder.rs +++ b/crates/ty_python_semantic/src/types/infer/builder.rs @@ -53,31 +53,29 @@ use crate::types::diagnostic::{ CALL_NON_CALLABLE, CONFLICTING_DECLARATIONS, CONFLICTING_METACLASS, CYCLIC_CLASS_DEFINITION, DIVISION_BY_ZERO, DUPLICATE_KW_ONLY, INCONSISTENT_MRO, INVALID_ARGUMENT_TYPE, INVALID_ASSIGNMENT, INVALID_ATTRIBUTE_ACCESS, INVALID_BASE, INVALID_DECLARATION, - INVALID_GENERIC_CLASS, INVALID_KEY, INVALID_LEGACY_TYPE_VARIABLE, INVALID_NAMED_TUPLE, - INVALID_PARAMETER_DEFAULT, INVALID_TYPE_FORM, INVALID_TYPE_GUARD_CALL, - INVALID_TYPE_VARIABLE_CONSTRAINTS, IncompatibleBases, NON_SUBSCRIPTABLE, - POSSIBLY_MISSING_IMPLICIT_CALL, POSSIBLY_MISSING_IMPORT, UNDEFINED_REVEAL, - UNRESOLVED_ATTRIBUTE, UNRESOLVED_GLOBAL, UNRESOLVED_IMPORT, UNRESOLVED_REFERENCE, - UNSUPPORTED_OPERATOR, USELESS_OVERLOAD_BODY, report_bad_dunder_set_call, - report_cannot_pop_required_field_on_typed_dict, report_implicit_return_type, - report_instance_layout_conflict, report_invalid_assignment, - report_invalid_attribute_assignment, report_invalid_generator_function_return_type, - report_invalid_key_on_typed_dict, report_invalid_return_type, - report_namedtuple_field_without_default_after_field_with_default, - report_possibly_missing_attribute, -}; -use crate::types::diagnostic::{ - INVALID_METACLASS, INVALID_OVERLOAD, INVALID_PROTOCOL, SUBCLASS_OF_FINAL_CLASS, + INVALID_GENERIC_CLASS, INVALID_KEY, INVALID_LEGACY_TYPE_VARIABLE, INVALID_METACLASS, + INVALID_NAMED_TUPLE, INVALID_OVERLOAD, INVALID_PARAMETER_DEFAULT, INVALID_PROTOCOL, + INVALID_TYPE_FORM, INVALID_TYPE_GUARD_CALL, INVALID_TYPE_VARIABLE_CONSTRAINTS, + IncompatibleBases, NON_SUBSCRIPTABLE, POSSIBLY_MISSING_IMPLICIT_CALL, POSSIBLY_MISSING_IMPORT, + SUBCLASS_OF_FINAL_CLASS, UNDEFINED_REVEAL, UNRESOLVED_ATTRIBUTE, UNRESOLVED_GLOBAL, + UNRESOLVED_IMPORT, UNRESOLVED_REFERENCE, UNSUPPORTED_OPERATOR, USELESS_OVERLOAD_BODY, hint_if_stdlib_submodule_exists_on_other_versions, report_attempted_protocol_instantiation, - report_duplicate_bases, report_index_out_of_bounds, report_invalid_exception_caught, + report_bad_dunder_set_call, report_cannot_pop_required_field_on_typed_dict, + report_duplicate_bases, report_implicit_return_type, report_index_out_of_bounds, + report_instance_layout_conflict, report_invalid_assignment, + report_invalid_attribute_assignment, report_invalid_exception_caught, report_invalid_exception_cause, report_invalid_exception_raised, - report_invalid_or_unsupported_base, report_invalid_type_checking_constant, - report_non_subscriptable, report_possibly_unresolved_reference, report_slice_step_size_zero, + report_invalid_generator_function_return_type, report_invalid_key_on_typed_dict, + report_invalid_or_unsupported_base, report_invalid_return_type, + report_invalid_type_checking_constant, + report_namedtuple_field_without_default_after_field_with_default, report_non_subscriptable, + report_possibly_missing_attribute, report_possibly_unresolved_reference, + report_rebound_typevar, report_slice_step_size_zero, }; use crate::types::function::{ FunctionDecorators, FunctionLiteral, FunctionType, KnownFunction, OverloadLiteral, }; -use crate::types::generics::{GenericContext, bind_typevar}; +use crate::types::generics::{GenericContext, bind_typevar, enclosing_generic_contexts}; use crate::types::generics::{LegacyGenericBase, SpecializationBuilder}; use crate::types::infer::nearest_enclosing_function; use crate::types::instance::SliceLiteral; @@ -838,6 +836,8 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { } } + // (6) If the class is generic, verify that its generic context does not violate any of + // the typevar scoping rules. if let (Some(legacy), Some(inherited)) = ( class.legacy_generic_context(self.db()), class.inherited_legacy_generic_context(self.db()), @@ -854,7 +854,29 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { } } - // (6) Check that a dataclass does not have more than one `KW_ONLY`. + let scope = class.body_scope(self.db()).scope(self.db()); + if self.context.is_lint_enabled(&INVALID_GENERIC_CLASS) + && let Some(parent) = scope.parent() + { + for self_typevar in class.typevars_referenced_in_definition(self.db()) { + let self_typevar_name = self_typevar.typevar(self.db()).name(self.db()); + for enclosing in enclosing_generic_contexts(self.db(), self.index, parent) { + if let Some(other_typevar) = + enclosing.binds_named_typevar(self.db(), self_typevar_name) + { + report_rebound_typevar( + &self.context, + self_typevar_name, + class, + class_node, + other_typevar, + ); + } + } + } + } + + // (7) Check that a dataclass does not have more than one `KW_ONLY`. if let Some(field_policy @ CodeGeneratorKind::DataclassLike(_)) = CodeGeneratorKind::from_class(self.db(), class) {