[ty] Implement typing.final for methods (#21646)

Co-authored-by: Micha Reiser <micha@reiser.io>
This commit is contained in:
Alex Waygood
2025-11-28 15:18:02 +00:00
committed by GitHub
parent c534bfaf01
commit 8bcfc198b8
14 changed files with 2607 additions and 159 deletions

View File

@@ -1,6 +1,6 @@
# Tests for the `@typing(_extensions).final` decorator
## Cannot subclass
## Cannot subclass a class decorated with `@final`
Don't do this:
@@ -29,3 +29,456 @@ class H(
G,
): ...
```
## Cannot override a method decorated with `@final`
<!-- snapshot-diagnostics -->
```pyi
from typing_extensions import final, Callable, TypeVar
def lossy_decorator(fn: Callable) -> Callable: ...
class Parent:
@final
def foo(self): ...
@final
@property
def my_property1(self) -> int: ...
@property
@final
def my_property2(self) -> int: ...
@final
@classmethod
def class_method1(cls) -> int: ...
@classmethod
@final
def class_method2(cls) -> int: ...
@final
@staticmethod
def static_method1() -> int: ...
@staticmethod
@final
def static_method2() -> int: ...
@lossy_decorator
@final
def decorated_1(self): ...
@final
@lossy_decorator
def decorated_2(self): ...
class Child(Parent):
# explicitly test the concise diagnostic message,
# which is different to the verbose diagnostic summary message:
#
# error: [override-of-final-method] "Cannot override final member `foo` from superclass `Parent`"
def foo(self): ...
@property
def my_property1(self) -> int: ... # error: [override-of-final-method]
@property
def my_property2(self) -> int: ... # error: [override-of-final-method]
@classmethod
def class_method1(cls) -> int: ... # error: [override-of-final-method]
@staticmethod
def static_method1() -> int: ... # error: [override-of-final-method]
@classmethod
def class_method2(cls) -> int: ... # error: [override-of-final-method]
@staticmethod
def static_method2() -> int: ... # error: [override-of-final-method]
def decorated_1(self): ... # TODO: should emit [override-of-final-method]
@lossy_decorator
def decorated_2(self): ... # TODO: should emit [override-of-final-method]
class OtherChild(Parent): ...
class Grandchild(OtherChild):
@staticmethod
# TODO: we should emit a Liskov violation here too
# error: [override-of-final-method]
def foo(): ...
@property
# TODO: we should emit a Liskov violation here too
# error: [override-of-final-method]
def my_property1(self) -> str: ...
# TODO: we should emit a Liskov violation here too
# error: [override-of-final-method]
class_method1 = None
# Diagnostic edge case: `final` is very far away from the method definition in the source code:
T = TypeVar("T")
def identity(x: T) -> T: ...
class Foo:
@final
@identity
@identity
@identity
@identity
@identity
@identity
@identity
@identity
@identity
@identity
@identity
@identity
@identity
@identity
@identity
@identity
@identity
@identity
def bar(self): ...
class Baz(Foo):
def bar(self): ... # error: [override-of-final-method]
```
## Diagnostic edge case: superclass with `@final` method has the same name as the subclass
<!-- snapshot-diagnostics -->
`module1.py`:
```py
from typing import final
class Foo:
@final
def f(self): ...
```
`module2.py`:
```py
import module1
class Foo(module1.Foo):
def f(self): ... # error: [override-of-final-method]
```
## Overloaded methods decorated with `@final`
In a stub file, `@final` should be applied to the first overload. In a runtime file, `@final` should
only be applied to the implementation function.
<!-- snapshot-diagnostics -->
`stub.pyi`:
```pyi
from typing import final, overload
class Good:
@overload
@final
def bar(self, x: str) -> str: ...
@overload
def bar(self, x: int) -> int: ...
@final
@overload
def baz(self, x: str) -> str: ...
@overload
def baz(self, x: int) -> int: ...
class ChildOfGood(Good):
@overload
def bar(self, x: str) -> str: ...
@overload
def bar(self, x: int) -> int: ... # error: [override-of-final-method]
@overload
def baz(self, x: str) -> str: ...
@overload
def baz(self, x: int) -> int: ... # error: [override-of-final-method]
class Bad:
@overload
def bar(self, x: str) -> str: ...
@overload
@final
# error: [invalid-overload]
def bar(self, x: int) -> int: ...
@overload
def baz(self, x: str) -> str: ...
@final
@overload
# error: [invalid-overload]
def baz(self, x: int) -> int: ...
class ChildOfBad(Bad):
@overload
def bar(self, x: str) -> str: ...
@overload
def bar(self, x: int) -> int: ... # error: [override-of-final-method]
@overload
def baz(self, x: str) -> str: ...
@overload
def baz(self, x: int) -> int: ... # error: [override-of-final-method]
```
`main.py`:
```py
from typing import overload, final
class Good:
@overload
def f(self, x: str) -> str: ...
@overload
def f(self, x: int) -> int: ...
@final
def f(self, x: int | str) -> int | str:
return x
class ChildOfGood(Good):
@overload
def f(self, x: str) -> str: ...
@overload
def f(self, x: int) -> int: ...
# error: [override-of-final-method]
def f(self, x: int | str) -> int | str:
return x
class Bad:
@overload
@final
def f(self, x: str) -> str: ...
@overload
def f(self, x: int) -> int: ...
# error: [invalid-overload]
def f(self, x: int | str) -> int | str:
return x
@final
@overload
def g(self, x: str) -> str: ...
@overload
def g(self, x: int) -> int: ...
# error: [invalid-overload]
def g(self, x: int | str) -> int | str:
return x
@overload
def h(self, x: str) -> str: ...
@overload
@final
def h(self, x: int) -> int: ...
# error: [invalid-overload]
def h(self, x: int | str) -> int | str:
return x
@overload
def i(self, x: str) -> str: ...
@final
@overload
def i(self, x: int) -> int: ...
# error: [invalid-overload]
def i(self, x: int | str) -> int | str:
return x
class ChildOfBad(Bad):
# TODO: these should all cause us to emit Liskov violations as well
f = None # error: [override-of-final-method]
g = None # error: [override-of-final-method]
h = None # error: [override-of-final-method]
i = None # error: [override-of-final-method]
```
## Edge case: the function is decorated with `@final` but originally defined elsewhere
As of 2025-11-26, pyrefly emits a diagnostic on this, but mypy and pyright do not. For mypy and
pyright to emit a diagnostic, the superclass definition decorated with `@final` must be a literal
function definition: an assignment definition where the right-hand side of the assignment is a
`@final-decorated` function is not sufficient for them to consider the superclass definition as
being `@final`.
For now, we choose to follow mypy's and pyright's behaviour here, in order to maximise compatibility
with other type checkers. We may decide to change this in the future, however, as it would simplify
our implementation. Mypy's and pyright's behaviour here is also arguably inconsistent with their
treatment of other type qualifiers such as `Final`. As discussed in
<https://discuss.python.org/t/imported-final-variable/82429>, both type checkers view the `Final`
type qualifier as travelling *across* scopes.
```py
from typing import final
class A:
@final
def method(self) -> None: ...
class B:
method = A.method
class C(B):
def method(self) -> None: ... # no diagnostic here (see prose discussion above)
```
## Constructor methods are also checked
```py
from typing import final
class A:
@final
def __init__(self) -> None: ...
class B(A):
def __init__(self) -> None: ... # error: [override-of-final-method]
```
## Only the first `@final` violation is reported
(Don't do this.)
<!-- snapshot-diagnostics -->
```py
from typing import final
class A:
@final
def f(self): ...
class B(A):
@final
def f(self): ... # error: [override-of-final-method]
class C(B):
@final
# we only emit one error here, not two
def f(self): ... # error: [override-of-final-method]
```
## For when you just really want to drive the point home
```py
from typing import final, Final
@final
@final
@final
@final
@final
@final
class A:
@final
@final
@final
@final
@final
def method(self): ...
@final
@final
@final
@final
@final
class B:
method: Final = A.method
class C(A): # error: [subclass-of-final-class]
def method(self): ... # error: [override-of-final-method]
class D(B): # error: [subclass-of-final-class]
# TODO: we should emit a diagnostic here
def method(self): ...
```
## An `@final` method is overridden by an implicit instance attribute
```py
from typing import final, Any
class Parent:
@final
def method(self) -> None: ...
class Child(Parent):
def __init__(self) -> None:
self.method: Any = 42 # TODO: we should emit `[override-of-final-method]` here
```
## A possibly-undefined `@final` method is overridden
<!-- snapshot-diagnostics -->
```py
from typing import final
def coinflip() -> bool:
return False
class A:
if coinflip():
@final
def method1(self) -> None: ...
else:
def method1(self) -> None: ...
if coinflip():
def method2(self) -> None: ...
else:
@final
def method2(self) -> None: ...
if coinflip():
@final
def method3(self) -> None: ...
else:
@final
def method3(self) -> None: ...
if coinflip():
def method4(self) -> None: ...
elif coinflip():
@final
def method4(self) -> None: ...
else:
def method4(self) -> None: ...
class B(A):
def method1(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 method4(self) -> None: ... # error: [override-of-final-method]
# Possible overrides of possibly `@final` methods...
class C(A):
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]
else:
pass
if coinflip():
def method2(self) -> None: ... # TODO: should emit [override-of-final-method]
else:
def method2(self) -> None: ... # TODO: should emit [override-of-final-method]
if coinflip():
def method3(self) -> None: ... # error: [override-of-final-method]
def method4(self) -> None: ... # error: [override-of-final-method]
```

View File

@@ -0,0 +1,320 @@
---
source: crates/ty_test/src/lib.rs
expression: snapshot
---
---
mdtest name: final.md - Tests for the `@typing(_extensions).final` decorator - A possibly-undefined `@final` method is overridden
mdtest path: crates/ty_python_semantic/resources/mdtest/final.md
---
# Python source files
## mdtest_snippet.py
```
1 | from typing import final
2 |
3 | def coinflip() -> bool:
4 | return False
5 |
6 | class A:
7 | if coinflip():
8 | @final
9 | def method1(self) -> None: ...
10 | else:
11 | def method1(self) -> None: ...
12 |
13 | if coinflip():
14 | def method2(self) -> None: ...
15 | else:
16 | @final
17 | def method2(self) -> None: ...
18 |
19 | if coinflip():
20 | @final
21 | def method3(self) -> None: ...
22 | else:
23 | @final
24 | def method3(self) -> None: ...
25 |
26 | if coinflip():
27 | def method4(self) -> None: ...
28 | elif coinflip():
29 | @final
30 | def method4(self) -> None: ...
31 | else:
32 | def method4(self) -> None: ...
33 |
34 | class B(A):
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]
38 | def method4(self) -> None: ... # error: [override-of-final-method]
39 |
40 | # Possible overrides of possibly `@final` methods...
41 | class C(A):
42 | if coinflip():
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?)
46 | def method1(self) -> None: ... # error: [override-of-final-method]
47 | else:
48 | pass
49 |
50 | if coinflip():
51 | def method2(self) -> None: ... # TODO: should emit [override-of-final-method]
52 | else:
53 | def method2(self) -> None: ... # TODO: should emit [override-of-final-method]
54 |
55 | if coinflip():
56 | def method3(self) -> None: ... # error: [override-of-final-method]
57 | def method4(self) -> None: ... # error: [override-of-final-method]
```
# Diagnostics
```
error[override-of-final-method]: Cannot override `A.method1`
--> src/mdtest_snippet.py:35:9
|
34 | class B(A):
35 | def method1(self) -> None: ... # error: [override-of-final-method]
| ^^^^^^^ Overrides a definition from superclass `A`
36 | def method2(self) -> None: ... # error: [override-of-final-method]
37 | def method3(self) -> None: ... # error: [override-of-final-method]
|
info: `A.method1` is decorated with `@final`, forbidding overrides
--> src/mdtest_snippet.py:8:9
|
6 | class A:
7 | if coinflip():
8 | @final
| ------
9 | def method1(self) -> None: ...
| ------- `A.method1` defined here
10 | else:
11 | def method1(self) -> None: ...
|
help: Remove the override of `method1`
info: rule `override-of-final-method` is enabled by default
32 | def method4(self) -> None: ...
33 |
34 | class B(A):
- def method1(self) -> None: ... # error: [override-of-final-method]
35 + # 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]
38 | 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.method2`
--> src/mdtest_snippet.py:36:9
|
34 | class B(A):
35 | def method1(self) -> None: ... # error: [override-of-final-method]
36 | def method2(self) -> None: ... # error: [override-of-final-method]
| ^^^^^^^ Overrides a definition from superclass `A`
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
--> src/mdtest_snippet.py:16:9
|
14 | def method2(self) -> None: ...
15 | else:
16 | @final
| ------
17 | def method2(self) -> None: ...
| ------- `A.method2` defined here
18 |
19 | if coinflip():
|
help: Remove the override of `method2`
info: rule `override-of-final-method` is enabled by default
33 |
34 | class B(A):
35 | def method1(self) -> None: ... # error: [override-of-final-method]
- def method2(self) -> None: ... # error: [override-of-final-method]
36 + # 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]
39 |
note: This is an unsafe fix and may change runtime behavior
```
```
error[override-of-final-method]: Cannot override `A.method3`
--> src/mdtest_snippet.py:37:9
|
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]
| ^^^^^^^ Overrides a definition from superclass `A`
38 | def method4(self) -> None: ... # error: [override-of-final-method]
|
info: `A.method3` is decorated with `@final`, forbidding overrides
--> src/mdtest_snippet.py:20:9
|
19 | if coinflip():
20 | @final
| ------
21 | def method3(self) -> None: ...
| ------- `A.method3` defined here
22 | else:
23 | @final
|
help: Remove the override of `method3`
info: rule `override-of-final-method` is enabled by default
34 | class B(A):
35 | def method1(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]
37 + # error: [override-of-final-method]
38 | def method4(self) -> None: ... # error: [override-of-final-method]
39 |
40 | # Possible overrides of possibly `@final` methods...
note: This is an unsafe fix and may change runtime behavior
```
```
error[override-of-final-method]: Cannot override `A.method4`
--> src/mdtest_snippet.py:38:9
|
36 | def method2(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]
| ^^^^^^^ Overrides a definition from superclass `A`
39 |
40 | # Possible overrides of possibly `@final` methods...
|
info: `A.method4` is decorated with `@final`, forbidding overrides
--> src/mdtest_snippet.py:29:9
|
27 | def method4(self) -> None: ...
28 | elif coinflip():
29 | @final
| ------
30 | def method4(self) -> None: ...
| ------- `A.method4` defined here
31 | else:
32 | def method4(self) -> None: ...
|
help: Remove the override of `method4`
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`
--> src/mdtest_snippet.py:46:13
|
44 | # statements inside the `if:` branch
45 | # (but it might still be a useful autofix in an IDE context?)
46 | def method1(self) -> None: ... # error: [override-of-final-method]
| ^^^^^^^ Overrides a definition from superclass `A`
47 | else:
48 | pass
|
info: `A.method1` is decorated with `@final`, forbidding overrides
--> src/mdtest_snippet.py:8:9
|
6 | class A:
7 | if coinflip():
8 | @final
| ------
9 | def method1(self) -> None: ...
| ------- `A.method1` defined here
10 | else:
11 | def method1(self) -> None: ...
|
help: Remove the override of `method1`
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`
--> src/mdtest_snippet.py:56:13
|
55 | if coinflip():
56 | def method3(self) -> None: ... # error: [override-of-final-method]
| ^^^^^^^ Overrides a definition from superclass `A`
57 | def method4(self) -> None: ... # error: [override-of-final-method]
|
info: `A.method3` is decorated with `@final`, forbidding overrides
--> src/mdtest_snippet.py:20:9
|
19 | if coinflip():
20 | @final
| ------
21 | def method3(self) -> None: ...
| ------- `A.method3` defined here
22 | else:
23 | @final
|
help: Remove the override of `method3`
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`
--> src/mdtest_snippet.py:57:13
|
55 | if coinflip():
56 | def method3(self) -> None: ... # error: [override-of-final-method]
57 | def method4(self) -> None: ... # error: [override-of-final-method]
| ^^^^^^^ Overrides a definition from superclass `A`
|
info: `A.method4` is decorated with `@final`, forbidding overrides
--> src/mdtest_snippet.py:29:9
|
27 | def method4(self) -> None: ...
28 | elif coinflip():
29 | @final
| ------
30 | def method4(self) -> None: ...
| ------- `A.method4` defined here
31 | else:
32 | def method4(self) -> None: ...
|
help: Remove the override of `method4`
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

@@ -0,0 +1,545 @@
---
source: crates/ty_test/src/lib.rs
expression: snapshot
---
---
mdtest name: final.md - Tests for the `@typing(_extensions).final` decorator - Cannot override a method decorated with `@final`
mdtest path: crates/ty_python_semantic/resources/mdtest/final.md
---
# Python source files
## mdtest_snippet.pyi
```
1 | from typing_extensions import final, Callable, TypeVar
2 |
3 | def lossy_decorator(fn: Callable) -> Callable: ...
4 |
5 | class Parent:
6 | @final
7 | def foo(self): ...
8 |
9 | @final
10 | @property
11 | def my_property1(self) -> int: ...
12 |
13 | @property
14 | @final
15 | def my_property2(self) -> int: ...
16 |
17 | @final
18 | @classmethod
19 | def class_method1(cls) -> int: ...
20 |
21 | @classmethod
22 | @final
23 | def class_method2(cls) -> int: ...
24 |
25 | @final
26 | @staticmethod
27 | def static_method1() -> int: ...
28 |
29 | @staticmethod
30 | @final
31 | def static_method2() -> int: ...
32 |
33 | @lossy_decorator
34 | @final
35 | def decorated_1(self): ...
36 |
37 | @final
38 | @lossy_decorator
39 | def decorated_2(self): ...
40 |
41 | class Child(Parent):
42 | # explicitly test the concise diagnostic message,
43 | # which is different to the verbose diagnostic summary message:
44 | #
45 | # error: [override-of-final-method] "Cannot override final member `foo` from superclass `Parent`"
46 | def foo(self): ...
47 | @property
48 | def my_property1(self) -> int: ... # error: [override-of-final-method]
49 |
50 | @property
51 | def my_property2(self) -> int: ... # error: [override-of-final-method]
52 |
53 | @classmethod
54 | def class_method1(cls) -> int: ... # error: [override-of-final-method]
55 |
56 | @staticmethod
57 | def static_method1() -> int: ... # error: [override-of-final-method]
58 |
59 | @classmethod
60 | def class_method2(cls) -> int: ... # error: [override-of-final-method]
61 |
62 | @staticmethod
63 | def static_method2() -> int: ... # error: [override-of-final-method]
64 |
65 | def decorated_1(self): ... # TODO: should emit [override-of-final-method]
66 |
67 | @lossy_decorator
68 | def decorated_2(self): ... # TODO: should emit [override-of-final-method]
69 |
70 | class OtherChild(Parent): ...
71 |
72 | class Grandchild(OtherChild):
73 | @staticmethod
74 | # TODO: we should emit a Liskov violation here too
75 | # error: [override-of-final-method]
76 | def foo(): ...
77 | @property
78 | # TODO: we should emit a Liskov violation here too
79 | # error: [override-of-final-method]
80 | def my_property1(self) -> str: ...
81 | # TODO: we should emit a Liskov violation here too
82 | # error: [override-of-final-method]
83 | class_method1 = None
84 |
85 | # Diagnostic edge case: `final` is very far away from the method definition in the source code:
86 |
87 | T = TypeVar("T")
88 |
89 | def identity(x: T) -> T: ...
90 |
91 | class Foo:
92 | @final
93 | @identity
94 | @identity
95 | @identity
96 | @identity
97 | @identity
98 | @identity
99 | @identity
100 | @identity
101 | @identity
102 | @identity
103 | @identity
104 | @identity
105 | @identity
106 | @identity
107 | @identity
108 | @identity
109 | @identity
110 | @identity
111 | def bar(self): ...
112 |
113 | class Baz(Foo):
114 | def bar(self): ... # error: [override-of-final-method]
```
# Diagnostics
```
error[override-of-final-method]: Cannot override `Parent.foo`
--> src/mdtest_snippet.pyi:46:9
|
44 | #
45 | # error: [override-of-final-method] "Cannot override final member `foo` from superclass `Parent`"
46 | def foo(self): ...
| ^^^ Overrides a definition from superclass `Parent`
47 | @property
48 | def my_property1(self) -> int: ... # error: [override-of-final-method]
|
info: `Parent.foo` is decorated with `@final`, forbidding overrides
--> src/mdtest_snippet.pyi:6:5
|
5 | class Parent:
6 | @final
| ------
7 | def foo(self): ...
| --- `Parent.foo` defined here
8 |
9 | @final
|
help: Remove the override of `foo`
info: rule `override-of-final-method` is enabled by default
43 | # which is different to the verbose diagnostic summary message:
44 | #
45 | # error: [override-of-final-method] "Cannot override final member `foo` from superclass `Parent`"
- def foo(self): ...
46 +
47 | @property
48 | def my_property1(self) -> int: ... # error: [override-of-final-method]
49 |
note: This is an unsafe fix and may change runtime behavior
```
```
error[override-of-final-method]: Cannot override `Parent.my_property1`
--> src/mdtest_snippet.pyi:48:9
|
46 | def foo(self): ...
47 | @property
48 | def my_property1(self) -> int: ... # error: [override-of-final-method]
| ^^^^^^^^^^^^ Overrides a definition from superclass `Parent`
49 |
50 | @property
|
info: `Parent.my_property1` is decorated with `@final`, forbidding overrides
--> src/mdtest_snippet.pyi:9:5
|
7 | def foo(self): ...
8 |
9 | @final
| ------
10 | @property
11 | def my_property1(self) -> int: ...
| ------------ `Parent.my_property1` defined here
12 |
13 | @property
|
help: Remove the override of `my_property1`
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`
--> src/mdtest_snippet.pyi:51:9
|
50 | @property
51 | def my_property2(self) -> int: ... # error: [override-of-final-method]
| ^^^^^^^^^^^^ Overrides a definition from superclass `Parent`
52 |
53 | @classmethod
|
info: `Parent.my_property2` is decorated with `@final`, forbidding overrides
--> src/mdtest_snippet.pyi:14:5
|
13 | @property
14 | @final
| ------
15 | def my_property2(self) -> int: ...
| ------------ `Parent.my_property2` defined here
16 |
17 | @final
|
help: Remove the override of `my_property2`
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`
--> src/mdtest_snippet.pyi:54:9
|
53 | @classmethod
54 | def class_method1(cls) -> int: ... # error: [override-of-final-method]
| ^^^^^^^^^^^^^ Overrides a definition from superclass `Parent`
55 |
56 | @staticmethod
|
info: `Parent.class_method1` is decorated with `@final`, forbidding overrides
--> src/mdtest_snippet.pyi:17:5
|
15 | def my_property2(self) -> int: ...
16 |
17 | @final
| ------
18 | @classmethod
19 | def class_method1(cls) -> int: ...
| ------------- `Parent.class_method1` defined here
20 |
21 | @classmethod
|
help: Remove the override of `class_method1`
info: rule `override-of-final-method` is enabled by default
50 | @property
51 | def my_property2(self) -> int: ... # error: [override-of-final-method]
52 |
- @classmethod
- def class_method1(cls) -> int: ... # error: [override-of-final-method]
53 + # error: [override-of-final-method]
54 |
55 | @staticmethod
56 | def static_method1() -> 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.static_method1`
--> src/mdtest_snippet.pyi:57:9
|
56 | @staticmethod
57 | def static_method1() -> int: ... # error: [override-of-final-method]
| ^^^^^^^^^^^^^^ Overrides a definition from superclass `Parent`
58 |
59 | @classmethod
|
info: `Parent.static_method1` is decorated with `@final`, forbidding overrides
--> src/mdtest_snippet.pyi:25:5
|
23 | def class_method2(cls) -> int: ...
24 |
25 | @final
| ------
26 | @staticmethod
27 | def static_method1() -> int: ...
| -------------- `Parent.static_method1` defined here
28 |
29 | @staticmethod
|
help: Remove the override of `static_method1`
info: rule `override-of-final-method` is enabled by default
53 | @classmethod
54 | def class_method1(cls) -> int: ... # error: [override-of-final-method]
55 |
- @staticmethod
- def static_method1() -> int: ... # error: [override-of-final-method]
56 + # error: [override-of-final-method]
57 |
58 | @classmethod
59 | def class_method2(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_method2`
--> src/mdtest_snippet.pyi:60:9
|
59 | @classmethod
60 | def class_method2(cls) -> int: ... # error: [override-of-final-method]
| ^^^^^^^^^^^^^ Overrides a definition from superclass `Parent`
61 |
62 | @staticmethod
|
info: `Parent.class_method2` is decorated with `@final`, forbidding overrides
--> src/mdtest_snippet.pyi:22:5
|
21 | @classmethod
22 | @final
| ------
23 | def class_method2(cls) -> int: ...
| ------------- `Parent.class_method2` defined here
24 |
25 | @final
|
help: Remove the override of `class_method2`
info: rule `override-of-final-method` is enabled by default
56 | @staticmethod
57 | def static_method1() -> int: ... # error: [override-of-final-method]
58 |
- @classmethod
- def class_method2(cls) -> int: ... # error: [override-of-final-method]
59 + # error: [override-of-final-method]
60 |
61 | @staticmethod
62 | def static_method2() -> 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.static_method2`
--> src/mdtest_snippet.pyi:63:9
|
62 | @staticmethod
63 | def static_method2() -> int: ... # error: [override-of-final-method]
| ^^^^^^^^^^^^^^ Overrides a definition from superclass `Parent`
64 |
65 | def decorated_1(self): ... # TODO: should emit [override-of-final-method]
|
info: `Parent.static_method2` is decorated with `@final`, forbidding overrides
--> src/mdtest_snippet.pyi:30:5
|
29 | @staticmethod
30 | @final
| ------
31 | def static_method2() -> int: ...
| -------------- `Parent.static_method2` defined here
32 |
33 | @lossy_decorator
|
help: Remove the override of `static_method2`
info: rule `override-of-final-method` is enabled by default
59 | @classmethod
60 | def class_method2(cls) -> int: ... # error: [override-of-final-method]
61 |
- @staticmethod
- def static_method2() -> int: ... # error: [override-of-final-method]
62 + # error: [override-of-final-method]
63 |
64 | def decorated_1(self): ... # TODO: should emit [override-of-final-method]
65 |
note: This is an unsafe fix and may change runtime behavior
```
```
error[override-of-final-method]: Cannot override `Parent.foo`
--> src/mdtest_snippet.pyi:76:9
|
74 | # TODO: we should emit a Liskov violation here too
75 | # error: [override-of-final-method]
76 | def foo(): ...
| ^^^ Overrides a definition from superclass `Parent`
77 | @property
78 | # TODO: we should emit a Liskov violation here too
|
info: `Parent.foo` is decorated with `@final`, forbidding overrides
--> src/mdtest_snippet.pyi:6:5
|
5 | class Parent:
6 | @final
| ------
7 | def foo(self): ...
| --- `Parent.foo` defined here
8 |
9 | @final
|
help: Remove the override of `foo`
info: rule `override-of-final-method` is enabled by default
70 | class OtherChild(Parent): ...
71 |
72 | class Grandchild(OtherChild):
- @staticmethod
- # TODO: we should emit a Liskov violation here too
- # error: [override-of-final-method]
- def foo(): ...
73 +
74 | @property
75 | # TODO: we should emit a Liskov violation here too
76 | # 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_property1`
--> src/mdtest_snippet.pyi:80:9
|
78 | # TODO: we should emit a Liskov violation here too
79 | # error: [override-of-final-method]
80 | def my_property1(self) -> str: ...
| ^^^^^^^^^^^^ Overrides a definition from superclass `Parent`
81 | # TODO: we should emit a Liskov violation here too
82 | # error: [override-of-final-method]
|
info: `Parent.my_property1` is decorated with `@final`, forbidding overrides
--> src/mdtest_snippet.pyi:9:5
|
7 | def foo(self): ...
8 |
9 | @final
| ------
10 | @property
11 | def my_property1(self) -> int: ...
| ------------ `Parent.my_property1` defined here
12 |
13 | @property
|
help: Remove the override of `my_property1`
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`
--> src/mdtest_snippet.pyi:83:5
|
81 | # TODO: we should emit a Liskov violation here too
82 | # error: [override-of-final-method]
83 | class_method1 = None
| ^^^^^^^^^^^^^ Overrides a definition from superclass `Parent`
84 |
85 | # 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
--> src/mdtest_snippet.pyi:17:5
|
15 | def my_property2(self) -> int: ...
16 |
17 | @final
| ------
18 | @classmethod
19 | def class_method1(cls) -> int: ...
| ------------- `Parent.class_method1` defined here
20 |
21 | @classmethod
|
help: Remove the override of `class_method1`
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`
--> src/mdtest_snippet.pyi:114:9
|
113 | class Baz(Foo):
114 | def bar(self): ... # error: [override-of-final-method]
| ^^^ Overrides a definition from superclass `Foo`
|
info: `Foo.bar` is decorated with `@final`, forbidding overrides
--> src/mdtest_snippet.pyi:92:5
|
91 | class Foo:
92 | @final
| ------
93 | @identity
94 | @identity
|
::: src/mdtest_snippet.pyi:111:9
|
109 | @identity
110 | @identity
111 | def bar(self): ...
| --- `Foo.bar` defined here
112 |
113 | class Baz(Foo):
|
help: Remove the override of `bar`
info: rule `override-of-final-method` is enabled by default
111 | def bar(self): ...
112 |
113 | class Baz(Foo):
- def bar(self): ... # error: [override-of-final-method]
114 + # error: [override-of-final-method]
note: This is an unsafe fix and may change runtime behavior
```

View File

@@ -0,0 +1,59 @@
---
source: crates/ty_test/src/lib.rs
expression: snapshot
---
---
mdtest name: final.md - Tests for the `@typing(_extensions).final` decorator - Diagnostic edge case: superclass with `@final` method has the same name as the subclass
mdtest path: crates/ty_python_semantic/resources/mdtest/final.md
---
# Python source files
## module1.py
```
1 | from typing import final
2 |
3 | class Foo:
4 | @final
5 | def f(self): ...
```
## module2.py
```
1 | import module1
2 |
3 | class Foo(module1.Foo):
4 | def f(self): ... # error: [override-of-final-method]
```
# Diagnostics
```
error[override-of-final-method]: Cannot override `module1.Foo.f`
--> src/module2.py:4:9
|
3 | class Foo(module1.Foo):
4 | def f(self): ... # error: [override-of-final-method]
| ^ Overrides a definition from superclass `module1.Foo`
|
info: `module1.Foo.f` is decorated with `@final`, forbidding overrides
--> src/module1.py:4:5
|
3 | class Foo:
4 | @final
| ------
5 | def f(self): ...
| - `module1.Foo.f` defined here
|
help: Remove the override of `f`
info: rule `override-of-final-method` is enabled by default
1 | import module1
2 |
3 | class Foo(module1.Foo):
- def f(self): ... # error: [override-of-final-method]
4 + # error: [override-of-final-method]
note: This is an unsafe fix and may change runtime behavior
```

View File

@@ -0,0 +1,101 @@
---
source: crates/ty_test/src/lib.rs
expression: snapshot
---
---
mdtest name: final.md - Tests for the `@typing(_extensions).final` decorator - Only the first `@final` violation is reported
mdtest path: crates/ty_python_semantic/resources/mdtest/final.md
---
# Python source files
## mdtest_snippet.py
```
1 | from typing import final
2 |
3 | class A:
4 | @final
5 | def f(self): ...
6 |
7 | class B(A):
8 | @final
9 | def f(self): ... # error: [override-of-final-method]
10 |
11 | class C(B):
12 | @final
13 | # we only emit one error here, not two
14 | def f(self): ... # error: [override-of-final-method]
```
# Diagnostics
```
error[override-of-final-method]: Cannot override `A.f`
--> src/mdtest_snippet.py:9:9
|
7 | class B(A):
8 | @final
9 | def f(self): ... # error: [override-of-final-method]
| ^ Overrides a definition from superclass `A`
10 |
11 | class C(B):
|
info: `A.f` is decorated with `@final`, forbidding overrides
--> src/mdtest_snippet.py:4:5
|
3 | class A:
4 | @final
| ------
5 | def f(self): ...
| - `A.f` defined here
6 |
7 | class B(A):
|
help: Remove the override of `f`
info: rule `override-of-final-method` is enabled by default
5 | def f(self): ...
6 |
7 | class B(A):
- @final
- def f(self): ... # error: [override-of-final-method]
8 + # error: [override-of-final-method]
9 |
10 | class C(B):
11 | @final
note: This is an unsafe fix and may change runtime behavior
```
```
error[override-of-final-method]: Cannot override `B.f`
--> src/mdtest_snippet.py:14:9
|
12 | @final
13 | # we only emit one error here, not two
14 | def f(self): ... # error: [override-of-final-method]
| ^ Overrides a definition from superclass `B`
|
info: `B.f` is decorated with `@final`, forbidding overrides
--> src/mdtest_snippet.py:8:5
|
7 | class B(A):
8 | @final
| ------
9 | def f(self): ... # error: [override-of-final-method]
| - `B.f` defined here
10 |
11 | class C(B):
|
help: Remove the override of `f`
info: rule `override-of-final-method` is enabled by default
9 | def f(self): ... # error: [override-of-final-method]
10 |
11 | class C(B):
- @final
- # we only emit one error here, not two
- def f(self): ... # error: [override-of-final-method]
12 + # error: [override-of-final-method]
note: This is an unsafe fix and may change runtime behavior
```

View File

@@ -0,0 +1,565 @@
---
source: crates/ty_test/src/lib.rs
expression: snapshot
---
---
mdtest name: final.md - Tests for the `@typing(_extensions).final` decorator - Overloaded methods decorated with `@final`
mdtest path: crates/ty_python_semantic/resources/mdtest/final.md
---
# Python source files
## stub.pyi
```
1 | from typing import final, overload
2 |
3 | class Good:
4 | @overload
5 | @final
6 | def bar(self, x: str) -> str: ...
7 | @overload
8 | def bar(self, x: int) -> int: ...
9 |
10 | @final
11 | @overload
12 | def baz(self, x: str) -> str: ...
13 | @overload
14 | def baz(self, x: int) -> int: ...
15 |
16 | class ChildOfGood(Good):
17 | @overload
18 | def bar(self, x: str) -> str: ...
19 | @overload
20 | def bar(self, x: int) -> int: ... # error: [override-of-final-method]
21 |
22 | @overload
23 | def baz(self, x: str) -> str: ...
24 | @overload
25 | def baz(self, x: int) -> int: ... # error: [override-of-final-method]
26 |
27 | class Bad:
28 | @overload
29 | def bar(self, x: str) -> str: ...
30 | @overload
31 | @final
32 | # error: [invalid-overload]
33 | def bar(self, x: int) -> int: ...
34 |
35 | @overload
36 | def baz(self, x: str) -> str: ...
37 | @final
38 | @overload
39 | # error: [invalid-overload]
40 | def baz(self, x: int) -> int: ...
41 |
42 | class ChildOfBad(Bad):
43 | @overload
44 | def bar(self, x: str) -> str: ...
45 | @overload
46 | def bar(self, x: int) -> int: ... # error: [override-of-final-method]
47 |
48 | @overload
49 | def baz(self, x: str) -> str: ...
50 | @overload
51 | def baz(self, x: int) -> int: ... # error: [override-of-final-method]
```
## main.py
```
1 | from typing import overload, final
2 |
3 | class Good:
4 | @overload
5 | def f(self, x: str) -> str: ...
6 | @overload
7 | def f(self, x: int) -> int: ...
8 | @final
9 | def f(self, x: int | str) -> int | str:
10 | return x
11 |
12 | class ChildOfGood(Good):
13 | @overload
14 | def f(self, x: str) -> str: ...
15 | @overload
16 | def f(self, x: int) -> int: ...
17 | # error: [override-of-final-method]
18 | def f(self, x: int | str) -> int | str:
19 | return x
20 |
21 | class Bad:
22 | @overload
23 | @final
24 | def f(self, x: str) -> str: ...
25 | @overload
26 | def f(self, x: int) -> int: ...
27 | # error: [invalid-overload]
28 | def f(self, x: int | str) -> int | str:
29 | return x
30 |
31 | @final
32 | @overload
33 | def g(self, x: str) -> str: ...
34 | @overload
35 | def g(self, x: int) -> int: ...
36 | # error: [invalid-overload]
37 | def g(self, x: int | str) -> int | str:
38 | return x
39 |
40 | @overload
41 | def h(self, x: str) -> str: ...
42 | @overload
43 | @final
44 | def h(self, x: int) -> int: ...
45 | # error: [invalid-overload]
46 | def h(self, x: int | str) -> int | str:
47 | return x
48 |
49 | @overload
50 | def i(self, x: str) -> str: ...
51 | @final
52 | @overload
53 | def i(self, x: int) -> int: ...
54 | # error: [invalid-overload]
55 | def i(self, x: int | str) -> int | str:
56 | return x
57 |
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]
61 | g = None # error: [override-of-final-method]
62 | h = None # error: [override-of-final-method]
63 | i = None # error: [override-of-final-method]
```
# Diagnostics
```
error[override-of-final-method]: Cannot override `Good.bar`
--> src/stub.pyi:20:9
|
18 | def bar(self, x: str) -> str: ...
19 | @overload
20 | def bar(self, x: int) -> int: ... # error: [override-of-final-method]
| ^^^ Overrides a definition from superclass `Good`
21 |
22 | @overload
|
info: `Good.bar` is decorated with `@final`, forbidding overrides
--> src/stub.pyi:5:5
|
3 | class Good:
4 | @overload
5 | @final
| ------
6 | def bar(self, x: str) -> str: ...
| --- `Good.bar` defined here
7 | @overload
8 | def bar(self, x: int) -> int: ...
|
help: Remove all overloads for `bar`
info: rule `override-of-final-method` is enabled by default
14 | def baz(self, x: int) -> int: ...
15 |
16 | class ChildOfGood(Good):
- @overload
- def bar(self, x: str) -> str: ...
- @overload
- def bar(self, x: int) -> int: ... # error: [override-of-final-method]
17 +
18 + # error: [override-of-final-method]
19 |
20 | @overload
21 | def baz(self, x: str) -> str: ...
note: This is an unsafe fix and may change runtime behavior
```
```
error[override-of-final-method]: Cannot override `Good.baz`
--> src/stub.pyi:25:9
|
23 | def baz(self, x: str) -> str: ...
24 | @overload
25 | def baz(self, x: int) -> int: ... # error: [override-of-final-method]
| ^^^ Overrides a definition from superclass `Good`
26 |
27 | class Bad:
|
info: `Good.baz` is decorated with `@final`, forbidding overrides
--> src/stub.pyi:10:5
|
8 | def bar(self, x: int) -> int: ...
9 |
10 | @final
| ------
11 | @overload
12 | def baz(self, x: str) -> str: ...
| --- `Good.baz` defined here
13 | @overload
14 | def baz(self, x: int) -> int: ...
|
help: Remove all overloads for `baz`
info: rule `override-of-final-method` is enabled by default
19 | @overload
20 | def bar(self, x: int) -> int: ... # error: [override-of-final-method]
21 |
- @overload
- def baz(self, x: str) -> str: ...
- @overload
- def baz(self, x: int) -> int: ... # error: [override-of-final-method]
22 +
23 + # error: [override-of-final-method]
24 |
25 | class Bad:
26 | @overload
note: This is an unsafe fix and may change runtime behavior
```
```
error[invalid-overload]: `@final` decorator should be applied only to the first overload
--> src/stub.pyi:29:9
|
27 | class Bad:
28 | @overload
29 | def bar(self, x: str) -> str: ...
| --- First overload defined here
30 | @overload
31 | @final
32 | # error: [invalid-overload]
33 | def bar(self, x: int) -> int: ...
| ^^^
34 |
35 | @overload
|
info: rule `invalid-overload` is enabled by default
```
```
error[invalid-overload]: `@final` decorator should be applied only to the first overload
--> src/stub.pyi:36:9
|
35 | @overload
36 | def baz(self, x: str) -> str: ...
| --- First overload defined here
37 | @final
38 | @overload
39 | # error: [invalid-overload]
40 | def baz(self, x: int) -> int: ...
| ^^^
41 |
42 | class ChildOfBad(Bad):
|
info: rule `invalid-overload` is enabled by default
```
```
error[override-of-final-method]: Cannot override `Bad.bar`
--> src/stub.pyi:46:9
|
44 | def bar(self, x: str) -> str: ...
45 | @overload
46 | def bar(self, x: int) -> int: ... # error: [override-of-final-method]
| ^^^ Overrides a definition from superclass `Bad`
47 |
48 | @overload
|
info: `Bad.bar` is decorated with `@final`, forbidding overrides
--> src/stub.pyi:29:9
|
27 | class Bad:
28 | @overload
29 | def bar(self, x: str) -> str: ...
| --- `Bad.bar` defined here
30 | @overload
31 | @final
|
help: Remove all overloads for `bar`
info: rule `override-of-final-method` is enabled by default
40 | def baz(self, x: int) -> int: ...
41 |
42 | class ChildOfBad(Bad):
- @overload
- def bar(self, x: str) -> str: ...
- @overload
- def bar(self, x: int) -> int: ... # error: [override-of-final-method]
43 |
44 + # error: [override-of-final-method]
45 +
46 | @overload
47 | def baz(self, x: str) -> str: ...
48 | @overload
note: This is an unsafe fix and may change runtime behavior
```
```
error[override-of-final-method]: Cannot override `Bad.baz`
--> src/stub.pyi:51:9
|
49 | def baz(self, x: str) -> str: ...
50 | @overload
51 | def baz(self, x: int) -> int: ... # error: [override-of-final-method]
| ^^^ Overrides a definition from superclass `Bad`
|
info: `Bad.baz` is decorated with `@final`, forbidding overrides
--> src/stub.pyi:36:9
|
35 | @overload
36 | def baz(self, x: str) -> str: ...
| --- `Bad.baz` defined here
37 | @final
38 | @overload
|
help: Remove all overloads for `baz`
info: rule `override-of-final-method` is enabled by default
45 | @overload
46 | def bar(self, x: int) -> int: ... # error: [override-of-final-method]
47 |
- @overload
- def baz(self, x: str) -> str: ...
- @overload
- def baz(self, x: int) -> int: ... # error: [override-of-final-method]
48 +
49 + # error: [override-of-final-method]
note: This is an unsafe fix and may change runtime behavior
```
```
error[override-of-final-method]: Cannot override `Good.f`
--> src/main.py:18:9
|
16 | def f(self, x: int) -> int: ...
17 | # error: [override-of-final-method]
18 | def f(self, x: int | str) -> int | str:
| ^ Overrides a definition from superclass `Good`
19 | return x
|
info: `Good.f` is decorated with `@final`, forbidding overrides
--> src/main.py:8:5
|
6 | @overload
7 | def f(self, x: int) -> int: ...
8 | @final
| ------
9 | def f(self, x: int | str) -> int | str:
| - `Good.f` defined here
10 | return x
|
help: Remove all overloads and the implementation for `f`
info: rule `override-of-final-method` is enabled by default
10 | return x
11 |
12 | class ChildOfGood(Good):
- @overload
- def f(self, x: str) -> str: ...
- @overload
- def f(self, x: int) -> int: ...
13 +
14 +
15 | # error: [override-of-final-method]
- def f(self, x: int | str) -> int | str:
- return x
16 +
17 |
18 | class Bad:
19 | @overload
note: This is an unsafe fix and may change runtime behavior
```
```
error[invalid-overload]: `@final` decorator should be applied only to the overload implementation
--> src/main.py:28:9
|
26 | def f(self, x: int) -> int: ...
27 | # error: [invalid-overload]
28 | def f(self, x: int | str) -> int | str:
| -
| |
| Implementation defined here
29 | return x
|
info: rule `invalid-overload` is enabled by default
```
```
error[invalid-overload]: `@final` decorator should be applied only to the overload implementation
--> src/main.py:37:9
|
35 | def g(self, x: int) -> int: ...
36 | # error: [invalid-overload]
37 | def g(self, x: int | str) -> int | str:
| -
| |
| Implementation defined here
38 | return x
|
info: rule `invalid-overload` is enabled by default
```
```
error[invalid-overload]: `@final` decorator should be applied only to the overload implementation
--> src/main.py:46:9
|
44 | def h(self, x: int) -> int: ...
45 | # error: [invalid-overload]
46 | def h(self, x: int | str) -> int | str:
| -
| |
| Implementation defined here
47 | return x
|
info: rule `invalid-overload` is enabled by default
```
```
error[invalid-overload]: `@final` decorator should be applied only to the overload implementation
--> src/main.py:55:9
|
53 | def i(self, x: int) -> int: ...
54 | # error: [invalid-overload]
55 | def i(self, x: int | str) -> int | str:
| -
| |
| Implementation defined here
56 | return x
|
info: rule `invalid-overload` is enabled by default
```
```
error[override-of-final-method]: Cannot override `Bad.f`
--> src/main.py:60:5
|
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]
| ^ Overrides a definition from superclass `Bad`
61 | g = None # error: [override-of-final-method]
62 | h = None # error: [override-of-final-method]
|
info: `Bad.f` is decorated with `@final`, forbidding overrides
--> src/main.py:28:9
|
26 | def f(self, x: int) -> int: ...
27 | # error: [invalid-overload]
28 | def f(self, x: int | str) -> int | str:
| - `Bad.f` defined here
29 | return x
|
help: Remove the override of `f`
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
```
```
error[override-of-final-method]: Cannot override `Bad.g`
--> src/main.py:61:5
|
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]
| ^ Overrides a definition from superclass `Bad`
62 | h = None # error: [override-of-final-method]
63 | i = None # error: [override-of-final-method]
|
info: `Bad.g` is decorated with `@final`, forbidding overrides
--> src/main.py:37:9
|
35 | def g(self, x: int) -> int: ...
36 | # error: [invalid-overload]
37 | def g(self, x: int | str) -> int | str:
| - `Bad.g` defined here
38 | return x
|
help: Remove the override of `g`
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
```
```
error[override-of-final-method]: Cannot override `Bad.h`
--> src/main.py:62:5
|
60 | f = None # error: [override-of-final-method]
61 | g = None # error: [override-of-final-method]
62 | h = None # error: [override-of-final-method]
| ^ Overrides a definition from superclass `Bad`
63 | i = None # error: [override-of-final-method]
|
info: `Bad.h` is decorated with `@final`, forbidding overrides
--> src/main.py:46:9
|
44 | def h(self, x: int) -> int: ...
45 | # error: [invalid-overload]
46 | def h(self, x: int | str) -> int | str:
| - `Bad.h` defined here
47 | return x
|
help: Remove the override of `h`
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
```
```
error[override-of-final-method]: Cannot override `Bad.i`
--> src/main.py:63:5
|
61 | g = None # error: [override-of-final-method]
62 | h = None # error: [override-of-final-method]
63 | i = None # error: [override-of-final-method]
| ^ Overrides a definition from superclass `Bad`
|
info: `Bad.i` is decorated with `@final`, forbidding overrides
--> src/main.py:55:9
|
53 | def i(self, x: int) -> int: ...
54 | # error: [invalid-overload]
55 | def i(self, x: int | str) -> int | str:
| - `Bad.i` defined here
56 | return x
|
help: Remove the override of `i`
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
```