Skip to content

Commit e6842c3

Browse files
committed
Simplify
1 parent 6118765 commit e6842c3

File tree

2 files changed

+31
-90
lines changed

2 files changed

+31
-90
lines changed

crates/ty_python_semantic/resources/mdtest/liskov.md

Lines changed: 3 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -183,9 +183,9 @@ class DC7(DC6):
183183

184184
### Protocol implementations
185185

186-
Regular class-body assignments can implement protocol instance attributes. They can also provide the
187-
value for protocol `ClassVar` attributes by inheriting the `ClassVar` declaration from the protocol
188-
member:
186+
Regular class-body assignments can implement protocol instance attributes. The `ClassVar` case below
187+
uses the same rule as normal classes: an unannotated class-body assignment over an inherited
188+
`ClassVar` provides a value while preserving the inherited declaration.
189189

190190
```py
191191
from typing import ClassVar, Protocol
@@ -210,26 +210,6 @@ class ProtocolWithClassVarImpl(ProtocolWithClassVar):
210210
z = 0
211211
```
212212

213-
### Imported unannotated assignments
214-
215-
Imported unannotated class attributes are not pure class variables either:
216-
217-
`module.py`:
218-
219-
```py
220-
class Base:
221-
attr = 1
222-
```
223-
224-
`main.py`:
225-
226-
```py
227-
from module import Base
228-
229-
class Subclass(Base):
230-
attr: int
231-
```
232-
233213
## Method return types
234214

235215
It is fine for a subclass method to return a subtype of the return type of the method it overrides:

crates/ty_python_semantic/src/types/overrides.rs

Lines changed: 28 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
//!
44
//! [Liskov Substitution Principle]: https://en.wikipedia.org/wiki/Liskov_substitution_principle
55
6+
use std::cell::OnceCell;
7+
68
use bitflags::bitflags;
79
use ruff_db::diagnostic::Annotation;
810
use ruff_python_ast::name::Name;
@@ -325,7 +327,7 @@ fn check_class_declaration<'db>(
325327
let mut overridden_final_method = None;
326328
let mut overridden_final_variable: Option<(ClassType<'db>, Option<Definition<'db>>)> = None;
327329
let is_private_member = is_mangled_private(member.name.as_str());
328-
let mut subclass_variable_kind = VariableKindCache::default();
330+
let subclass_variable_kind: OnceCell<Option<VariableKind>> = OnceCell::new();
329331

330332
// Track the first superclass that defines this method (the "immediate parent" for this method).
331333
// We need this to check if parent itself already has an LSP violation with an ancestor.
@@ -470,8 +472,6 @@ fn check_class_declaration<'db>(
470472
continue;
471473
}
472474

473-
let superclass_definition =
474-
superclass_symbol_id.and_then(|id| symbol_definition(db, superclass_scope, id));
475475
let superclass_variable_kind = superclass_variable_kind(
476476
db,
477477
superclass.own_class_member(db, None, &member.name).inner,
@@ -485,22 +485,21 @@ fn check_class_declaration<'db>(
485485
}
486486

487487
if let Some(superclass_variable_kind) = superclass_variable_kind {
488-
let subclass_variable_kind = subclass_variable_kind.get_or_compute(|| {
488+
let subclass_kind = *subclass_variable_kind.get_or_init(|| {
489489
variable_kind(
490490
db,
491491
class.own_class_member(db, None, &member.name).inner,
492492
subclass_instance_member,
493493
)
494494
});
495495

496-
if let Some(invalid_override) = invalid_classvar_instance_override(
497-
subclass_variable_kind,
498-
Some(superclass_variable_kind),
499-
) {
496+
if let Some(subclass_kind) = subclass_kind
497+
&& subclass_kind != superclass_variable_kind
498+
{
500499
// An unannotated class-body assignment can inherit an overridden `ClassVar`
501500
// declaration instead of introducing a conflicting instance variable.
502-
if invalid_override.subclass_kind == VariableKind::Instance
503-
&& invalid_override.superclass_kind == VariableKind::Class
501+
if subclass_kind == VariableKind::Instance
502+
&& superclass_variable_kind == VariableKind::Class
504503
&& first_reachable_definition
505504
.kind(db)
506505
.is_unannotated_assignment()
@@ -512,18 +511,21 @@ fn check_class_declaration<'db>(
512511
immediate_parent_variable_kind
513512
&& immediate_parent != superclass
514513
&& immediate_parent.is_subclass_of(db, superclass)
515-
&& immediate_parent_kind != invalid_override.superclass_kind
514+
&& immediate_parent_kind != superclass_variable_kind
516515
{
517516
continue;
518517
}
519518

519+
let superclass_definition = superclass_symbol_id
520+
.and_then(|id| symbol_definition(db, superclass_scope, id));
520521
report_invalid_attribute_override(
521522
context,
522523
&member.name,
523524
*first_reachable_definition,
524525
superclass,
525526
superclass_definition,
526-
invalid_override,
527+
subclass_kind,
528+
superclass_variable_kind,
527529
);
528530
liskov_diagnostic_emitted = true;
529531
continue;
@@ -652,42 +654,17 @@ fn check_class_declaration<'db>(
652654
}
653655
}
654656

655-
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
656-
enum VariableKindCache {
657-
#[default]
658-
Uncomputed,
659-
NotVariable,
660-
Variable(VariableKind),
661-
}
662-
663-
impl VariableKindCache {
664-
fn get_or_compute(
665-
&mut self,
666-
compute: impl FnOnce() -> Option<VariableKind>,
667-
) -> Option<VariableKind> {
668-
match *self {
669-
Self::NotVariable => None,
670-
Self::Variable(variable_kind) => Some(variable_kind),
671-
Self::Uncomputed => {
672-
if let Some(variable_kind) = compute() {
673-
*self = Self::Variable(variable_kind);
674-
Some(variable_kind)
675-
} else {
676-
*self = Self::NotVariable;
677-
None
678-
}
679-
}
680-
}
681-
}
682-
}
683-
657+
/// Whether an attribute declaration is a class variable or an instance variable.
684658
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
685659
enum VariableKind {
660+
/// A variable annotated with `ClassVar`.
686661
Class,
662+
/// An instance variable, including an unannotated class-body assignment.
687663
Instance,
688664
}
689665

690666
impl VariableKind {
667+
/// Returns the wording used for this variable kind in diagnostics.
691668
const fn description(self) -> &'static str {
692669
match self {
693670
VariableKind::Class => "class variable",
@@ -696,24 +673,7 @@ impl VariableKind {
696673
}
697674
}
698675

699-
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
700-
struct InvalidAttributeOverride {
701-
subclass_kind: VariableKind,
702-
superclass_kind: VariableKind,
703-
}
704-
705-
fn invalid_classvar_instance_override(
706-
subclass_kind: Option<VariableKind>,
707-
superclass_kind: Option<VariableKind>,
708-
) -> Option<InvalidAttributeOverride> {
709-
let subclass_kind = subclass_kind?;
710-
let superclass_kind = superclass_kind?;
711-
(subclass_kind != superclass_kind).then_some(InvalidAttributeOverride {
712-
subclass_kind,
713-
superclass_kind,
714-
})
715-
}
716-
676+
/// Returns the variable kind for a superclass member, excluding final attributes.
717677
fn superclass_variable_kind<'db>(
718678
db: &'db dyn Db,
719679
class_member: PlaceAndQualifiers<'db>,
@@ -726,6 +686,7 @@ fn superclass_variable_kind<'db>(
726686
variable_kind(db, class_member, instance_member)
727687
}
728688

689+
/// Returns the variable kind for an attribute if it should participate in `ClassVar` override checks.
729690
fn variable_kind<'db>(
730691
db: &'db dyn Db,
731692
class_member: PlaceAndQualifiers<'db>,
@@ -739,11 +700,7 @@ fn variable_kind<'db>(
739700
return None;
740701
}
741702

742-
if class_member.qualifiers.contains(TypeQualifiers::CLASS_VAR)
743-
|| instance_member
744-
.qualifiers
745-
.contains(TypeQualifiers::CLASS_VAR)
746-
{
703+
if class_member.is_class_var() || instance_member.is_class_var() {
747704
return Some(VariableKind::Class);
748705
}
749706

@@ -760,6 +717,7 @@ fn variable_kind<'db>(
760717
Some(VariableKind::Instance)
761718
}
762719

720+
/// Returns the definition to use as the secondary annotation for an overridden symbol.
763721
fn symbol_definition<'db>(
764722
db: &'db dyn Db,
765723
scope: ScopeId<'db>,
@@ -775,6 +733,7 @@ fn symbol_definition<'db>(
775733
})
776734
}
777735

736+
/// Returns `true` if a type represents a variable-like attribute.
778737
fn is_variable_like_type<'db>(db: &'db dyn Db, ty: Type<'db>) -> bool {
779738
match ty {
780739
Type::FunctionLiteral(_)
@@ -794,13 +753,15 @@ fn is_variable_like_type<'db>(db: &'db dyn Db, ty: Type<'db>) -> bool {
794753
}
795754
}
796755

756+
/// Reports an invalid override between a class variable and an instance variable.
797757
fn report_invalid_attribute_override<'db>(
798758
context: &InferContext<'db, '_>,
799759
member: &Name,
800760
subclass_definition: Definition<'db>,
801761
superclass: ClassType<'db>,
802762
superclass_definition: Option<Definition<'db>>,
803-
invalid_override: InvalidAttributeOverride,
763+
subclass_kind: VariableKind,
764+
superclass_kind: VariableKind,
804765
) {
805766
let db = context.db();
806767

@@ -813,8 +774,8 @@ fn report_invalid_attribute_override<'db>(
813774

814775
let superclass_name = superclass.name(db);
815776
let superclass_member = format!("{superclass_name}.{member}");
816-
let subclass_kind = invalid_override.subclass_kind.description();
817-
let superclass_kind = invalid_override.superclass_kind.description();
777+
let subclass_kind = subclass_kind.description();
778+
let superclass_kind = superclass_kind.description();
818779

819780
let mut diagnostic =
820781
builder.into_diagnostic(format_args!("Invalid override of attribute `{member}`"));

0 commit comments

Comments
 (0)