[ty] Don't introduce invalid syntax when autofixing override-of-final-method (#21699)

This commit is contained in:
Alex Waygood 2025-11-30 13:40:33 +00:00 committed by GitHub
parent 69ace00210
commit b02e8212c9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 433 additions and 424 deletions

View File

@ -354,6 +354,13 @@ impl Diagnostic {
Arc::make_mut(&mut self.inner).fix = Some(fix); Arc::make_mut(&mut self.inner).fix = Some(fix);
} }
/// If `fix` is `Some`, set the fix for this diagnostic.
pub fn set_optional_fix(&mut self, fix: Option<Fix>) {
if let Some(fix) = fix {
self.set_fix(fix);
}
}
/// Remove the fix for this diagnostic. /// Remove the fix for this diagnostic.
pub fn remove_fix(&mut self) { pub fn remove_fix(&mut self) {
Arc::make_mut(&mut self.inner).fix = None; Arc::make_mut(&mut self.inner).fix = None;

View File

@ -51,6 +51,10 @@ class Parent:
@final @final
def my_property2(self) -> int: ... def my_property2(self) -> int: ...
@property
@final
def my_property3(self) -> int: ...
@final @final
@classmethod @classmethod
def class_method1(cls) -> int: ... def class_method1(cls) -> int: ...
@ -86,6 +90,13 @@ class Child(Parent):
@property @property
def my_property2(self) -> int: ... # error: [override-of-final-method] def my_property2(self) -> int: ... # error: [override-of-final-method]
@my_property2.setter
def my_property2(self, x: int) -> None: ...
@property
def my_property3(self) -> int: ... # error: [override-of-final-method]
@my_property3.deleter
def my_proeprty3(self) -> None: ...
@classmethod @classmethod
def class_method1(cls) -> int: ... # error: [override-of-final-method] def class_method1(cls) -> int: ... # error: [override-of-final-method]
@ -461,14 +472,17 @@ class B(A):
def method1(self) -> None: ... # error: [override-of-final-method] def method1(self) -> None: ... # error: [override-of-final-method]
def method2(self) -> None: ... # error: [override-of-final-method] def method2(self) -> None: ... # error: [override-of-final-method]
def method3(self) -> None: ... # error: [override-of-final-method] def method3(self) -> None: ... # error: [override-of-final-method]
def method4(self) -> None: ... # error: [override-of-final-method]
# check that autofixes don't introduce invalid syntax
# if there are multiple statements on one line
#
# TODO: we should emit a Liskov violation here too
# error: [override-of-final-method]
method4 = 42; unrelated = 56 # fmt: skip
# Possible overrides of possibly `@final` methods... # Possible overrides of possibly `@final` methods...
class C(A): class C(A):
if coinflip(): if coinflip():
# TODO: the autofix here introduces invalid syntax because there are now no
# statements inside the `if:` branch
# (but it might still be a useful autofix in an IDE context?)
def method1(self) -> None: ... # error: [override-of-final-method] def method1(self) -> None: ... # error: [override-of-final-method]
else: else:
pass pass

View File

@ -49,26 +49,29 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/final.md
35 | def method1(self) -> None: ... # error: [override-of-final-method] 35 | def method1(self) -> None: ... # error: [override-of-final-method]
36 | def method2(self) -> None: ... # error: [override-of-final-method] 36 | def method2(self) -> None: ... # error: [override-of-final-method]
37 | def method3(self) -> None: ... # error: [override-of-final-method] 37 | def method3(self) -> None: ... # error: [override-of-final-method]
38 | def method4(self) -> None: ... # error: [override-of-final-method] 38 |
39 | 39 | # check that autofixes don't introduce invalid syntax
40 | # Possible overrides of possibly `@final` methods... 40 | # if there are multiple statements on one line
41 | class C(A): 41 | #
42 | if coinflip(): 42 | # TODO: we should emit a Liskov violation here too
43 | # TODO: the autofix here introduces invalid syntax because there are now no 43 | # error: [override-of-final-method]
44 | # statements inside the `if:` branch 44 | method4 = 42; unrelated = 56 # fmt: skip
45 | # (but it might still be a useful autofix in an IDE context?) 45 |
46 | def method1(self) -> None: ... # error: [override-of-final-method] 46 | # Possible overrides of possibly `@final` methods...
47 | else: 47 | class C(A):
48 | pass 48 | if coinflip():
49 | 49 | def method1(self) -> None: ... # error: [override-of-final-method]
50 | if coinflip(): 50 | else:
51 | def method2(self) -> None: ... # TODO: should emit [override-of-final-method] 51 | pass
52 | else: 52 |
53 | def method2(self) -> None: ... # TODO: should emit [override-of-final-method] 53 | if coinflip():
54 | 54 | def method2(self) -> None: ... # TODO: should emit [override-of-final-method]
55 | if coinflip(): 55 | else:
56 | def method3(self) -> None: ... # error: [override-of-final-method] 56 | def method2(self) -> None: ... # TODO: should emit [override-of-final-method]
57 | def method4(self) -> None: ... # error: [override-of-final-method] 57 |
58 | if coinflip():
59 | def method3(self) -> None: ... # error: [override-of-final-method]
60 | def method4(self) -> None: ... # error: [override-of-final-method]
``` ```
# Diagnostics # Diagnostics
@ -104,7 +107,7 @@ info: rule `override-of-final-method` is enabled by default
35 + # error: [override-of-final-method] 35 + # error: [override-of-final-method]
36 | def method2(self) -> None: ... # error: [override-of-final-method] 36 | def method2(self) -> None: ... # error: [override-of-final-method]
37 | def method3(self) -> None: ... # error: [override-of-final-method] 37 | def method3(self) -> None: ... # error: [override-of-final-method]
38 | def method4(self) -> None: ... # error: [override-of-final-method] 38 |
note: This is an unsafe fix and may change runtime behavior note: This is an unsafe fix and may change runtime behavior
``` ```
@ -118,7 +121,6 @@ error[override-of-final-method]: Cannot override `A.method2`
36 | def method2(self) -> None: ... # error: [override-of-final-method] 36 | def method2(self) -> None: ... # error: [override-of-final-method]
| ^^^^^^^ Overrides a definition from superclass `A` | ^^^^^^^ Overrides a definition from superclass `A`
37 | def method3(self) -> None: ... # error: [override-of-final-method] 37 | def method3(self) -> None: ... # error: [override-of-final-method]
38 | def method4(self) -> None: ... # error: [override-of-final-method]
| |
info: `A.method2` is decorated with `@final`, forbidding overrides info: `A.method2` is decorated with `@final`, forbidding overrides
--> src/mdtest_snippet.py:16:9 --> src/mdtest_snippet.py:16:9
@ -140,8 +142,8 @@ info: rule `override-of-final-method` is enabled by default
- def method2(self) -> None: ... # error: [override-of-final-method] - def method2(self) -> None: ... # error: [override-of-final-method]
36 + # error: [override-of-final-method] 36 + # error: [override-of-final-method]
37 | def method3(self) -> None: ... # error: [override-of-final-method] 37 | def method3(self) -> None: ... # error: [override-of-final-method]
38 | def method4(self) -> None: ... # error: [override-of-final-method] 38 |
39 | 39 | # check that autofixes don't introduce invalid syntax
note: This is an unsafe fix and may change runtime behavior note: This is an unsafe fix and may change runtime behavior
``` ```
@ -154,7 +156,8 @@ error[override-of-final-method]: Cannot override `A.method3`
36 | def method2(self) -> None: ... # error: [override-of-final-method] 36 | def method2(self) -> None: ... # error: [override-of-final-method]
37 | def method3(self) -> None: ... # error: [override-of-final-method] 37 | def method3(self) -> None: ... # error: [override-of-final-method]
| ^^^^^^^ Overrides a definition from superclass `A` | ^^^^^^^ Overrides a definition from superclass `A`
38 | def method4(self) -> None: ... # error: [override-of-final-method] 38 |
39 | # check that autofixes don't introduce invalid syntax
| |
info: `A.method3` is decorated with `@final`, forbidding overrides info: `A.method3` is decorated with `@final`, forbidding overrides
--> src/mdtest_snippet.py:20:9 --> src/mdtest_snippet.py:20:9
@ -174,23 +177,23 @@ info: rule `override-of-final-method` is enabled by default
36 | def method2(self) -> None: ... # error: [override-of-final-method] 36 | def method2(self) -> None: ... # error: [override-of-final-method]
- def method3(self) -> None: ... # error: [override-of-final-method] - def method3(self) -> None: ... # error: [override-of-final-method]
37 + # error: [override-of-final-method] 37 + # error: [override-of-final-method]
38 | def method4(self) -> None: ... # error: [override-of-final-method] 38 |
39 | 39 | # check that autofixes don't introduce invalid syntax
40 | # Possible overrides of possibly `@final` methods... 40 | # if there are multiple statements on one line
note: This is an unsafe fix and may change runtime behavior note: This is an unsafe fix and may change runtime behavior
``` ```
``` ```
error[override-of-final-method]: Cannot override `A.method4` error[override-of-final-method]: Cannot override `A.method4`
--> src/mdtest_snippet.py:38:9 --> src/mdtest_snippet.py:44:5
| |
36 | def method2(self) -> None: ... # error: [override-of-final-method] 42 | # TODO: we should emit a Liskov violation here too
37 | def method3(self) -> None: ... # error: [override-of-final-method] 43 | # error: [override-of-final-method]
38 | def method4(self) -> None: ... # error: [override-of-final-method] 44 | method4 = 42; unrelated = 56 # fmt: skip
| ^^^^^^^ Overrides a definition from superclass `A` | ^^^^^^^ Overrides a definition from superclass `A`
39 | 45 |
40 | # Possible overrides of possibly `@final` methods... 46 | # Possible overrides of possibly `@final` methods...
| |
info: `A.method4` is decorated with `@final`, forbidding overrides info: `A.method4` is decorated with `@final`, forbidding overrides
--> src/mdtest_snippet.py:29:9 --> src/mdtest_snippet.py:29:9
@ -206,28 +209,19 @@ info: `A.method4` is decorated with `@final`, forbidding overrides
| |
help: Remove the override of `method4` help: Remove the override of `method4`
info: rule `override-of-final-method` is enabled by default info: rule `override-of-final-method` is enabled by default
35 | def method1(self) -> None: ... # error: [override-of-final-method]
36 | def method2(self) -> None: ... # error: [override-of-final-method]
37 | def method3(self) -> None: ... # error: [override-of-final-method]
- def method4(self) -> None: ... # error: [override-of-final-method]
38 + # error: [override-of-final-method]
39 |
40 | # Possible overrides of possibly `@final` methods...
41 | class C(A):
note: This is an unsafe fix and may change runtime behavior
``` ```
``` ```
error[override-of-final-method]: Cannot override `A.method1` error[override-of-final-method]: Cannot override `A.method1`
--> src/mdtest_snippet.py:46:13 --> src/mdtest_snippet.py:49:13
| |
44 | # statements inside the `if:` branch 47 | class C(A):
45 | # (but it might still be a useful autofix in an IDE context?) 48 | if coinflip():
46 | def method1(self) -> None: ... # error: [override-of-final-method] 49 | def method1(self) -> None: ... # error: [override-of-final-method]
| ^^^^^^^ Overrides a definition from superclass `A` | ^^^^^^^ Overrides a definition from superclass `A`
47 | else: 50 | else:
48 | pass 51 | pass
| |
info: `A.method1` is decorated with `@final`, forbidding overrides info: `A.method1` is decorated with `@final`, forbidding overrides
--> src/mdtest_snippet.py:8:9 --> src/mdtest_snippet.py:8:9
@ -243,26 +237,17 @@ info: `A.method1` is decorated with `@final`, forbidding overrides
| |
help: Remove the override of `method1` help: Remove the override of `method1`
info: rule `override-of-final-method` is enabled by default info: rule `override-of-final-method` is enabled by default
43 | # TODO: the autofix here introduces invalid syntax because there are now no
44 | # statements inside the `if:` branch
45 | # (but it might still be a useful autofix in an IDE context?)
- def method1(self) -> None: ... # error: [override-of-final-method]
46 + # error: [override-of-final-method]
47 | else:
48 | pass
49 |
note: This is an unsafe fix and may change runtime behavior
``` ```
``` ```
error[override-of-final-method]: Cannot override `A.method3` error[override-of-final-method]: Cannot override `A.method3`
--> src/mdtest_snippet.py:56:13 --> src/mdtest_snippet.py:59:13
| |
55 | if coinflip(): 58 | if coinflip():
56 | def method3(self) -> None: ... # error: [override-of-final-method] 59 | def method3(self) -> None: ... # error: [override-of-final-method]
| ^^^^^^^ Overrides a definition from superclass `A` | ^^^^^^^ Overrides a definition from superclass `A`
57 | def method4(self) -> None: ... # error: [override-of-final-method] 60 | def method4(self) -> None: ... # error: [override-of-final-method]
| |
info: `A.method3` is decorated with `@final`, forbidding overrides info: `A.method3` is decorated with `@final`, forbidding overrides
--> src/mdtest_snippet.py:20:9 --> src/mdtest_snippet.py:20:9
@ -277,23 +262,16 @@ info: `A.method3` is decorated with `@final`, forbidding overrides
| |
help: Remove the override of `method3` help: Remove the override of `method3`
info: rule `override-of-final-method` is enabled by default info: rule `override-of-final-method` is enabled by default
53 | def method2(self) -> None: ... # TODO: should emit [override-of-final-method]
54 |
55 | if coinflip():
- def method3(self) -> None: ... # error: [override-of-final-method]
56 + # error: [override-of-final-method]
57 | def method4(self) -> None: ... # error: [override-of-final-method]
note: This is an unsafe fix and may change runtime behavior
``` ```
``` ```
error[override-of-final-method]: Cannot override `A.method4` error[override-of-final-method]: Cannot override `A.method4`
--> src/mdtest_snippet.py:57:13 --> src/mdtest_snippet.py:60:13
| |
55 | if coinflip(): 58 | if coinflip():
56 | def method3(self) -> None: ... # error: [override-of-final-method] 59 | def method3(self) -> None: ... # error: [override-of-final-method]
57 | def method4(self) -> None: ... # error: [override-of-final-method] 60 | def method4(self) -> None: ... # error: [override-of-final-method]
| ^^^^^^^ Overrides a definition from superclass `A` | ^^^^^^^ Overrides a definition from superclass `A`
| |
info: `A.method4` is decorated with `@final`, forbidding overrides info: `A.method4` is decorated with `@final`, forbidding overrides
@ -310,11 +288,5 @@ info: `A.method4` is decorated with `@final`, forbidding overrides
| |
help: Remove the override of `method4` help: Remove the override of `method4`
info: rule `override-of-final-method` is enabled by default info: rule `override-of-final-method` is enabled by default
54 |
55 | if coinflip():
56 | def method3(self) -> None: ... # error: [override-of-final-method]
- def method4(self) -> None: ... # error: [override-of-final-method]
57 + # error: [override-of-final-method]
note: This is an unsafe fix and may change runtime behavior
``` ```

View File

@ -28,93 +28,93 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/final.md
14 | @final 14 | @final
15 | def my_property2(self) -> int: ... 15 | def my_property2(self) -> int: ...
16 | 16 |
17 | @final 17 | @property
18 | @classmethod 18 | @final
19 | def class_method1(cls) -> int: ... 19 | def my_property3(self) -> int: ...
20 | 20 |
21 | @classmethod 21 | @final
22 | @final 22 | @classmethod
23 | def class_method2(cls) -> int: ... 23 | def class_method1(cls) -> int: ...
24 | 24 |
25 | @final 25 | @classmethod
26 | @staticmethod 26 | @final
27 | def static_method1() -> int: ... 27 | def class_method2(cls) -> int: ...
28 | 28 |
29 | @staticmethod 29 | @final
30 | @final 30 | @staticmethod
31 | def static_method2() -> int: ... 31 | def static_method1() -> int: ...
32 | 32 |
33 | @lossy_decorator 33 | @staticmethod
34 | @final 34 | @final
35 | def decorated_1(self): ... 35 | def static_method2() -> int: ...
36 | 36 |
37 | @final 37 | @lossy_decorator
38 | @lossy_decorator 38 | @final
39 | def decorated_2(self): ... 39 | def decorated_1(self): ...
40 | 40 |
41 | class Child(Parent): 41 | @final
42 | # explicitly test the concise diagnostic message, 42 | @lossy_decorator
43 | # which is different to the verbose diagnostic summary message: 43 | def decorated_2(self): ...
44 | # 44 |
45 | # error: [override-of-final-method] "Cannot override final member `foo` from superclass `Parent`" 45 | class Child(Parent):
46 | def foo(self): ... 46 | # explicitly test the concise diagnostic message,
47 | @property 47 | # which is different to the verbose diagnostic summary message:
48 | def my_property1(self) -> int: ... # error: [override-of-final-method] 48 | #
49 | 49 | # error: [override-of-final-method] "Cannot override final member `foo` from superclass `Parent`"
50 | @property 50 | def foo(self): ...
51 | def my_property2(self) -> int: ... # error: [override-of-final-method] 51 | @property
52 | 52 | def my_property1(self) -> int: ... # error: [override-of-final-method]
53 | @classmethod 53 |
54 | def class_method1(cls) -> int: ... # error: [override-of-final-method] 54 | @property
55 | 55 | def my_property2(self) -> int: ... # error: [override-of-final-method]
56 | @staticmethod 56 | @my_property2.setter
57 | def static_method1() -> int: ... # error: [override-of-final-method] 57 | def my_property2(self, x: int) -> None: ...
58 | 58 |
59 | @classmethod 59 | @property
60 | def class_method2(cls) -> int: ... # error: [override-of-final-method] 60 | def my_property3(self) -> int: ... # error: [override-of-final-method]
61 | 61 | @my_property3.deleter
62 | @staticmethod 62 | def my_proeprty3(self) -> None: ...
63 | def static_method2() -> int: ... # error: [override-of-final-method] 63 |
64 | 64 | @classmethod
65 | def decorated_1(self): ... # TODO: should emit [override-of-final-method] 65 | def class_method1(cls) -> int: ... # error: [override-of-final-method]
66 | 66 |
67 | @lossy_decorator 67 | @staticmethod
68 | def decorated_2(self): ... # TODO: should emit [override-of-final-method] 68 | def static_method1() -> int: ... # error: [override-of-final-method]
69 | 69 |
70 | class OtherChild(Parent): ... 70 | @classmethod
71 | 71 | def class_method2(cls) -> int: ... # error: [override-of-final-method]
72 | class Grandchild(OtherChild): 72 |
73 | @staticmethod 73 | @staticmethod
74 | # TODO: we should emit a Liskov violation here too 74 | def static_method2() -> int: ... # error: [override-of-final-method]
75 | # error: [override-of-final-method] 75 |
76 | def foo(): ... 76 | def decorated_1(self): ... # TODO: should emit [override-of-final-method]
77 | @property 77 |
78 | # TODO: we should emit a Liskov violation here too 78 | @lossy_decorator
79 | # error: [override-of-final-method] 79 | def decorated_2(self): ... # TODO: should emit [override-of-final-method]
80 | def my_property1(self) -> str: ... 80 |
81 | # TODO: we should emit a Liskov violation here too 81 | class OtherChild(Parent): ...
82 | # error: [override-of-final-method] 82 |
83 | class_method1 = None 83 | class Grandchild(OtherChild):
84 | 84 | @staticmethod
85 | # Diagnostic edge case: `final` is very far away from the method definition in the source code: 85 | # TODO: we should emit a Liskov violation here too
86 | 86 | # error: [override-of-final-method]
87 | T = TypeVar("T") 87 | def foo(): ...
88 | 88 | @property
89 | def identity(x: T) -> T: ... 89 | # TODO: we should emit a Liskov violation here too
90 | 90 | # error: [override-of-final-method]
91 | class Foo: 91 | def my_property1(self) -> str: ...
92 | @final 92 | # TODO: we should emit a Liskov violation here too
93 | @identity 93 | # error: [override-of-final-method]
94 | @identity 94 | class_method1 = None
95 | @identity 95 |
96 | @identity 96 | # Diagnostic edge case: `final` is very far away from the method definition in the source code:
97 | @identity 97 |
98 | @identity 98 | T = TypeVar("T")
99 | @identity 99 |
100 | @identity 100 | def identity(x: T) -> T: ...
101 | @identity 101 |
102 | @identity 102 | class Foo:
103 | @identity 103 | @final
104 | @identity 104 | @identity
105 | @identity 105 | @identity
106 | @identity 106 | @identity
@ -122,24 +122,35 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/final.md
108 | @identity 108 | @identity
109 | @identity 109 | @identity
110 | @identity 110 | @identity
111 | def bar(self): ... 111 | @identity
112 | 112 | @identity
113 | class Baz(Foo): 113 | @identity
114 | def bar(self): ... # error: [override-of-final-method] 114 | @identity
115 | @identity
116 | @identity
117 | @identity
118 | @identity
119 | @identity
120 | @identity
121 | @identity
122 | def bar(self): ...
123 |
124 | class Baz(Foo):
125 | def bar(self): ... # error: [override-of-final-method]
``` ```
# Diagnostics # Diagnostics
``` ```
error[override-of-final-method]: Cannot override `Parent.foo` error[override-of-final-method]: Cannot override `Parent.foo`
--> src/mdtest_snippet.pyi:46:9 --> src/mdtest_snippet.pyi:50:9
| |
44 | # 48 | #
45 | # error: [override-of-final-method] "Cannot override final member `foo` from superclass `Parent`" 49 | # error: [override-of-final-method] "Cannot override final member `foo` from superclass `Parent`"
46 | def foo(self): ... 50 | def foo(self): ...
| ^^^ Overrides a definition from superclass `Parent` | ^^^ Overrides a definition from superclass `Parent`
47 | @property 51 | @property
48 | def my_property1(self) -> int: ... # error: [override-of-final-method] 52 | def my_property1(self) -> int: ... # error: [override-of-final-method]
| |
info: `Parent.foo` is decorated with `@final`, forbidding overrides info: `Parent.foo` is decorated with `@final`, forbidding overrides
--> src/mdtest_snippet.pyi:6:5 --> src/mdtest_snippet.pyi:6:5
@ -154,28 +165,28 @@ info: `Parent.foo` is decorated with `@final`, forbidding overrides
| |
help: Remove the override of `foo` help: Remove the override of `foo`
info: rule `override-of-final-method` is enabled by default info: rule `override-of-final-method` is enabled by default
43 | # which is different to the verbose diagnostic summary message: 47 | # which is different to the verbose diagnostic summary message:
44 | # 48 | #
45 | # error: [override-of-final-method] "Cannot override final member `foo` from superclass `Parent`" 49 | # error: [override-of-final-method] "Cannot override final member `foo` from superclass `Parent`"
- def foo(self): ... - def foo(self): ...
46 + 50 +
47 | @property 51 | @property
48 | def my_property1(self) -> int: ... # error: [override-of-final-method] 52 | def my_property1(self) -> int: ... # error: [override-of-final-method]
49 | 53 |
note: This is an unsafe fix and may change runtime behavior note: This is an unsafe fix and may change runtime behavior
``` ```
``` ```
error[override-of-final-method]: Cannot override `Parent.my_property1` error[override-of-final-method]: Cannot override `Parent.my_property1`
--> src/mdtest_snippet.pyi:48:9 --> src/mdtest_snippet.pyi:52:9
| |
46 | def foo(self): ... 50 | def foo(self): ...
47 | @property 51 | @property
48 | def my_property1(self) -> int: ... # error: [override-of-final-method] 52 | def my_property1(self) -> int: ... # error: [override-of-final-method]
| ^^^^^^^^^^^^ Overrides a definition from superclass `Parent` | ^^^^^^^^^^^^ Overrides a definition from superclass `Parent`
49 | 53 |
50 | @property 54 | @property
| |
info: `Parent.my_property1` is decorated with `@final`, forbidding overrides info: `Parent.my_property1` is decorated with `@final`, forbidding overrides
--> src/mdtest_snippet.pyi:9:5 --> src/mdtest_snippet.pyi:9:5
@ -192,28 +203,18 @@ info: `Parent.my_property1` is decorated with `@final`, forbidding overrides
| |
help: Remove the override of `my_property1` help: Remove the override of `my_property1`
info: rule `override-of-final-method` is enabled by default info: rule `override-of-final-method` is enabled by default
44 | #
45 | # error: [override-of-final-method] "Cannot override final member `foo` from superclass `Parent`"
46 | def foo(self): ...
- @property
- def my_property1(self) -> int: ... # error: [override-of-final-method]
47 + # error: [override-of-final-method]
48 |
49 | @property
50 | def my_property2(self) -> int: ... # error: [override-of-final-method]
note: This is an unsafe fix and may change runtime behavior
``` ```
``` ```
error[override-of-final-method]: Cannot override `Parent.my_property2` error[override-of-final-method]: Cannot override `Parent.my_property2`
--> src/mdtest_snippet.pyi:51:9 --> src/mdtest_snippet.pyi:55:9
| |
50 | @property 54 | @property
51 | def my_property2(self) -> int: ... # error: [override-of-final-method] 55 | def my_property2(self) -> int: ... # error: [override-of-final-method]
| ^^^^^^^^^^^^ Overrides a definition from superclass `Parent` | ^^^^^^^^^^^^ Overrides a definition from superclass `Parent`
52 | 56 | @my_property2.setter
53 | @classmethod 57 | def my_property2(self, x: int) -> None: ...
| |
info: `Parent.my_property2` is decorated with `@final`, forbidding overrides info: `Parent.my_property2` is decorated with `@final`, forbidding overrides
--> src/mdtest_snippet.pyi:14:5 --> src/mdtest_snippet.pyi:14:5
@ -224,181 +225,197 @@ info: `Parent.my_property2` is decorated with `@final`, forbidding overrides
15 | def my_property2(self) -> int: ... 15 | def my_property2(self) -> int: ...
| ------------ `Parent.my_property2` defined here | ------------ `Parent.my_property2` defined here
16 | 16 |
17 | @final 17 | @property
| |
help: Remove the override of `my_property2` help: Remove the getter and setter for `my_property2`
info: rule `override-of-final-method` is enabled by default
```
```
error[override-of-final-method]: Cannot override `Parent.my_property3`
--> src/mdtest_snippet.pyi:60:9
|
59 | @property
60 | def my_property3(self) -> int: ... # error: [override-of-final-method]
| ^^^^^^^^^^^^ Overrides a definition from superclass `Parent`
61 | @my_property3.deleter
62 | def my_proeprty3(self) -> None: ...
|
info: `Parent.my_property3` is decorated with `@final`, forbidding overrides
--> src/mdtest_snippet.pyi:18:5
|
17 | @property
18 | @final
| ------
19 | def my_property3(self) -> int: ...
| ------------ `Parent.my_property3` defined here
20 |
21 | @final
|
help: Remove the override of `my_property3`
info: rule `override-of-final-method` is enabled by default info: rule `override-of-final-method` is enabled by default
47 | @property
48 | def my_property1(self) -> int: ... # error: [override-of-final-method]
49 |
- @property
- def my_property2(self) -> int: ... # error: [override-of-final-method]
50 + # error: [override-of-final-method]
51 |
52 | @classmethod
53 | def class_method1(cls) -> int: ... # error: [override-of-final-method]
note: This is an unsafe fix and may change runtime behavior
``` ```
``` ```
error[override-of-final-method]: Cannot override `Parent.class_method1` error[override-of-final-method]: Cannot override `Parent.class_method1`
--> src/mdtest_snippet.pyi:54:9 --> src/mdtest_snippet.pyi:65:9
| |
53 | @classmethod 64 | @classmethod
54 | def class_method1(cls) -> int: ... # error: [override-of-final-method] 65 | def class_method1(cls) -> int: ... # error: [override-of-final-method]
| ^^^^^^^^^^^^^ Overrides a definition from superclass `Parent` | ^^^^^^^^^^^^^ Overrides a definition from superclass `Parent`
55 | 66 |
56 | @staticmethod 67 | @staticmethod
| |
info: `Parent.class_method1` is decorated with `@final`, forbidding overrides info: `Parent.class_method1` is decorated with `@final`, forbidding overrides
--> src/mdtest_snippet.pyi:17:5 --> src/mdtest_snippet.pyi:21:5
| |
15 | def my_property2(self) -> int: ... 19 | def my_property3(self) -> int: ...
16 |
17 | @final
| ------
18 | @classmethod
19 | def class_method1(cls) -> int: ...
| ------------- `Parent.class_method1` defined here
20 | 20 |
21 | @classmethod 21 | @final
| ------
22 | @classmethod
23 | def class_method1(cls) -> int: ...
| ------------- `Parent.class_method1` defined here
24 |
25 | @classmethod
| |
help: Remove the override of `class_method1` help: Remove the override of `class_method1`
info: rule `override-of-final-method` is enabled by default info: rule `override-of-final-method` is enabled by default
50 | @property 61 | @my_property3.deleter
51 | def my_property2(self) -> int: ... # error: [override-of-final-method] 62 | def my_proeprty3(self) -> None: ...
52 | 63 |
- @classmethod - @classmethod
- def class_method1(cls) -> int: ... # error: [override-of-final-method] - def class_method1(cls) -> int: ... # error: [override-of-final-method]
53 + # error: [override-of-final-method] 64 + # error: [override-of-final-method]
54 | 65 |
55 | @staticmethod 66 | @staticmethod
56 | def static_method1() -> int: ... # error: [override-of-final-method] 67 | def static_method1() -> int: ... # error: [override-of-final-method]
note: This is an unsafe fix and may change runtime behavior note: This is an unsafe fix and may change runtime behavior
``` ```
``` ```
error[override-of-final-method]: Cannot override `Parent.static_method1` error[override-of-final-method]: Cannot override `Parent.static_method1`
--> src/mdtest_snippet.pyi:57:9 --> src/mdtest_snippet.pyi:68:9
| |
56 | @staticmethod 67 | @staticmethod
57 | def static_method1() -> int: ... # error: [override-of-final-method] 68 | def static_method1() -> int: ... # error: [override-of-final-method]
| ^^^^^^^^^^^^^^ Overrides a definition from superclass `Parent` | ^^^^^^^^^^^^^^ Overrides a definition from superclass `Parent`
58 | 69 |
59 | @classmethod 70 | @classmethod
| |
info: `Parent.static_method1` is decorated with `@final`, forbidding overrides info: `Parent.static_method1` is decorated with `@final`, forbidding overrides
--> src/mdtest_snippet.pyi:25:5 --> src/mdtest_snippet.pyi:29:5
| |
23 | def class_method2(cls) -> int: ... 27 | def class_method2(cls) -> int: ...
24 |
25 | @final
| ------
26 | @staticmethod
27 | def static_method1() -> int: ...
| -------------- `Parent.static_method1` defined here
28 | 28 |
29 | @staticmethod 29 | @final
| ------
30 | @staticmethod
31 | def static_method1() -> int: ...
| -------------- `Parent.static_method1` defined here
32 |
33 | @staticmethod
| |
help: Remove the override of `static_method1` help: Remove the override of `static_method1`
info: rule `override-of-final-method` is enabled by default info: rule `override-of-final-method` is enabled by default
53 | @classmethod 64 | @classmethod
54 | def class_method1(cls) -> int: ... # error: [override-of-final-method] 65 | def class_method1(cls) -> int: ... # error: [override-of-final-method]
55 | 66 |
- @staticmethod - @staticmethod
- def static_method1() -> int: ... # error: [override-of-final-method] - def static_method1() -> int: ... # error: [override-of-final-method]
56 + # error: [override-of-final-method] 67 + # error: [override-of-final-method]
57 | 68 |
58 | @classmethod 69 | @classmethod
59 | def class_method2(cls) -> int: ... # error: [override-of-final-method] 70 | def class_method2(cls) -> int: ... # error: [override-of-final-method]
note: This is an unsafe fix and may change runtime behavior note: This is an unsafe fix and may change runtime behavior
``` ```
``` ```
error[override-of-final-method]: Cannot override `Parent.class_method2` error[override-of-final-method]: Cannot override `Parent.class_method2`
--> src/mdtest_snippet.pyi:60:9 --> src/mdtest_snippet.pyi:71:9
| |
59 | @classmethod 70 | @classmethod
60 | def class_method2(cls) -> int: ... # error: [override-of-final-method] 71 | def class_method2(cls) -> int: ... # error: [override-of-final-method]
| ^^^^^^^^^^^^^ Overrides a definition from superclass `Parent` | ^^^^^^^^^^^^^ Overrides a definition from superclass `Parent`
61 | 72 |
62 | @staticmethod 73 | @staticmethod
| |
info: `Parent.class_method2` is decorated with `@final`, forbidding overrides info: `Parent.class_method2` is decorated with `@final`, forbidding overrides
--> src/mdtest_snippet.pyi:22:5 --> src/mdtest_snippet.pyi:26:5
| |
21 | @classmethod 25 | @classmethod
22 | @final 26 | @final
| ------ | ------
23 | def class_method2(cls) -> int: ... 27 | def class_method2(cls) -> int: ...
| ------------- `Parent.class_method2` defined here | ------------- `Parent.class_method2` defined here
24 | 28 |
25 | @final 29 | @final
| |
help: Remove the override of `class_method2` help: Remove the override of `class_method2`
info: rule `override-of-final-method` is enabled by default info: rule `override-of-final-method` is enabled by default
56 | @staticmethod 67 | @staticmethod
57 | def static_method1() -> int: ... # error: [override-of-final-method] 68 | def static_method1() -> int: ... # error: [override-of-final-method]
58 | 69 |
- @classmethod - @classmethod
- def class_method2(cls) -> int: ... # error: [override-of-final-method] - def class_method2(cls) -> int: ... # error: [override-of-final-method]
59 + # error: [override-of-final-method] 70 + # error: [override-of-final-method]
60 | 71 |
61 | @staticmethod 72 | @staticmethod
62 | def static_method2() -> int: ... # error: [override-of-final-method] 73 | def static_method2() -> int: ... # error: [override-of-final-method]
note: This is an unsafe fix and may change runtime behavior note: This is an unsafe fix and may change runtime behavior
``` ```
``` ```
error[override-of-final-method]: Cannot override `Parent.static_method2` error[override-of-final-method]: Cannot override `Parent.static_method2`
--> src/mdtest_snippet.pyi:63:9 --> src/mdtest_snippet.pyi:74:9
| |
62 | @staticmethod 73 | @staticmethod
63 | def static_method2() -> int: ... # error: [override-of-final-method] 74 | def static_method2() -> int: ... # error: [override-of-final-method]
| ^^^^^^^^^^^^^^ Overrides a definition from superclass `Parent` | ^^^^^^^^^^^^^^ Overrides a definition from superclass `Parent`
64 | 75 |
65 | def decorated_1(self): ... # TODO: should emit [override-of-final-method] 76 | def decorated_1(self): ... # TODO: should emit [override-of-final-method]
| |
info: `Parent.static_method2` is decorated with `@final`, forbidding overrides info: `Parent.static_method2` is decorated with `@final`, forbidding overrides
--> src/mdtest_snippet.pyi:30:5 --> src/mdtest_snippet.pyi:34:5
| |
29 | @staticmethod 33 | @staticmethod
30 | @final 34 | @final
| ------ | ------
31 | def static_method2() -> int: ... 35 | def static_method2() -> int: ...
| -------------- `Parent.static_method2` defined here | -------------- `Parent.static_method2` defined here
32 | 36 |
33 | @lossy_decorator 37 | @lossy_decorator
| |
help: Remove the override of `static_method2` help: Remove the override of `static_method2`
info: rule `override-of-final-method` is enabled by default info: rule `override-of-final-method` is enabled by default
59 | @classmethod 70 | @classmethod
60 | def class_method2(cls) -> int: ... # error: [override-of-final-method] 71 | def class_method2(cls) -> int: ... # error: [override-of-final-method]
61 | 72 |
- @staticmethod - @staticmethod
- def static_method2() -> int: ... # error: [override-of-final-method] - def static_method2() -> int: ... # error: [override-of-final-method]
62 + # error: [override-of-final-method] 73 + # error: [override-of-final-method]
63 | 74 |
64 | def decorated_1(self): ... # TODO: should emit [override-of-final-method] 75 | def decorated_1(self): ... # TODO: should emit [override-of-final-method]
65 | 76 |
note: This is an unsafe fix and may change runtime behavior note: This is an unsafe fix and may change runtime behavior
``` ```
``` ```
error[override-of-final-method]: Cannot override `Parent.foo` error[override-of-final-method]: Cannot override `Parent.foo`
--> src/mdtest_snippet.pyi:76:9 --> src/mdtest_snippet.pyi:87:9
| |
74 | # TODO: we should emit a Liskov violation here too 85 | # TODO: we should emit a Liskov violation here too
75 | # error: [override-of-final-method] 86 | # error: [override-of-final-method]
76 | def foo(): ... 87 | def foo(): ...
| ^^^ Overrides a definition from superclass `Parent` | ^^^ Overrides a definition from superclass `Parent`
77 | @property 88 | @property
78 | # TODO: we should emit a Liskov violation here too 89 | # TODO: we should emit a Liskov violation here too
| |
info: `Parent.foo` is decorated with `@final`, forbidding overrides info: `Parent.foo` is decorated with `@final`, forbidding overrides
--> src/mdtest_snippet.pyi:6:5 --> src/mdtest_snippet.pyi:6:5
@ -413,31 +430,31 @@ info: `Parent.foo` is decorated with `@final`, forbidding overrides
| |
help: Remove the override of `foo` help: Remove the override of `foo`
info: rule `override-of-final-method` is enabled by default info: rule `override-of-final-method` is enabled by default
70 | class OtherChild(Parent): ... 81 | class OtherChild(Parent): ...
71 | 82 |
72 | class Grandchild(OtherChild): 83 | class Grandchild(OtherChild):
- @staticmethod - @staticmethod
- # TODO: we should emit a Liskov violation here too - # TODO: we should emit a Liskov violation here too
- # error: [override-of-final-method] - # error: [override-of-final-method]
- def foo(): ... - def foo(): ...
73 + 84 +
74 | @property 85 | @property
75 | # TODO: we should emit a Liskov violation here too 86 | # TODO: we should emit a Liskov violation here too
76 | # error: [override-of-final-method] 87 | # error: [override-of-final-method]
note: This is an unsafe fix and may change runtime behavior note: This is an unsafe fix and may change runtime behavior
``` ```
``` ```
error[override-of-final-method]: Cannot override `Parent.my_property1` error[override-of-final-method]: Cannot override `Parent.my_property1`
--> src/mdtest_snippet.pyi:80:9 --> src/mdtest_snippet.pyi:91:9
| |
78 | # TODO: we should emit a Liskov violation here too 89 | # TODO: we should emit a Liskov violation here too
79 | # error: [override-of-final-method] 90 | # error: [override-of-final-method]
80 | def my_property1(self) -> str: ... 91 | def my_property1(self) -> str: ...
| ^^^^^^^^^^^^ Overrides a definition from superclass `Parent` | ^^^^^^^^^^^^ Overrides a definition from superclass `Parent`
81 | # TODO: we should emit a Liskov violation here too 92 | # TODO: we should emit a Liskov violation here too
82 | # error: [override-of-final-method] 93 | # error: [override-of-final-method]
| |
info: `Parent.my_property1` is decorated with `@final`, forbidding overrides info: `Parent.my_property1` is decorated with `@final`, forbidding overrides
--> src/mdtest_snippet.pyi:9:5 --> src/mdtest_snippet.pyi:9:5
@ -454,92 +471,71 @@ info: `Parent.my_property1` is decorated with `@final`, forbidding overrides
| |
help: Remove the override of `my_property1` help: Remove the override of `my_property1`
info: rule `override-of-final-method` is enabled by default info: rule `override-of-final-method` is enabled by default
74 | # TODO: we should emit a Liskov violation here too
75 | # error: [override-of-final-method]
76 | def foo(): ...
- @property
- # TODO: we should emit a Liskov violation here too
- # error: [override-of-final-method]
- def my_property1(self) -> str: ...
77 +
78 | # TODO: we should emit a Liskov violation here too
79 | # error: [override-of-final-method]
80 | class_method1 = None
note: This is an unsafe fix and may change runtime behavior
``` ```
``` ```
error[override-of-final-method]: Cannot override `Parent.class_method1` error[override-of-final-method]: Cannot override `Parent.class_method1`
--> src/mdtest_snippet.pyi:83:5 --> src/mdtest_snippet.pyi:94:5
| |
81 | # TODO: we should emit a Liskov violation here too 92 | # TODO: we should emit a Liskov violation here too
82 | # error: [override-of-final-method] 93 | # error: [override-of-final-method]
83 | class_method1 = None 94 | class_method1 = None
| ^^^^^^^^^^^^^ Overrides a definition from superclass `Parent` | ^^^^^^^^^^^^^ Overrides a definition from superclass `Parent`
84 | 95 |
85 | # Diagnostic edge case: `final` is very far away from the method definition in the source code: 96 | # Diagnostic edge case: `final` is very far away from the method definition in the source code:
| |
info: `Parent.class_method1` is decorated with `@final`, forbidding overrides info: `Parent.class_method1` is decorated with `@final`, forbidding overrides
--> src/mdtest_snippet.pyi:17:5 --> src/mdtest_snippet.pyi:21:5
| |
15 | def my_property2(self) -> int: ... 19 | def my_property3(self) -> int: ...
16 |
17 | @final
| ------
18 | @classmethod
19 | def class_method1(cls) -> int: ...
| ------------- `Parent.class_method1` defined here
20 | 20 |
21 | @classmethod 21 | @final
| ------
22 | @classmethod
23 | def class_method1(cls) -> int: ...
| ------------- `Parent.class_method1` defined here
24 |
25 | @classmethod
| |
help: Remove the override of `class_method1` help: Remove the override of `class_method1`
info: rule `override-of-final-method` is enabled by default info: rule `override-of-final-method` is enabled by default
80 | def my_property1(self) -> str: ...
81 | # TODO: we should emit a Liskov violation here too
82 | # error: [override-of-final-method]
- class_method1 = None
83 +
84 |
85 | # Diagnostic edge case: `final` is very far away from the method definition in the source code:
86 |
note: This is an unsafe fix and may change runtime behavior
``` ```
``` ```
error[override-of-final-method]: Cannot override `Foo.bar` error[override-of-final-method]: Cannot override `Foo.bar`
--> src/mdtest_snippet.pyi:114:9 --> src/mdtest_snippet.pyi:125:9
| |
113 | class Baz(Foo): 124 | class Baz(Foo):
114 | def bar(self): ... # error: [override-of-final-method] 125 | def bar(self): ... # error: [override-of-final-method]
| ^^^ Overrides a definition from superclass `Foo` | ^^^ Overrides a definition from superclass `Foo`
| |
info: `Foo.bar` is decorated with `@final`, forbidding overrides info: `Foo.bar` is decorated with `@final`, forbidding overrides
--> src/mdtest_snippet.pyi:92:5 --> src/mdtest_snippet.pyi:103:5
| |
91 | class Foo: 102 | class Foo:
92 | @final 103 | @final
| ------ | ------
93 | @identity 104 | @identity
94 | @identity 105 | @identity
| |
::: src/mdtest_snippet.pyi:111:9 ::: src/mdtest_snippet.pyi:122:9
| |
109 | @identity 120 | @identity
110 | @identity 121 | @identity
111 | def bar(self): ... 122 | def bar(self): ...
| --- `Foo.bar` defined here | --- `Foo.bar` defined here
112 | 123 |
113 | class Baz(Foo): 124 | class Baz(Foo):
| |
help: Remove the override of `bar` help: Remove the override of `bar`
info: rule `override-of-final-method` is enabled by default info: rule `override-of-final-method` is enabled by default
111 | def bar(self): ... 122 | def bar(self): ...
112 | 123 |
113 | class Baz(Foo): 124 | class Baz(Foo):
- def bar(self): ... # error: [override-of-final-method] - def bar(self): ... # error: [override-of-final-method]
114 + # error: [override-of-final-method] 125 + pass # error: [override-of-final-method]
note: This is an unsafe fix and may change runtime behavior note: This is an unsafe fix and may change runtime behavior
``` ```

View File

@ -53,7 +53,7 @@ info: rule `override-of-final-method` is enabled by default
2 | 2 |
3 | class Foo(module1.Foo): 3 | class Foo(module1.Foo):
- def f(self): ... # error: [override-of-final-method] - def f(self): ... # error: [override-of-final-method]
4 + # error: [override-of-final-method] 4 + pass # error: [override-of-final-method]
note: This is an unsafe fix and may change runtime behavior note: This is an unsafe fix and may change runtime behavior
``` ```

View File

@ -59,7 +59,7 @@ info: rule `override-of-final-method` is enabled by default
7 | class B(A): 7 | class B(A):
- @final - @final
- def f(self): ... # error: [override-of-final-method] - def f(self): ... # error: [override-of-final-method]
8 + # error: [override-of-final-method] 8 + pass # error: [override-of-final-method]
9 | 9 |
10 | class C(B): 10 | class C(B):
11 | @final 11 | @final
@ -95,7 +95,7 @@ info: rule `override-of-final-method` is enabled by default
- @final - @final
- # we only emit one error here, not two - # we only emit one error here, not two
- def f(self): ... # error: [override-of-final-method] - def f(self): ... # error: [override-of-final-method]
12 + # error: [override-of-final-method] 12 + pass # error: [override-of-final-method]
note: This is an unsafe fix and may change runtime behavior note: This is an unsafe fix and may change runtime behavior
``` ```

View File

@ -287,12 +287,11 @@ info: rule `override-of-final-method` is enabled by default
- def bar(self, x: str) -> str: ... - def bar(self, x: str) -> str: ...
- @overload - @overload
- def bar(self, x: int) -> int: ... # error: [override-of-final-method] - def bar(self, x: int) -> int: ... # error: [override-of-final-method]
43 | 43 +
44 + # error: [override-of-final-method] 44 + # error: [override-of-final-method]
45 + 45 |
46 | @overload 46 | @overload
47 | def baz(self, x: str) -> str: ... 47 | def baz(self, x: str) -> str: ...
48 | @overload
note: This is an unsafe fix and may change runtime behavior note: This is an unsafe fix and may change runtime behavior
``` ```
@ -360,12 +359,12 @@ info: rule `override-of-final-method` is enabled by default
- def f(self, x: str) -> str: ... - def f(self, x: str) -> str: ...
- @overload - @overload
- def f(self, x: int) -> int: ... - def f(self, x: int) -> int: ...
13 + 13 + pass
14 + 14 + pass
15 | # error: [override-of-final-method] 15 | # error: [override-of-final-method]
- def f(self, x: int | str) -> int | str: - def f(self, x: int | str) -> int | str:
- return x - return x
16 + 16 + pass
17 | 17 |
18 | class Bad: 18 | class Bad:
19 | @overload 19 | @overload
@ -459,15 +458,6 @@ info: `Bad.f` is decorated with `@final`, forbidding overrides
| |
help: Remove the override of `f` help: Remove the override of `f`
info: rule `override-of-final-method` is enabled by default info: rule `override-of-final-method` is enabled by default
57 |
58 | class ChildOfBad(Bad):
59 | # TODO: these should all cause us to emit Liskov violations as well
- f = None # error: [override-of-final-method]
60 + # error: [override-of-final-method]
61 | g = None # error: [override-of-final-method]
62 | h = None # error: [override-of-final-method]
63 | i = None # error: [override-of-final-method]
note: This is an unsafe fix and may change runtime behavior
``` ```
@ -493,14 +483,6 @@ info: `Bad.g` is decorated with `@final`, forbidding overrides
| |
help: Remove the override of `g` help: Remove the override of `g`
info: rule `override-of-final-method` is enabled by default info: rule `override-of-final-method` is enabled by default
58 | class ChildOfBad(Bad):
59 | # TODO: these should all cause us to emit Liskov violations as well
60 | f = None # error: [override-of-final-method]
- g = None # error: [override-of-final-method]
61 + # error: [override-of-final-method]
62 | h = None # error: [override-of-final-method]
63 | i = None # error: [override-of-final-method]
note: This is an unsafe fix and may change runtime behavior
``` ```
@ -525,13 +507,6 @@ info: `Bad.h` is decorated with `@final`, forbidding overrides
| |
help: Remove the override of `h` help: Remove the override of `h`
info: rule `override-of-final-method` is enabled by default info: rule `override-of-final-method` is enabled by default
59 | # TODO: these should all cause us to emit Liskov violations as well
60 | f = None # error: [override-of-final-method]
61 | g = None # error: [override-of-final-method]
- h = None # error: [override-of-final-method]
62 + # error: [override-of-final-method]
63 | i = None # error: [override-of-final-method]
note: This is an unsafe fix and may change runtime behavior
``` ```
@ -555,11 +530,5 @@ info: `Bad.i` is decorated with `@final`, forbidding overrides
| |
help: Remove the override of `i` help: Remove the override of `i`
info: rule `override-of-final-method` is enabled by default info: rule `override-of-final-method` is enabled by default
60 | f = None # error: [override-of-final-method]
61 | g = None # error: [override-of-final-method]
62 | h = None # error: [override-of-final-method]
- i = None # error: [override-of-final-method]
63 + # error: [override-of-final-method]
note: This is an unsafe fix and may change runtime behavior
``` ```

View File

@ -3804,6 +3804,7 @@ pub(super) fn report_overridden_final_method<'db>(
context: &InferContext<'db, '_>, context: &InferContext<'db, '_>,
member: &str, member: &str,
subclass_definition: Definition<'db>, subclass_definition: Definition<'db>,
// N.B. the type of the *definition*, not the type on an instance of the subclass
subclass_type: Type<'db>, subclass_type: Type<'db>,
superclass: ClassType<'db>, superclass: ClassType<'db>,
subclass: ClassType<'db>, subclass: ClassType<'db>,
@ -3811,6 +3812,23 @@ pub(super) fn report_overridden_final_method<'db>(
) { ) {
let db = context.db(); let db = context.db();
// Some hijinks so that we emit a diagnostic on the property getter rather than the property setter
let property_getter_definition = if subclass_definition.kind(db).is_function_def()
&& let Type::PropertyInstance(property) = subclass_type
&& let Some(Type::FunctionLiteral(getter)) = property.getter(db)
{
let getter_definition = getter.definition(db);
if getter_definition.scope(db) == subclass_definition.scope(db) {
Some(getter_definition)
} else {
None
}
} else {
None
};
let subclass_definition = property_getter_definition.unwrap_or(subclass_definition);
let Some(builder) = context.report_lint( let Some(builder) = context.report_lint(
&OVERRIDE_OF_FINAL_METHOD, &OVERRIDE_OF_FINAL_METHOD,
subclass_definition.focus_range(db, context.module()), subclass_definition.focus_range(db, context.module()),
@ -3871,37 +3889,69 @@ pub(super) fn report_overridden_final_method<'db>(
diagnostic.sub(sub); diagnostic.sub(sub);
let underlying_function = match subclass_type { // It's tempting to autofix properties as well,
Type::FunctionLiteral(function) => Some(function), // but you'd want to delete the `@my_property.deleter` as well as the getter and the deleter,
Type::BoundMethod(method) => Some(method.function(db)), // and we don't model property deleters at all right now.
_ => None, if let Type::FunctionLiteral(function) = subclass_type {
let class_node = subclass
.class_literal(db)
.0
.body_scope(db)
.node(db)
.expect_class()
.node(context.module());
let (overloads, implementation) = function.overloads_and_implementation(db);
let overload_count = overloads.len() + usize::from(implementation.is_some());
let is_only = overload_count >= class_node.body.len();
let overload_deletion = |overload: &OverloadLiteral<'db>| {
let range = overload.node(db, context.file(), context.module()).range();
if is_only {
Edit::range_replacement("pass".to_string(), range)
} else {
Edit::range_deletion(range)
}
}; };
if let Some(function) = underlying_function { let should_fix = overloads
let overload_deletion = |overload: &OverloadLiteral<'db>| { .iter()
Edit::range_deletion(overload.node(db, context.file(), context.module()).range()) .copied()
}; .chain(implementation)
.all(|overload| {
class_node
.body
.iter()
.filter_map(ast::Stmt::as_function_def_stmt)
.contains(overload.node(db, context.file(), context.module()))
});
match function.overloads_and_implementation(db) { match function.overloads_and_implementation(db) {
([first_overload, rest @ ..], None) => { ([first_overload, rest @ ..], None) => {
diagnostic.help(format_args!("Remove all overloads for `{member}`")); diagnostic.help(format_args!("Remove all overloads for `{member}`"));
diagnostic.set_fix(Fix::unsafe_edits( diagnostic.set_optional_fix(should_fix.then(|| {
Fix::unsafe_edits(
overload_deletion(first_overload), overload_deletion(first_overload),
rest.iter().map(overload_deletion), rest.iter().map(overload_deletion),
)); )
}));
} }
([first_overload, rest @ ..], Some(implementation)) => { ([first_overload, rest @ ..], Some(implementation)) => {
diagnostic.help(format_args!( diagnostic.help(format_args!(
"Remove all overloads and the implementation for `{member}`" "Remove all overloads and the implementation for `{member}`"
)); ));
diagnostic.set_fix(Fix::unsafe_edits( diagnostic.set_optional_fix(should_fix.then(|| {
Fix::unsafe_edits(
overload_deletion(first_overload), overload_deletion(first_overload),
rest.iter().chain([&implementation]).map(overload_deletion), rest.iter().chain([&implementation]).map(overload_deletion),
)); )
}));
} }
([], Some(implementation)) => { ([], Some(implementation)) => {
diagnostic.help(format_args!("Remove the override of `{member}`")); diagnostic.help(format_args!("Remove the override of `{member}`"));
diagnostic.set_fix(Fix::unsafe_edit(overload_deletion(&implementation))); diagnostic.set_optional_fix(
should_fix.then(|| Fix::unsafe_edit(overload_deletion(&implementation))),
);
} }
([], None) => { ([], None) => {
// Should be impossible to get here: how would we even infer a function as a function // Should be impossible to get here: how would we even infer a function as a function
@ -3911,11 +3961,12 @@ pub(super) fn report_overridden_final_method<'db>(
); );
} }
} }
} else if let Type::PropertyInstance(property) = subclass_type
&& property.setter(db).is_some()
{
diagnostic.help(format_args!("Remove the getter and setter for `{member}`"));
} else { } else {
diagnostic.help(format_args!("Remove the override of `{member}`")); diagnostic.help(format_args!("Remove the override of `{member}`"));
diagnostic.set_fix(Fix::unsafe_edit(Edit::range_deletion(
subclass_definition.full_range(db, context.module()).range(),
)));
} }
} }

View File

@ -327,7 +327,7 @@ fn check_class_declaration<'db>(
context, context,
&member.name, &member.name,
*definition, *definition,
type_on_subclass_instance, member.ty,
superclass, superclass,
class, class,
&superclass_method, &superclass_method,