diff --git a/crates/ty_python_semantic/resources/mdtest/override.md b/crates/ty_python_semantic/resources/mdtest/override.md index 386db9b340..7f726d4289 100644 --- a/crates/ty_python_semantic/resources/mdtest/override.md +++ b/crates/ty_python_semantic/resources/mdtest/override.md @@ -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: ... diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/override.md_-_`typing.override`_-_Basics_(b7c220f8171f11f0).snap b/crates/ty_python_semantic/resources/mdtest/snapshots/override.md_-_`typing.override`_-_Basics_(b7c220f8171f11f0).snap index 6e0cf0c01e..6c6c55d590 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/override.md_-_`typing.override`_-_Basics_(b7c220f8171f11f0).snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/override.md_-_`typing.override`_-_Basics_(b7c220f8171f11f0).snap @@ -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 diff --git a/crates/ty_python_semantic/src/types/overrides.rs b/crates/ty_python_semantic/src/types/overrides.rs index 683f5549d1..97f89aa8e5 100644 --- a/crates/ty_python_semantic/src/types/overrides.rs +++ b/crates/ty_python_semantic/src/types/overrides.rs @@ -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; 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; 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, + } +}