mirror of https://github.com/astral-sh/ruff
[ty] Extend `invalid-explicit-override` to also cover properties decorated with `@override` that do not override anything (#21756)
This commit is contained in:
parent
92c5f62ec0
commit
5756b3809c
|
|
@ -19,54 +19,74 @@ class A:
|
||||||
|
|
||||||
class Parent:
|
class Parent:
|
||||||
def foo(self): ...
|
def foo(self): ...
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def my_property1(self) -> int: ...
|
def my_property1(self) -> int: ...
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def my_property2(self) -> int: ...
|
def my_property2(self) -> int: ...
|
||||||
|
|
||||||
baz = None
|
baz = None
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def class_method1(cls) -> int: ...
|
def class_method1(cls) -> int: ...
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def static_method1() -> int: ...
|
def static_method1() -> int: ...
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def class_method2(cls) -> int: ...
|
def class_method2(cls) -> int: ...
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def static_method2() -> int: ...
|
def static_method2() -> int: ...
|
||||||
|
|
||||||
@lossy_decorator
|
@lossy_decorator
|
||||||
def decorated_1(self): ...
|
def decorated_1(self): ...
|
||||||
|
|
||||||
@lossy_decorator
|
@lossy_decorator
|
||||||
def decorated_2(self): ...
|
def decorated_2(self): ...
|
||||||
|
|
||||||
@lossy_decorator
|
@lossy_decorator
|
||||||
def decorated_3(self): ...
|
def decorated_3(self): ...
|
||||||
|
|
||||||
class Child(Parent):
|
class Child(Parent):
|
||||||
@override
|
@override
|
||||||
def foo(self): ... # fine: overrides `Parent.foo`
|
def foo(self): ... # fine: overrides `Parent.foo`
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@override
|
@override
|
||||||
def my_property1(self) -> int: ... # fine: overrides `Parent.my_property1`
|
def my_property1(self) -> int: ... # fine: overrides `Parent.my_property1`
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@property
|
@property
|
||||||
def my_property2(self) -> int: ... # fine: overrides `Parent.my_property2`
|
def my_property2(self) -> int: ... # fine: overrides `Parent.my_property2`
|
||||||
|
|
||||||
@override
|
@override
|
||||||
def baz(self): ... # fine: overrides `Parent.baz`
|
def baz(self): ... # fine: overrides `Parent.baz`
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@override
|
@override
|
||||||
def class_method1(cls) -> int: ... # fine: overrides `Parent.class_method1`
|
def class_method1(cls) -> int: ... # fine: overrides `Parent.class_method1`
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@override
|
@override
|
||||||
def static_method1() -> int: ... # fine: overrides `Parent.static_method1`
|
def static_method1() -> int: ... # fine: overrides `Parent.static_method1`
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@classmethod
|
@classmethod
|
||||||
def class_method2(cls) -> int: ... # fine: overrides `Parent.class_method2`
|
def class_method2(cls) -> int: ... # fine: overrides `Parent.class_method2`
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def static_method2() -> int: ... # fine: overrides `Parent.static_method2`
|
def static_method2() -> int: ... # fine: overrides `Parent.static_method2`
|
||||||
|
|
||||||
@override
|
@override
|
||||||
def decorated_1(self): ... # fine: overrides `Parent.decorated_1`
|
def decorated_1(self): ... # fine: overrides `Parent.decorated_1`
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@lossy_decorator
|
@lossy_decorator
|
||||||
def decorated_2(self): ... # fine: overrides `Parent.decorated_2`
|
def decorated_2(self): ... # fine: overrides `Parent.decorated_2`
|
||||||
|
|
||||||
@lossy_decorator
|
@lossy_decorator
|
||||||
@override
|
@override
|
||||||
def decorated_3(self): ... # fine: overrides `Parent.decorated_3`
|
def decorated_3(self): ... # fine: overrides `Parent.decorated_3`
|
||||||
|
|
@ -76,28 +96,37 @@ class OtherChild(Parent): ...
|
||||||
class Grandchild(OtherChild):
|
class Grandchild(OtherChild):
|
||||||
@override
|
@override
|
||||||
def foo(self): ... # fine: overrides `Parent.foo`
|
def foo(self): ... # fine: overrides `Parent.foo`
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@property
|
@property
|
||||||
def bar(self) -> int: ... # fine: overrides `Parent.bar`
|
def my_property1(self) -> int: ... # fine: overrides `Parent.my_property1`
|
||||||
|
|
||||||
@override
|
@override
|
||||||
def baz(self): ... # fine: overrides `Parent.baz`
|
def baz(self): ... # fine: overrides `Parent.baz`
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@override
|
@override
|
||||||
def class_method1(cls) -> int: ... # fine: overrides `Parent.class_method1`
|
def class_method1(cls) -> int: ... # fine: overrides `Parent.class_method1`
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@override
|
@override
|
||||||
def static_method1() -> int: ... # fine: overrides `Parent.static_method1`
|
def static_method1() -> int: ... # fine: overrides `Parent.static_method1`
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@classmethod
|
@classmethod
|
||||||
def class_method2(cls) -> int: ... # fine: overrides `Parent.class_method2`
|
def class_method2(cls) -> int: ... # fine: overrides `Parent.class_method2`
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def static_method2() -> int: ... # fine: overrides `Parent.static_method2`
|
def static_method2() -> int: ... # fine: overrides `Parent.static_method2`
|
||||||
|
|
||||||
@override
|
@override
|
||||||
def decorated_1(self): ... # fine: overrides `Parent.decorated_1`
|
def decorated_1(self): ... # fine: overrides `Parent.decorated_1`
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@lossy_decorator
|
@lossy_decorator
|
||||||
def decorated_2(self): ... # fine: overrides `Parent.decorated_2`
|
def decorated_2(self): ... # fine: overrides `Parent.decorated_2`
|
||||||
|
|
||||||
@lossy_decorator
|
@lossy_decorator
|
||||||
@override
|
@override
|
||||||
def decorated_3(self): ... # fine: overrides `Parent.decorated_3`
|
def decorated_3(self): ... # fine: overrides `Parent.decorated_3`
|
||||||
|
|
@ -105,27 +134,41 @@ class Grandchild(OtherChild):
|
||||||
class Invalid:
|
class Invalid:
|
||||||
@override
|
@override
|
||||||
def ___reprrr__(self): ... # error: [invalid-explicit-override]
|
def ___reprrr__(self): ... # error: [invalid-explicit-override]
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@classmethod
|
@classmethod
|
||||||
def foo(self): ... # error: [invalid-explicit-override]
|
def foo(self): ... # error: [invalid-explicit-override]
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@override
|
@override
|
||||||
def bar(self): ... # error: [invalid-explicit-override]
|
def bar(self): ... # error: [invalid-explicit-override]
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@override
|
@override
|
||||||
def baz(): ... # error: [invalid-explicit-override]
|
def baz(): ... # error: [invalid-explicit-override]
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def eggs(): ... # error: [invalid-explicit-override]
|
def eggs(): ... # error: [invalid-explicit-override]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@override
|
@override
|
||||||
def bad_property1(self) -> int: ... # TODO: should emit `invalid-explicit-override` here
|
def bad_property1(self) -> int: ... # error: [invalid-explicit-override]
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@property
|
@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
|
@lossy_decorator
|
||||||
@override
|
@override
|
||||||
def lossy(self): ... # TODO: should emit `invalid-explicit-override` here
|
def lossy(self): ... # TODO: should emit `invalid-explicit-override` here
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@lossy_decorator
|
@lossy_decorator
|
||||||
def lossy2(self): ... # TODO: should emit `invalid-explicit-override` here
|
def lossy2(self): ... # TODO: should emit `invalid-explicit-override` here
|
||||||
|
|
@ -136,11 +179,14 @@ class LiskovViolatingButNotOverrideViolating(Parent):
|
||||||
@override
|
@override
|
||||||
@property
|
@property
|
||||||
def foo(self) -> int: ...
|
def foo(self) -> int: ...
|
||||||
|
|
||||||
@override
|
@override
|
||||||
def my_property1(self) -> int: ...
|
def my_property1(self) -> int: ...
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@override
|
@override
|
||||||
def class_method1() -> int: ...
|
def class_method1() -> int: ...
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@override
|
@override
|
||||||
def static_method1(cls) -> int: ...
|
def static_method1(cls) -> int: ...
|
||||||
|
|
|
||||||
|
|
@ -22,175 +22,221 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/override.md
|
||||||
8 |
|
8 |
|
||||||
9 | class Parent:
|
9 | class Parent:
|
||||||
10 | def foo(self): ...
|
10 | def foo(self): ...
|
||||||
11 | @property
|
11 |
|
||||||
12 | def my_property1(self) -> int: ...
|
12 | @property
|
||||||
13 | @property
|
13 | def my_property1(self) -> int: ...
|
||||||
14 | def my_property2(self) -> int: ...
|
14 |
|
||||||
15 | baz = None
|
15 | @property
|
||||||
16 | @classmethod
|
16 | def my_property2(self) -> int: ...
|
||||||
17 | def class_method1(cls) -> int: ...
|
17 |
|
||||||
18 | @staticmethod
|
18 | baz = None
|
||||||
19 | def static_method1() -> int: ...
|
19 |
|
||||||
20 | @classmethod
|
20 | @classmethod
|
||||||
21 | def class_method2(cls) -> int: ...
|
21 | def class_method1(cls) -> int: ...
|
||||||
22 | @staticmethod
|
22 |
|
||||||
23 | def static_method2() -> int: ...
|
23 | @staticmethod
|
||||||
24 | @lossy_decorator
|
24 | def static_method1() -> int: ...
|
||||||
25 | def decorated_1(self): ...
|
25 |
|
||||||
26 | @lossy_decorator
|
26 | @classmethod
|
||||||
27 | def decorated_2(self): ...
|
27 | def class_method2(cls) -> int: ...
|
||||||
28 | @lossy_decorator
|
28 |
|
||||||
29 | def decorated_3(self): ...
|
29 | @staticmethod
|
||||||
30 |
|
30 | def static_method2() -> int: ...
|
||||||
31 | class Child(Parent):
|
31 |
|
||||||
32 | @override
|
32 | @lossy_decorator
|
||||||
33 | def foo(self): ... # fine: overrides `Parent.foo`
|
33 | def decorated_1(self): ...
|
||||||
34 | @property
|
34 |
|
||||||
35 | @override
|
35 | @lossy_decorator
|
||||||
36 | def my_property1(self) -> int: ... # fine: overrides `Parent.my_property1`
|
36 | def decorated_2(self): ...
|
||||||
37 | @override
|
37 |
|
||||||
38 | @property
|
38 | @lossy_decorator
|
||||||
39 | def my_property2(self) -> int: ... # fine: overrides `Parent.my_property2`
|
39 | def decorated_3(self): ...
|
||||||
40 | @override
|
40 |
|
||||||
41 | def baz(self): ... # fine: overrides `Parent.baz`
|
41 | class Child(Parent):
|
||||||
42 | @classmethod
|
42 | @override
|
||||||
43 | @override
|
43 | def foo(self): ... # fine: overrides `Parent.foo`
|
||||||
44 | def class_method1(cls) -> int: ... # fine: overrides `Parent.class_method1`
|
44 |
|
||||||
45 | @staticmethod
|
45 | @property
|
||||||
46 | @override
|
46 | @override
|
||||||
47 | def static_method1() -> int: ... # fine: overrides `Parent.static_method1`
|
47 | def my_property1(self) -> int: ... # fine: overrides `Parent.my_property1`
|
||||||
48 | @override
|
48 |
|
||||||
49 | @classmethod
|
49 | @override
|
||||||
50 | def class_method2(cls) -> int: ... # fine: overrides `Parent.class_method2`
|
50 | @property
|
||||||
51 | @override
|
51 | def my_property2(self) -> int: ... # fine: overrides `Parent.my_property2`
|
||||||
52 | @staticmethod
|
52 |
|
||||||
53 | def static_method2() -> int: ... # fine: overrides `Parent.static_method2`
|
53 | @override
|
||||||
54 | @override
|
54 | def baz(self): ... # fine: overrides `Parent.baz`
|
||||||
55 | def decorated_1(self): ... # fine: overrides `Parent.decorated_1`
|
55 |
|
||||||
56 | @override
|
56 | @classmethod
|
||||||
57 | @lossy_decorator
|
57 | @override
|
||||||
58 | def decorated_2(self): ... # fine: overrides `Parent.decorated_2`
|
58 | def class_method1(cls) -> int: ... # fine: overrides `Parent.class_method1`
|
||||||
59 | @lossy_decorator
|
59 |
|
||||||
60 | @override
|
60 | @staticmethod
|
||||||
61 | def decorated_3(self): ... # fine: overrides `Parent.decorated_3`
|
61 | @override
|
||||||
62 |
|
62 | def static_method1() -> int: ... # fine: overrides `Parent.static_method1`
|
||||||
63 | class OtherChild(Parent): ...
|
63 |
|
||||||
64 |
|
64 | @override
|
||||||
65 | class Grandchild(OtherChild):
|
65 | @classmethod
|
||||||
66 | @override
|
66 | def class_method2(cls) -> int: ... # fine: overrides `Parent.class_method2`
|
||||||
67 | def foo(self): ... # fine: overrides `Parent.foo`
|
67 |
|
||||||
68 | @override
|
68 | @override
|
||||||
69 | @property
|
69 | @staticmethod
|
||||||
70 | def bar(self) -> int: ... # fine: overrides `Parent.bar`
|
70 | def static_method2() -> int: ... # fine: overrides `Parent.static_method2`
|
||||||
71 | @override
|
71 |
|
||||||
72 | def baz(self): ... # fine: overrides `Parent.baz`
|
72 | @override
|
||||||
73 | @classmethod
|
73 | def decorated_1(self): ... # fine: overrides `Parent.decorated_1`
|
||||||
74 | @override
|
74 |
|
||||||
75 | def class_method1(cls) -> int: ... # fine: overrides `Parent.class_method1`
|
75 | @override
|
||||||
76 | @staticmethod
|
76 | @lossy_decorator
|
||||||
77 | @override
|
77 | def decorated_2(self): ... # fine: overrides `Parent.decorated_2`
|
||||||
78 | def static_method1() -> int: ... # fine: overrides `Parent.static_method1`
|
78 |
|
||||||
79 | @override
|
79 | @lossy_decorator
|
||||||
80 | @classmethod
|
80 | @override
|
||||||
81 | def class_method2(cls) -> int: ... # fine: overrides `Parent.class_method2`
|
81 | def decorated_3(self): ... # fine: overrides `Parent.decorated_3`
|
||||||
82 | @override
|
82 |
|
||||||
83 | @staticmethod
|
83 | class OtherChild(Parent): ...
|
||||||
84 | def static_method2() -> int: ... # fine: overrides `Parent.static_method2`
|
84 |
|
||||||
85 | @override
|
85 | class Grandchild(OtherChild):
|
||||||
86 | def decorated_1(self): ... # fine: overrides `Parent.decorated_1`
|
86 | @override
|
||||||
87 | @override
|
87 | def foo(self): ... # fine: overrides `Parent.foo`
|
||||||
88 | @lossy_decorator
|
88 |
|
||||||
89 | def decorated_2(self): ... # fine: overrides `Parent.decorated_2`
|
89 | @override
|
||||||
90 | @lossy_decorator
|
90 | @property
|
||||||
91 | @override
|
91 | def my_property1(self) -> int: ... # fine: overrides `Parent.my_property1`
|
||||||
92 | def decorated_3(self): ... # fine: overrides `Parent.decorated_3`
|
92 |
|
||||||
93 |
|
93 | @override
|
||||||
94 | class Invalid:
|
94 | def baz(self): ... # fine: overrides `Parent.baz`
|
||||||
95 | @override
|
95 |
|
||||||
96 | def ___reprrr__(self): ... # error: [invalid-explicit-override]
|
96 | @classmethod
|
||||||
97 | @override
|
97 | @override
|
||||||
98 | @classmethod
|
98 | def class_method1(cls) -> int: ... # fine: overrides `Parent.class_method1`
|
||||||
99 | def foo(self): ... # error: [invalid-explicit-override]
|
99 |
|
||||||
100 | @classmethod
|
100 | @staticmethod
|
||||||
101 | @override
|
101 | @override
|
||||||
102 | def bar(self): ... # error: [invalid-explicit-override]
|
102 | def static_method1() -> int: ... # fine: overrides `Parent.static_method1`
|
||||||
103 | @staticmethod
|
103 |
|
||||||
104 | @override
|
104 | @override
|
||||||
105 | def baz(): ... # error: [invalid-explicit-override]
|
105 | @classmethod
|
||||||
106 | @override
|
106 | def class_method2(cls) -> int: ... # fine: overrides `Parent.class_method2`
|
||||||
107 | @staticmethod
|
107 |
|
||||||
108 | def eggs(): ... # error: [invalid-explicit-override]
|
108 | @override
|
||||||
109 | @property
|
109 | @staticmethod
|
||||||
110 | @override
|
110 | def static_method2() -> int: ... # fine: overrides `Parent.static_method2`
|
||||||
111 | def bad_property1(self) -> int: ... # TODO: should emit `invalid-explicit-override` here
|
111 |
|
||||||
112 | @override
|
112 | @override
|
||||||
113 | @property
|
113 | def decorated_1(self): ... # fine: overrides `Parent.decorated_1`
|
||||||
114 | def bad_property2(self) -> int: ... # TODO: should emit `invalid-explicit-override` here
|
114 |
|
||||||
115 | @lossy_decorator
|
115 | @override
|
||||||
116 | @override
|
116 | @lossy_decorator
|
||||||
117 | def lossy(self): ... # TODO: should emit `invalid-explicit-override` here
|
117 | def decorated_2(self): ... # fine: overrides `Parent.decorated_2`
|
||||||
118 | @override
|
118 |
|
||||||
119 | @lossy_decorator
|
119 | @lossy_decorator
|
||||||
120 | def lossy2(self): ... # TODO: should emit `invalid-explicit-override` here
|
120 | @override
|
||||||
121 |
|
121 | def decorated_3(self): ... # fine: overrides `Parent.decorated_3`
|
||||||
122 | # TODO: all overrides in this class should cause us to emit *Liskov* violations,
|
122 |
|
||||||
123 | # but not `@override` violations
|
123 | class Invalid:
|
||||||
124 | class LiskovViolatingButNotOverrideViolating(Parent):
|
124 | @override
|
||||||
125 | @override
|
125 | def ___reprrr__(self): ... # error: [invalid-explicit-override]
|
||||||
126 | @property
|
126 |
|
||||||
127 | def foo(self) -> int: ...
|
127 | @override
|
||||||
128 | @override
|
128 | @classmethod
|
||||||
129 | def my_property1(self) -> int: ...
|
129 | def foo(self): ... # error: [invalid-explicit-override]
|
||||||
130 | @staticmethod
|
130 |
|
||||||
131 | @override
|
131 | @classmethod
|
||||||
132 | def class_method1() -> int: ...
|
132 | @override
|
||||||
133 | @classmethod
|
133 | def bar(self): ... # error: [invalid-explicit-override]
|
||||||
134 | @override
|
134 |
|
||||||
135 | def static_method1(cls) -> int: ...
|
135 | @staticmethod
|
||||||
136 |
|
136 | @override
|
||||||
137 | # Diagnostic edge case: `override` is very far away from the method definition in the source code:
|
137 | def baz(): ... # error: [invalid-explicit-override]
|
||||||
138 |
|
138 |
|
||||||
139 | T = TypeVar("T")
|
139 | @override
|
||||||
140 |
|
140 | @staticmethod
|
||||||
141 | def identity(x: T) -> T: ...
|
141 | def eggs(): ... # error: [invalid-explicit-override]
|
||||||
142 |
|
142 |
|
||||||
143 | class Foo:
|
143 | @property
|
||||||
144 | @override
|
144 | @override
|
||||||
145 | @identity
|
145 | def bad_property1(self) -> int: ... # error: [invalid-explicit-override]
|
||||||
146 | @identity
|
146 |
|
||||||
147 | @identity
|
147 | @override
|
||||||
148 | @identity
|
148 | @property
|
||||||
149 | @identity
|
149 | def bad_property2(self) -> int: ... # error: [invalid-explicit-override]
|
||||||
150 | @identity
|
150 |
|
||||||
151 | @identity
|
151 | @property
|
||||||
152 | @identity
|
152 | @override
|
||||||
153 | @identity
|
153 | def bad_settable_property(self) -> int: ... # error: [invalid-explicit-override]
|
||||||
154 | @identity
|
154 | @bad_settable_property.setter
|
||||||
155 | @identity
|
155 | def bad_settable_property(self, x: int) -> None: ...
|
||||||
156 | @identity
|
156 |
|
||||||
157 | @identity
|
157 | @lossy_decorator
|
||||||
158 | @identity
|
158 | @override
|
||||||
159 | @identity
|
159 | def lossy(self): ... # TODO: should emit `invalid-explicit-override` here
|
||||||
160 | @identity
|
160 |
|
||||||
161 | @identity
|
161 | @override
|
||||||
162 | @identity
|
162 | @lossy_decorator
|
||||||
163 | def bar(self): ... # error: [invalid-explicit-override]
|
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
|
# Diagnostics
|
||||||
|
|
||||||
```
|
```
|
||||||
error[invalid-explicit-override]: Method `___reprrr__` is decorated with `@override` but does not override anything
|
error[invalid-explicit-override]: Method `___reprrr__` is decorated with `@override` but does not override anything
|
||||||
--> src/mdtest_snippet.pyi:95:5
|
--> src/mdtest_snippet.pyi:124:5
|
||||||
|
|
|
|
||||||
94 | class Invalid:
|
123 | class Invalid:
|
||||||
95 | @override
|
124 | @override
|
||||||
| ---------
|
| ---------
|
||||||
96 | def ___reprrr__(self): ... # error: [invalid-explicit-override]
|
125 | def ___reprrr__(self): ... # error: [invalid-explicit-override]
|
||||||
| ^^^^^^^^^^^
|
| ^^^^^^^^^^^
|
||||||
97 | @override
|
126 |
|
||||||
98 | @classmethod
|
127 | @override
|
||||||
|
|
|
|
||||||
info: No `___reprrr__` definitions were found on any superclasses of `Invalid`
|
info: No `___reprrr__` definitions were found on any superclasses of `Invalid`
|
||||||
info: rule `invalid-explicit-override` is enabled by default
|
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
|
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
|
125 | def ___reprrr__(self): ... # error: [invalid-explicit-override]
|
||||||
96 | def ___reprrr__(self): ... # error: [invalid-explicit-override]
|
126 |
|
||||||
97 | @override
|
127 | @override
|
||||||
| ---------
|
| ---------
|
||||||
98 | @classmethod
|
128 | @classmethod
|
||||||
99 | def foo(self): ... # error: [invalid-explicit-override]
|
129 | def foo(self): ... # error: [invalid-explicit-override]
|
||||||
| ^^^
|
| ^^^
|
||||||
100 | @classmethod
|
130 |
|
||||||
101 | @override
|
131 | @classmethod
|
||||||
|
|
|
|
||||||
info: No `foo` definitions were found on any superclasses of `Invalid`
|
info: No `foo` definitions were found on any superclasses of `Invalid`
|
||||||
info: rule `invalid-explicit-override` is enabled by default
|
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
|
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]
|
131 | @classmethod
|
||||||
100 | @classmethod
|
132 | @override
|
||||||
101 | @override
|
|
||||||
| ---------
|
| ---------
|
||||||
102 | def bar(self): ... # error: [invalid-explicit-override]
|
133 | def bar(self): ... # error: [invalid-explicit-override]
|
||||||
| ^^^
|
| ^^^
|
||||||
103 | @staticmethod
|
134 |
|
||||||
104 | @override
|
135 | @staticmethod
|
||||||
|
|
|
|
||||||
info: No `bar` definitions were found on any superclasses of `Invalid`
|
info: No `bar` definitions were found on any superclasses of `Invalid`
|
||||||
info: rule `invalid-explicit-override` is enabled by default
|
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
|
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]
|
135 | @staticmethod
|
||||||
103 | @staticmethod
|
136 | @override
|
||||||
104 | @override
|
|
||||||
| ---------
|
| ---------
|
||||||
105 | def baz(): ... # error: [invalid-explicit-override]
|
137 | def baz(): ... # error: [invalid-explicit-override]
|
||||||
| ^^^
|
| ^^^
|
||||||
106 | @override
|
138 |
|
||||||
107 | @staticmethod
|
139 | @override
|
||||||
|
|
|
|
||||||
info: No `baz` definitions were found on any superclasses of `Invalid`
|
info: No `baz` definitions were found on any superclasses of `Invalid`
|
||||||
info: rule `invalid-explicit-override` is enabled by default
|
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
|
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
|
137 | def baz(): ... # error: [invalid-explicit-override]
|
||||||
105 | def baz(): ... # error: [invalid-explicit-override]
|
138 |
|
||||||
106 | @override
|
139 | @override
|
||||||
| ---------
|
| ---------
|
||||||
107 | @staticmethod
|
140 | @staticmethod
|
||||||
108 | def eggs(): ... # error: [invalid-explicit-override]
|
141 | def eggs(): ... # error: [invalid-explicit-override]
|
||||||
| ^^^^
|
| ^^^^
|
||||||
109 | @property
|
142 |
|
||||||
110 | @override
|
143 | @property
|
||||||
|
|
|
|
||||||
info: No `eggs` definitions were found on any superclasses of `Invalid`
|
info: No `eggs` definitions were found on any superclasses of `Invalid`
|
||||||
info: rule `invalid-explicit-override` is enabled by default
|
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
|
error[invalid-explicit-override]: Method `bad_property1` is decorated with `@override` but does not override anything
|
||||||
--> src/mdtest_snippet.pyi:163:9
|
--> src/mdtest_snippet.pyi:144:5
|
||||||
|
|
|
|
||||||
161 | @identity
|
143 | @property
|
||||||
162 | @identity
|
|
||||||
163 | def bar(self): ... # error: [invalid-explicit-override]
|
|
||||||
| ^^^
|
|
||||||
|
|
|
||||||
::: src/mdtest_snippet.pyi:144:5
|
|
||||||
|
|
|
||||||
143 | class Foo:
|
|
||||||
144 | @override
|
144 | @override
|
||||||
| ---------
|
| ---------
|
||||||
145 | @identity
|
145 | def bad_property1(self) -> int: ... # error: [invalid-explicit-override]
|
||||||
146 | @identity
|
| ^^^^^^^^^^^^^
|
||||||
|
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: No `bar` definitions were found on any superclasses of `Foo`
|
||||||
info: rule `invalid-explicit-override` is enabled by default
|
info: rule `invalid-explicit-override` is enabled by default
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@ use crate::{
|
||||||
report_overridden_final_method,
|
report_overridden_final_method,
|
||||||
},
|
},
|
||||||
function::{FunctionDecorators, FunctionType, KnownFunction},
|
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())
|
.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 db = context.db();
|
||||||
|
|
||||||
let MemberWithDefinition { member, definition } = member;
|
let MemberWithDefinition { member, definition } = member;
|
||||||
|
|
@ -153,6 +126,8 @@ fn check_class_declaration<'db>(
|
||||||
if class_kind == Some(CodeGeneratorKind::NamedTuple)
|
if class_kind == Some(CodeGeneratorKind::NamedTuple)
|
||||||
&& configuration.check_prohibited_named_tuple_attrs()
|
&& configuration.check_prohibited_named_tuple_attrs()
|
||||||
&& PROHIBITED_NAMEDTUPLE_ATTRS.contains(&member.name.as_str())
|
&& 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(_))
|
&& !matches!(definition.kind(db), DefinitionKind::AnnotatedAssignment(_))
|
||||||
&& let Some(builder) = context.report_lint(
|
&& let Some(builder) = context.report_lint(
|
||||||
&INVALID_NAMED_TUPLE,
|
&INVALID_NAMED_TUPLE,
|
||||||
|
|
@ -331,35 +306,11 @@ fn check_class_declaration<'db>(
|
||||||
|
|
||||||
if !subclass_overrides_superclass_declaration
|
if !subclass_overrides_superclass_declaration
|
||||||
&& !has_dynamic_superclass
|
&& !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()
|
&& 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() {
|
check_explicit_overrides(context, member, class);
|
||||||
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)
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some((superclass, superclass_method)) = overridden_final_method {
|
if let Some((superclass, superclass_method)) = overridden_final_method {
|
||||||
|
|
@ -434,3 +385,72 @@ impl OverrideRulesConfig {
|
||||||
self.contains(OverrideRulesConfig::PROHIBITED_NAMED_TUPLE_ATTR)
|
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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue