mirror of
https://github.com/astral-sh/ruff
synced 2026-01-07 22:54:28 -05:00
[ty] Implement typing.final for methods (#21646)
Co-authored-by: Micha Reiser <micha@reiser.io>
This commit is contained in:
@@ -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]
|
||||
```
|
||||
|
||||
@@ -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
|
||||
|
||||
```
|
||||
@@ -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
|
||||
|
||||
```
|
||||
@@ -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
|
||||
|
||||
```
|
||||
@@ -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
|
||||
|
||||
```
|
||||
@@ -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
|
||||
|
||||
```
|
||||
Reference in New Issue
Block a user