Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 18 additions & 2 deletions pyrefly/lib/alt/class/class_field.rs
Original file line number Diff line number Diff line change
Expand Up @@ -718,6 +718,18 @@ impl ClassField {
matches!(&self.0, ClassFieldInner::InstanceAttribute { .. })
}

/// Returns true if this field can have the `@override` decorator applied to it.
pub fn can_have_override_decorator(&self) -> bool {
match &self.0 {
ClassFieldInner::InstanceAttribute { .. }
| ClassFieldInner::NestedClass { .. }
| ClassFieldInner::ClassAttribute { .. } => false,
ClassFieldInner::Property { .. }
| ClassFieldInner::Descriptor { .. }
| ClassFieldInner::Method { .. } => true,
}
}

pub fn ty(&self) -> Type {
match &self.0 {
ClassFieldInner::Property { ty, .. } => ty.clone(),
Expand Down Expand Up @@ -862,7 +874,7 @@ impl ClassField {
}
}

fn is_class_var(&self) -> bool {
pub fn is_class_var(&self) -> bool {
match &self.0 {
ClassFieldInner::Property { .. } => false,
ClassFieldInner::Descriptor { annotation, .. } => {
Expand Down Expand Up @@ -3067,7 +3079,11 @@ impl<'a, Ans: LookupAnswer> AnswersSolver<'a, Ans> {
// Check for missing @override decorator when overriding a parent attribute.
// This error is emitted when a method overrides a parent but doesn't have @override.
// Since this error has Severity::Ignore by default, it won't be shown unless enabled.
if !is_override && parent_attr_found && !parent_has_any {
if !is_override
&& parent_attr_found
&& !parent_has_any
&& class_field.can_have_override_decorator()
{
self.error(
errors,
range,
Expand Down
46 changes: 44 additions & 2 deletions pyrefly/lib/test/class_overrides.rs
Original file line number Diff line number Diff line change
Expand Up @@ -912,7 +912,7 @@ def f(d: D):

// Tests for MissingOverrideDecorator (strict override enforcement)
testcase!(
test_missing_override_decorator,
test_missing_override_decorator_method,
TestEnv::new().enable_missing_override_decorator_error(),
r#"
class Base:
Expand Down Expand Up @@ -1000,7 +1000,7 @@ class Base:
x: int

class Derived(Base):
x: int # E: Class member `Derived.x` overrides a member in a parent class but is missing an `@override` decorator
x: int
"#,
);

Expand Down Expand Up @@ -1043,3 +1043,45 @@ class Derived(Base):
def __str__(self) -> str: ... # E: overrides a member in a parent class but is missing an `@override` decorator
"#,
);

testcase!(
test_missing_override_decorator_instance_attribute,
TestEnv::new().enable_missing_override_decorator_error(),
r#"
class A:
def __init__(self):
self.a = 1

class B(A):
def __init__(self):
self.a = 2 # OK - instance attribute, not a method override
"#,
);

testcase!(
test_missing_override_decorator_nested_class,
TestEnv::new().enable_missing_override_decorator_error(),
r#"
# Nested classes cannot have the @override decorator applied to them.
class A:
class C: pass

class B(A):
class C(A.C): pass # OK - nested class, @override cannot be applied
"#,
);

testcase!(
test_missing_override_decorator_classvar,
TestEnv::new().enable_missing_override_decorator_error(),
r#"
from typing import ClassVar

# ClassVars cannot have the @override decorator applied to them.
class A:
x: ClassVar[int]

class B(A):
x: ClassVar[int] # OK - ClassVar, @override cannot be applied
"#,
);
Loading