[ty] Extend `invalid-explicit-override` to also cover properties decorated with `@override` that do not override anything (#21756)

This commit is contained in:
Alex Waygood 2025-12-03 11:27:47 +00:00 committed by GitHub
parent 92c5f62ec0
commit 5756b3809c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 415 additions and 252 deletions

View File

@ -19,54 +19,74 @@ class A:
class Parent:
def foo(self): ...
@property
def my_property1(self) -> int: ...
@property
def my_property2(self) -> int: ...
baz = None
@classmethod
def class_method1(cls) -> int: ...
@staticmethod
def static_method1() -> int: ...
@classmethod
def class_method2(cls) -> int: ...
@staticmethod
def static_method2() -> int: ...
@lossy_decorator
def decorated_1(self): ...
@lossy_decorator
def decorated_2(self): ...
@lossy_decorator
def decorated_3(self): ...
class Child(Parent):
@override
def foo(self): ... # fine: overrides `Parent.foo`
@property
@override
def my_property1(self) -> int: ... # fine: overrides `Parent.my_property1`
@override
@property
def my_property2(self) -> int: ... # fine: overrides `Parent.my_property2`
@override
def baz(self): ... # fine: overrides `Parent.baz`
@classmethod
@override
def class_method1(cls) -> int: ... # fine: overrides `Parent.class_method1`
@staticmethod
@override
def static_method1() -> int: ... # fine: overrides `Parent.static_method1`
@override
@classmethod
def class_method2(cls) -> int: ... # fine: overrides `Parent.class_method2`
@override
@staticmethod
def static_method2() -> int: ... # fine: overrides `Parent.static_method2`
@override
def decorated_1(self): ... # fine: overrides `Parent.decorated_1`
@override
@lossy_decorator
def decorated_2(self): ... # fine: overrides `Parent.decorated_2`
@lossy_decorator
@override
def decorated_3(self): ... # fine: overrides `Parent.decorated_3`
@ -76,28 +96,37 @@ class OtherChild(Parent): ...
class Grandchild(OtherChild):
@override
def foo(self): ... # fine: overrides `Parent.foo`
@override
@property
def bar(self) -> int: ... # fine: overrides `Parent.bar`
def my_property1(self) -> int: ... # fine: overrides `Parent.my_property1`
@override
def baz(self): ... # fine: overrides `Parent.baz`
@classmethod
@override
def class_method1(cls) -> int: ... # fine: overrides `Parent.class_method1`
@staticmethod
@override
def static_method1() -> int: ... # fine: overrides `Parent.static_method1`
@override
@classmethod
def class_method2(cls) -> int: ... # fine: overrides `Parent.class_method2`
@override
@staticmethod
def static_method2() -> int: ... # fine: overrides `Parent.static_method2`
@override
def decorated_1(self): ... # fine: overrides `Parent.decorated_1`
@override
@lossy_decorator
def decorated_2(self): ... # fine: overrides `Parent.decorated_2`
@lossy_decorator
@override
def decorated_3(self): ... # fine: overrides `Parent.decorated_3`
@ -105,27 +134,41 @@ class Grandchild(OtherChild):
class Invalid:
@override
def ___reprrr__(self): ... # error: [invalid-explicit-override]
@override
@classmethod
def foo(self): ... # error: [invalid-explicit-override]
@classmethod
@override
def bar(self): ... # error: [invalid-explicit-override]
@staticmethod
@override
def baz(): ... # error: [invalid-explicit-override]
@override
@staticmethod
def eggs(): ... # error: [invalid-explicit-override]
@property
@override
def bad_property1(self) -> int: ... # TODO: should emit `invalid-explicit-override` here
def bad_property1(self) -> int: ... # error: [invalid-explicit-override]
@override
@property
def bad_property2(self) -> int: ... # TODO: should emit `invalid-explicit-override` here
def bad_property2(self) -> int: ... # error: [invalid-explicit-override]
@property
@override
def bad_settable_property(self) -> int: ... # error: [invalid-explicit-override]
@bad_settable_property.setter
def bad_settable_property(self, x: int) -> None: ...
@lossy_decorator
@override
def lossy(self): ... # TODO: should emit `invalid-explicit-override` here
@override
@lossy_decorator
def lossy2(self): ... # TODO: should emit `invalid-explicit-override` here
@ -136,11 +179,14 @@ class LiskovViolatingButNotOverrideViolating(Parent):
@override
@property
def foo(self) -> int: ...
@override
def my_property1(self) -> int: ...
@staticmethod
@override
def class_method1() -> int: ...
@classmethod
@override
def static_method1(cls) -> int: ...

View File

@ -22,175 +22,221 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/override.md
8 |
9 | class Parent:
10 | def foo(self): ...
11 | @property
12 | def my_property1(self) -> int: ...
13 | @property
14 | def my_property2(self) -> int: ...
15 | baz = None
16 | @classmethod
17 | def class_method1(cls) -> int: ...
18 | @staticmethod
19 | def static_method1() -> int: ...
11 |
12 | @property
13 | def my_property1(self) -> int: ...
14 |
15 | @property
16 | def my_property2(self) -> int: ...
17 |
18 | baz = None
19 |
20 | @classmethod
21 | def class_method2(cls) -> int: ...
22 | @staticmethod
23 | def static_method2() -> int: ...
24 | @lossy_decorator
25 | def decorated_1(self): ...
26 | @lossy_decorator
27 | def decorated_2(self): ...
28 | @lossy_decorator
29 | def decorated_3(self): ...
30 |
31 | class Child(Parent):
32 | @override
33 | def foo(self): ... # fine: overrides `Parent.foo`
34 | @property
35 | @override
36 | def my_property1(self) -> int: ... # fine: overrides `Parent.my_property1`
37 | @override
38 | @property
39 | def my_property2(self) -> int: ... # fine: overrides `Parent.my_property2`
40 | @override
41 | def baz(self): ... # fine: overrides `Parent.baz`
42 | @classmethod
43 | @override
44 | def class_method1(cls) -> int: ... # fine: overrides `Parent.class_method1`
45 | @staticmethod
21 | def class_method1(cls) -> int: ...
22 |
23 | @staticmethod
24 | def static_method1() -> int: ...
25 |
26 | @classmethod
27 | def class_method2(cls) -> int: ...
28 |
29 | @staticmethod
30 | def static_method2() -> int: ...
31 |
32 | @lossy_decorator
33 | def decorated_1(self): ...
34 |
35 | @lossy_decorator
36 | def decorated_2(self): ...
37 |
38 | @lossy_decorator
39 | def decorated_3(self): ...
40 |
41 | class Child(Parent):
42 | @override
43 | def foo(self): ... # fine: overrides `Parent.foo`
44 |
45 | @property
46 | @override
47 | def static_method1() -> int: ... # fine: overrides `Parent.static_method1`
48 | @override
49 | @classmethod
50 | def class_method2(cls) -> int: ... # fine: overrides `Parent.class_method2`
51 | @override
52 | @staticmethod
53 | def static_method2() -> int: ... # fine: overrides `Parent.static_method2`
54 | @override
55 | def decorated_1(self): ... # fine: overrides `Parent.decorated_1`
56 | @override
57 | @lossy_decorator
58 | def decorated_2(self): ... # fine: overrides `Parent.decorated_2`
59 | @lossy_decorator
60 | @override
61 | def decorated_3(self): ... # fine: overrides `Parent.decorated_3`
62 |
63 | class OtherChild(Parent): ...
64 |
65 | class Grandchild(OtherChild):
66 | @override
67 | def foo(self): ... # fine: overrides `Parent.foo`
47 | def my_property1(self) -> int: ... # fine: overrides `Parent.my_property1`
48 |
49 | @override
50 | @property
51 | def my_property2(self) -> int: ... # fine: overrides `Parent.my_property2`
52 |
53 | @override
54 | def baz(self): ... # fine: overrides `Parent.baz`
55 |
56 | @classmethod
57 | @override
58 | def class_method1(cls) -> int: ... # fine: overrides `Parent.class_method1`
59 |
60 | @staticmethod
61 | @override
62 | def static_method1() -> int: ... # fine: overrides `Parent.static_method1`
63 |
64 | @override
65 | @classmethod
66 | def class_method2(cls) -> int: ... # fine: overrides `Parent.class_method2`
67 |
68 | @override
69 | @property
70 | def bar(self) -> int: ... # fine: overrides `Parent.bar`
71 | @override
72 | def baz(self): ... # fine: overrides `Parent.baz`
73 | @classmethod
74 | @override
75 | def class_method1(cls) -> int: ... # fine: overrides `Parent.class_method1`
76 | @staticmethod
77 | @override
78 | def static_method1() -> int: ... # fine: overrides `Parent.static_method1`
79 | @override
80 | @classmethod
81 | def class_method2(cls) -> int: ... # fine: overrides `Parent.class_method2`
82 | @override
83 | @staticmethod
84 | def static_method2() -> int: ... # fine: overrides `Parent.static_method2`
85 | @override
86 | def decorated_1(self): ... # fine: overrides `Parent.decorated_1`
87 | @override
88 | @lossy_decorator
89 | def decorated_2(self): ... # fine: overrides `Parent.decorated_2`
90 | @lossy_decorator
91 | @override
92 | def decorated_3(self): ... # fine: overrides `Parent.decorated_3`
93 |
94 | class Invalid:
95 | @override
96 | def ___reprrr__(self): ... # error: [invalid-explicit-override]
69 | @staticmethod
70 | def static_method2() -> int: ... # fine: overrides `Parent.static_method2`
71 |
72 | @override
73 | def decorated_1(self): ... # fine: overrides `Parent.decorated_1`
74 |
75 | @override
76 | @lossy_decorator
77 | def decorated_2(self): ... # fine: overrides `Parent.decorated_2`
78 |
79 | @lossy_decorator
80 | @override
81 | def decorated_3(self): ... # fine: overrides `Parent.decorated_3`
82 |
83 | class OtherChild(Parent): ...
84 |
85 | class Grandchild(OtherChild):
86 | @override
87 | def foo(self): ... # fine: overrides `Parent.foo`
88 |
89 | @override
90 | @property
91 | def my_property1(self) -> int: ... # fine: overrides `Parent.my_property1`
92 |
93 | @override
94 | def baz(self): ... # fine: overrides `Parent.baz`
95 |
96 | @classmethod
97 | @override
98 | @classmethod
99 | def foo(self): ... # error: [invalid-explicit-override]
100 | @classmethod
98 | def class_method1(cls) -> int: ... # fine: overrides `Parent.class_method1`
99 |
100 | @staticmethod
101 | @override
102 | def bar(self): ... # error: [invalid-explicit-override]
103 | @staticmethod
102 | def static_method1() -> int: ... # fine: overrides `Parent.static_method1`
103 |
104 | @override
105 | def baz(): ... # error: [invalid-explicit-override]
106 | @override
107 | @staticmethod
108 | def eggs(): ... # error: [invalid-explicit-override]
109 | @property
110 | @override
111 | def bad_property1(self) -> int: ... # TODO: should emit `invalid-explicit-override` here
105 | @classmethod
106 | def class_method2(cls) -> int: ... # fine: overrides `Parent.class_method2`
107 |
108 | @override
109 | @staticmethod
110 | def static_method2() -> int: ... # fine: overrides `Parent.static_method2`
111 |
112 | @override
113 | @property
114 | def bad_property2(self) -> int: ... # TODO: should emit `invalid-explicit-override` here
115 | @lossy_decorator
116 | @override
117 | def lossy(self): ... # TODO: should emit `invalid-explicit-override` here
118 | @override
113 | def decorated_1(self): ... # fine: overrides `Parent.decorated_1`
114 |
115 | @override
116 | @lossy_decorator
117 | def decorated_2(self): ... # fine: overrides `Parent.decorated_2`
118 |
119 | @lossy_decorator
120 | def lossy2(self): ... # TODO: should emit `invalid-explicit-override` here
121 |
122 | # TODO: all overrides in this class should cause us to emit *Liskov* violations,
123 | # but not `@override` violations
124 | class LiskovViolatingButNotOverrideViolating(Parent):
125 | @override
126 | @property
127 | def foo(self) -> int: ...
128 | @override
129 | def my_property1(self) -> int: ...
130 | @staticmethod
131 | @override
132 | def class_method1() -> int: ...
133 | @classmethod
134 | @override
135 | def static_method1(cls) -> int: ...
136 |
137 | # Diagnostic edge case: `override` is very far away from the method definition in the source code:
120 | @override
121 | def decorated_3(self): ... # fine: overrides `Parent.decorated_3`
122 |
123 | class Invalid:
124 | @override
125 | def ___reprrr__(self): ... # error: [invalid-explicit-override]
126 |
127 | @override
128 | @classmethod
129 | def foo(self): ... # error: [invalid-explicit-override]
130 |
131 | @classmethod
132 | @override
133 | def bar(self): ... # error: [invalid-explicit-override]
134 |
135 | @staticmethod
136 | @override
137 | def baz(): ... # error: [invalid-explicit-override]
138 |
139 | T = TypeVar("T")
140 |
141 | def identity(x: T) -> T: ...
139 | @override
140 | @staticmethod
141 | def eggs(): ... # error: [invalid-explicit-override]
142 |
143 | class Foo:
143 | @property
144 | @override
145 | @identity
146 | @identity
147 | @identity
148 | @identity
149 | @identity
150 | @identity
151 | @identity
152 | @identity
153 | @identity
154 | @identity
155 | @identity
156 | @identity
157 | @identity
158 | @identity
159 | @identity
160 | @identity
161 | @identity
162 | @identity
163 | def bar(self): ... # error: [invalid-explicit-override]
145 | def bad_property1(self) -> int: ... # error: [invalid-explicit-override]
146 |
147 | @override
148 | @property
149 | def bad_property2(self) -> int: ... # error: [invalid-explicit-override]
150 |
151 | @property
152 | @override
153 | def bad_settable_property(self) -> int: ... # error: [invalid-explicit-override]
154 | @bad_settable_property.setter
155 | def bad_settable_property(self, x: int) -> None: ...
156 |
157 | @lossy_decorator
158 | @override
159 | def lossy(self): ... # TODO: should emit `invalid-explicit-override` here
160 |
161 | @override
162 | @lossy_decorator
163 | def lossy2(self): ... # TODO: should emit `invalid-explicit-override` here
164 |
165 | # TODO: all overrides in this class should cause us to emit *Liskov* violations,
166 | # but not `@override` violations
167 | class LiskovViolatingButNotOverrideViolating(Parent):
168 | @override
169 | @property
170 | def foo(self) -> int: ...
171 |
172 | @override
173 | def my_property1(self) -> int: ...
174 |
175 | @staticmethod
176 | @override
177 | def class_method1() -> int: ...
178 |
179 | @classmethod
180 | @override
181 | def static_method1(cls) -> int: ...
182 |
183 | # Diagnostic edge case: `override` is very far away from the method definition in the source code:
184 |
185 | T = TypeVar("T")
186 |
187 | def identity(x: T) -> T: ...
188 |
189 | class Foo:
190 | @override
191 | @identity
192 | @identity
193 | @identity
194 | @identity
195 | @identity
196 | @identity
197 | @identity
198 | @identity
199 | @identity
200 | @identity
201 | @identity
202 | @identity
203 | @identity
204 | @identity
205 | @identity
206 | @identity
207 | @identity
208 | @identity
209 | def bar(self): ... # error: [invalid-explicit-override]
```
# Diagnostics
```
error[invalid-explicit-override]: Method `___reprrr__` is decorated with `@override` but does not override anything
--> src/mdtest_snippet.pyi:95:5
|
94 | class Invalid:
95 | @override
| ---------
96 | def ___reprrr__(self): ... # error: [invalid-explicit-override]
| ^^^^^^^^^^^
97 | @override
98 | @classmethod
|
--> src/mdtest_snippet.pyi:124:5
|
123 | class Invalid:
124 | @override
| ---------
125 | def ___reprrr__(self): ... # error: [invalid-explicit-override]
| ^^^^^^^^^^^
126 |
127 | @override
|
info: No `___reprrr__` definitions were found on any superclasses of `Invalid`
info: rule `invalid-explicit-override` is enabled by default
@ -198,17 +244,17 @@ info: rule `invalid-explicit-override` is enabled by default
```
error[invalid-explicit-override]: Method `foo` is decorated with `@override` but does not override anything
--> src/mdtest_snippet.pyi:97:5
--> src/mdtest_snippet.pyi:127:5
|
95 | @override
96 | def ___reprrr__(self): ... # error: [invalid-explicit-override]
97 | @override
125 | def ___reprrr__(self): ... # error: [invalid-explicit-override]
126 |
127 | @override
| ---------
98 | @classmethod
99 | def foo(self): ... # error: [invalid-explicit-override]
128 | @classmethod
129 | def foo(self): ... # error: [invalid-explicit-override]
| ^^^
100 | @classmethod
101 | @override
130 |
131 | @classmethod
|
info: No `foo` definitions were found on any superclasses of `Invalid`
info: rule `invalid-explicit-override` is enabled by default
@ -217,16 +263,15 @@ info: rule `invalid-explicit-override` is enabled by default
```
error[invalid-explicit-override]: Method `bar` is decorated with `@override` but does not override anything
--> src/mdtest_snippet.pyi:101:5
--> src/mdtest_snippet.pyi:132:5
|
99 | def foo(self): ... # error: [invalid-explicit-override]
100 | @classmethod
101 | @override
131 | @classmethod
132 | @override
| ---------
102 | def bar(self): ... # error: [invalid-explicit-override]
133 | def bar(self): ... # error: [invalid-explicit-override]
| ^^^
103 | @staticmethod
104 | @override
134 |
135 | @staticmethod
|
info: No `bar` definitions were found on any superclasses of `Invalid`
info: rule `invalid-explicit-override` is enabled by default
@ -235,16 +280,15 @@ info: rule `invalid-explicit-override` is enabled by default
```
error[invalid-explicit-override]: Method `baz` is decorated with `@override` but does not override anything
--> src/mdtest_snippet.pyi:104:5
--> src/mdtest_snippet.pyi:136:5
|
102 | def bar(self): ... # error: [invalid-explicit-override]
103 | @staticmethod
104 | @override
135 | @staticmethod
136 | @override
| ---------
105 | def baz(): ... # error: [invalid-explicit-override]
137 | def baz(): ... # error: [invalid-explicit-override]
| ^^^
106 | @override
107 | @staticmethod
138 |
139 | @override
|
info: No `baz` definitions were found on any superclasses of `Invalid`
info: rule `invalid-explicit-override` is enabled by default
@ -253,17 +297,17 @@ info: rule `invalid-explicit-override` is enabled by default
```
error[invalid-explicit-override]: Method `eggs` is decorated with `@override` but does not override anything
--> src/mdtest_snippet.pyi:106:5
--> src/mdtest_snippet.pyi:139:5
|
104 | @override
105 | def baz(): ... # error: [invalid-explicit-override]
106 | @override
137 | def baz(): ... # error: [invalid-explicit-override]
138 |
139 | @override
| ---------
107 | @staticmethod
108 | def eggs(): ... # error: [invalid-explicit-override]
140 | @staticmethod
141 | def eggs(): ... # error: [invalid-explicit-override]
| ^^^^
109 | @property
110 | @override
142 |
143 | @property
|
info: No `eggs` definitions were found on any superclasses of `Invalid`
info: rule `invalid-explicit-override` is enabled by default
@ -271,21 +315,74 @@ info: rule `invalid-explicit-override` is enabled by default
```
```
error[invalid-explicit-override]: Method `bar` is decorated with `@override` but does not override anything
--> src/mdtest_snippet.pyi:163:9
error[invalid-explicit-override]: Method `bad_property1` is decorated with `@override` but does not override anything
--> src/mdtest_snippet.pyi:144:5
|
161 | @identity
162 | @identity
163 | def bar(self): ... # error: [invalid-explicit-override]
| ^^^
|
::: src/mdtest_snippet.pyi:144:5
|
143 | class Foo:
143 | @property
144 | @override
| ---------
145 | @identity
146 | @identity
145 | def bad_property1(self) -> int: ... # error: [invalid-explicit-override]
| ^^^^^^^^^^^^^
146 |
147 | @override
|
info: No `bad_property1` definitions were found on any superclasses of `Invalid`
info: rule `invalid-explicit-override` is enabled by default
```
```
error[invalid-explicit-override]: Method `bad_property2` is decorated with `@override` but does not override anything
--> src/mdtest_snippet.pyi:147:5
|
145 | def bad_property1(self) -> int: ... # error: [invalid-explicit-override]
146 |
147 | @override
| ---------
148 | @property
149 | def bad_property2(self) -> int: ... # error: [invalid-explicit-override]
| ^^^^^^^^^^^^^
150 |
151 | @property
|
info: No `bad_property2` definitions were found on any superclasses of `Invalid`
info: rule `invalid-explicit-override` is enabled by default
```
```
error[invalid-explicit-override]: Method `bad_settable_property` is decorated with `@override` but does not override anything
--> src/mdtest_snippet.pyi:152:5
|
151 | @property
152 | @override
| ---------
153 | def bad_settable_property(self) -> int: ... # error: [invalid-explicit-override]
| ^^^^^^^^^^^^^^^^^^^^^
154 | @bad_settable_property.setter
155 | def bad_settable_property(self, x: int) -> None: ...
|
info: No `bad_settable_property` definitions were found on any superclasses of `Invalid`
info: rule `invalid-explicit-override` is enabled by default
```
```
error[invalid-explicit-override]: Method `bar` is decorated with `@override` but does not override anything
--> src/mdtest_snippet.pyi:209:9
|
207 | @identity
208 | @identity
209 | def bar(self): ... # error: [invalid-explicit-override]
| ^^^
|
::: src/mdtest_snippet.pyi:190:5
|
189 | class Foo:
190 | @override
| ---------
191 | @identity
192 | @identity
|
info: No `bar` definitions were found on any superclasses of `Foo`
info: rule `invalid-explicit-override` is enabled by default

View File

@ -25,7 +25,7 @@ use crate::{
report_overridden_final_method,
},
function::{FunctionDecorators, FunctionType, KnownFunction},
list_members::{MemberWithDefinition, all_members_of_scope},
list_members::{Member, MemberWithDefinition, all_members_of_scope},
},
};
@ -101,33 +101,6 @@ fn check_class_declaration<'db>(
.any(|definition| definition.kind(db).is_function_def())
}
fn extract_underlying_functions<'db>(
db: &'db dyn Db,
ty: Type<'db>,
) -> Option<smallvec::SmallVec<[FunctionType<'db>; 1]>> {
match ty {
Type::FunctionLiteral(function) => Some(smallvec::smallvec_inline![function]),
Type::BoundMethod(method) => Some(smallvec::smallvec_inline![method.function(db)]),
Type::PropertyInstance(property) => {
extract_underlying_functions(db, property.getter(db)?)
}
Type::Union(union) => {
let mut functions = smallvec::smallvec![];
for member in union.elements(db) {
if let Some(mut member_functions) = extract_underlying_functions(db, *member) {
functions.append(&mut member_functions);
}
}
if functions.is_empty() {
None
} else {
Some(functions)
}
}
_ => None,
}
}
let db = context.db();
let MemberWithDefinition { member, definition } = member;
@ -153,6 +126,8 @@ fn check_class_declaration<'db>(
if class_kind == Some(CodeGeneratorKind::NamedTuple)
&& configuration.check_prohibited_named_tuple_attrs()
&& PROHIBITED_NAMEDTUPLE_ATTRS.contains(&member.name.as_str())
// accessing `.kind()` here is fine as `definition`
// will always be a definition in the file currently being checked
&& !matches!(definition.kind(db), DefinitionKind::AnnotatedAssignment(_))
&& let Some(builder) = context.report_lint(
&INVALID_NAMED_TUPLE,
@ -331,35 +306,11 @@ fn check_class_declaration<'db>(
if !subclass_overrides_superclass_declaration
&& !has_dynamic_superclass
// accessing `.kind()` here is fine as `definition`
// will always be a definition in the file currently being checked
&& definition.kind(db).is_function_def()
&& let Type::FunctionLiteral(function) = member.ty
&& function.has_known_decorator(db, FunctionDecorators::OVERRIDE)
{
let function_literal = if context.in_stub() {
function.first_overload_or_implementation(db)
} else {
function.literal(db).last_definition(db)
};
if let Some(builder) = context.report_lint(
&INVALID_EXPLICIT_OVERRIDE,
function_literal.focus_range(db, context.module()),
) {
let mut diagnostic = builder.into_diagnostic(format_args!(
"Method `{}` is decorated with `@override` but does not override anything",
member.name
));
if let Some(decorator_span) =
function_literal.find_known_decorator_span(db, KnownFunction::Override)
{
diagnostic.annotate(Annotation::secondary(decorator_span));
}
diagnostic.info(format_args!(
"No `{member}` definitions were found on any superclasses of `{class}`",
member = &member.name,
class = class.name(db)
));
}
check_explicit_overrides(context, member, class);
}
if let Some((superclass, superclass_method)) = overridden_final_method {
@ -434,3 +385,72 @@ impl OverrideRulesConfig {
self.contains(OverrideRulesConfig::PROHIBITED_NAMED_TUPLE_ATTR)
}
}
fn check_explicit_overrides<'db>(
context: &InferContext<'db, '_>,
member: &Member<'db>,
class: ClassType<'db>,
) {
let db = context.db();
let underlying_functions = extract_underlying_functions(db, member.ty);
let Some(functions) = underlying_functions else {
return;
};
if !functions
.iter()
.any(|function| function.has_known_decorator(db, FunctionDecorators::OVERRIDE))
{
return;
}
let function_literal = if context.in_stub() {
functions[0].first_overload_or_implementation(db)
} else {
functions[0].literal(db).last_definition(db)
};
let Some(builder) = context.report_lint(
&INVALID_EXPLICIT_OVERRIDE,
function_literal.focus_range(db, context.module()),
) else {
return;
};
let mut diagnostic = builder.into_diagnostic(format_args!(
"Method `{}` is decorated with `@override` but does not override anything",
member.name
));
if let Some(decorator_span) =
function_literal.find_known_decorator_span(db, KnownFunction::Override)
{
diagnostic.annotate(Annotation::secondary(decorator_span));
}
diagnostic.info(format_args!(
"No `{member}` definitions were found on any superclasses of `{class}`",
member = &member.name,
class = class.name(db)
));
}
fn extract_underlying_functions<'db>(
db: &'db dyn Db,
ty: Type<'db>,
) -> Option<smallvec::SmallVec<[FunctionType<'db>; 1]>> {
match ty {
Type::FunctionLiteral(function) => Some(smallvec::smallvec_inline![function]),
Type::BoundMethod(method) => Some(smallvec::smallvec_inline![method.function(db)]),
Type::PropertyInstance(property) => extract_underlying_functions(db, property.getter(db)?),
Type::Union(union) => {
let mut functions = smallvec::smallvec![];
for member in union.elements(db) {
if let Some(mut member_functions) = extract_underlying_functions(db, *member) {
functions.append(&mut member_functions);
}
}
if functions.is_empty() {
None
} else {
Some(functions)
}
}
_ => None,
}
}