diff --git a/crates/ty/docs/rules.md b/crates/ty/docs/rules.md
index 178fb687ba9914..759e5754ef2326 100644
--- a/crates/ty/docs/rules.md
+++ b/crates/ty/docs/rules.md
@@ -8,7 +8,7 @@
Default level: error ·
Added in 0.0.13 ·
Related issues ·
-View source
+View source
@@ -49,7 +49,7 @@ class Derived(Base): # Error: `Derived` does not implement `method`
Default level: warn ·
Added in 0.0.1-alpha.20 ·
Related issues ·
-View source
+View source
@@ -90,7 +90,7 @@ class SubProto(BaseProto, Protocol):
Default level: error ·
Added in 0.0.14 ·
Related issues ·
-View source
+View source
@@ -157,7 +157,7 @@ def test(): -> "int":
Default level: error ·
Preview (since 0.0.16) ·
Related issues ·
-View source
+View source
@@ -206,7 +206,7 @@ Foo.method() # Error: cannot call abstract classmethod
Default level: error ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -230,7 +230,7 @@ Calling a non-callable object will raise a `TypeError` at runtime.
Default level: error ·
Added in 0.0.7 ·
Related issues ·
-View source
+View source
@@ -261,7 +261,7 @@ def f(x: object):
Default level: error ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -293,7 +293,7 @@ f(int) # error
Default level: error ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -324,7 +324,7 @@ a = 1
Default level: error ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -356,7 +356,7 @@ class C(A, B): ...
Default level: error ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -388,7 +388,7 @@ class B(A): ...
Default level: error ·
Added in 0.0.1-alpha.29 ·
Related issues ·
-View source
+View source
@@ -416,7 +416,7 @@ type B = A
Default level: error ·
Preview (since 1.0.0) ·
Related issues ·
-View source
+View source
@@ -448,7 +448,7 @@ class Example:
Default level: warn ·
Added in 0.0.1-alpha.16 ·
Related issues ·
-View source
+View source
@@ -475,7 +475,7 @@ old_func() # emits [deprecated] diagnostic
Default level: ignore ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -504,7 +504,7 @@ false positives it can produce.
Default level: error ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -531,7 +531,7 @@ class B(A, A): ...
Default level: error ·
Added in 0.0.1-alpha.12 ·
Related issues ·
-View source
+View source
@@ -569,7 +569,7 @@ class A: # Crash at runtime
Default level: error ·
Added in 0.0.14 ·
Related issues ·
-View source
+View source
@@ -634,13 +634,45 @@ Static analysis tools like ty can't analyze type annotations that contain escape
def foo() -> "intt\b": ...
```
+## `final-on-non-method`
+
+
+Default level: error ·
+Added in 0.0.20 ·
+Related issues ·
+View source
+
+
+
+**What it does**
+
+Checks for `@final` decorators applied to non-method functions.
+
+**Why is this bad?**
+
+The `@final` decorator is only meaningful on methods and classes.
+Applying it to a module-level function or a nested function has no
+effect and is likely a mistake.
+
+**Example**
+
+
+```python
+from typing import final
+
+# Error: @final is not allowed on non-method functions
+@final
+def my_function() -> int:
+ return 0
+```
+
## `final-without-value`
Default level: error ·
Added in 0.0.15 ·
Related issues ·
-View source
+View source
@@ -766,7 +798,7 @@ def test(): -> "Literal[5]":
Default level: error ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -796,7 +828,7 @@ class C(A, B): ...
Default level: error ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -822,7 +854,7 @@ t[3] # IndexError: tuple index out of range
Default level: warn ·
Added in 0.0.1-alpha.33 ·
Related issues ·
-View source
+View source
@@ -856,7 +888,7 @@ class MyClass: ...
Default level: error ·
Added in 0.0.1-alpha.12 ·
Related issues ·
-View source
+View source
@@ -945,7 +977,7 @@ an atypical memory layout.
Default level: error ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -972,7 +1004,7 @@ func("foo") # error: [invalid-argument-type]
Default level: error ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -1000,7 +1032,7 @@ a: int = ''
Default level: error ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -1034,7 +1066,7 @@ C.instance_var = 3 # error: Cannot assign to instance variable
Default level: error ·
Added in 0.0.1-alpha.19 ·
Related issues ·
-View source
+View source
@@ -1070,7 +1102,7 @@ asyncio.run(main())
Default level: error ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -1094,7 +1126,7 @@ class A(42): ... # error: [invalid-base]
Default level: error ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -1121,7 +1153,7 @@ with 1:
Default level: error ·
Added in 0.0.12 ·
Related issues ·
-View source
+View source
@@ -1158,7 +1190,7 @@ class Foo(NamedTuple):
Default level: error ·
Added in 0.0.13 ·
Related issues ·
-View source
+View source
@@ -1190,7 +1222,7 @@ class A:
Default level: error ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -1219,7 +1251,7 @@ a: str
Default level: error ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -1263,7 +1295,7 @@ except ZeroDivisionError:
Default level: error ·
Added in 0.0.1-alpha.28 ·
Related issues ·
-View source
+View source
@@ -1305,7 +1337,7 @@ class D(A):
Default level: error ·
Added in 0.0.1-alpha.35 ·
Related issues ·
-View source
+View source
@@ -1349,7 +1381,7 @@ class NonFrozenChild(FrozenBase): # Error raised here
Default level: error ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -1387,7 +1419,7 @@ class D(Generic[U, T]): ...
Default level: error ·
Added in 0.0.12 ·
Related issues ·
-View source
+View source
@@ -1466,7 +1498,7 @@ a = 20 / 0 # type: ignore
Default level: error ·
Added in 0.0.1-alpha.17 ·
Related issues ·
-View source
+View source
@@ -1505,7 +1537,7 @@ carol = Person(name="Carol", age=25) # typo!
Default level: warn ·
Added in 0.0.15 ·
Related issues ·
-View source
+View source
@@ -1566,7 +1598,7 @@ def f(x, y, /): # Python 3.8+ syntax
Default level: error ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -1601,7 +1633,7 @@ def f(t: TypeVar("U")): ...
Default level: error ·
Added in 0.0.18 ·
Related issues ·
-View source
+View source
@@ -1629,7 +1661,7 @@ match x:
Default level: error ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -1663,7 +1695,7 @@ class B(metaclass=f): ...
Default level: error ·
Added in 0.0.1-alpha.20 ·
Related issues ·
-View source
+View source
@@ -1770,7 +1802,7 @@ Correct use of `@override` is enforced by ty's `invalid-explicit-override` rule.
Default level: error ·
Added in 0.0.1-alpha.19 ·
Related issues ·
-View source
+View source
@@ -1824,7 +1856,7 @@ AttributeError: Cannot overwrite NamedTuple attribute _asdict
Default level: error ·
Added in 0.0.1-alpha.27 ·
Related issues ·
-View source
+View source
@@ -1854,7 +1886,7 @@ Baz = NewType("Baz", int | str) # error: invalid base for `typing.NewType`
Default level: error ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -1904,7 +1936,7 @@ def foo(x: int) -> int: ...
Default level: error ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -1930,7 +1962,7 @@ def f(a: int = ''): ...
Default level: error ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -1961,7 +1993,7 @@ P2 = ParamSpec("S2") # error: ParamSpec name must match the variable it's assig
Default level: error ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -1995,7 +2027,7 @@ TypeError: Protocols can only inherit from other protocols, got
Default level: error ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -2044,7 +2076,7 @@ def g():
Default level: error ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -2073,7 +2105,7 @@ def func() -> int:
Default level: error ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -2169,7 +2201,7 @@ class C: ...
Default level: error ·
Added in 0.0.10 ·
Related issues ·
-View source
+View source
@@ -2215,7 +2247,7 @@ class MyClass:
Default level: error ·
Added in 0.0.1-alpha.6 ·
Related issues ·
-View source
+View source
@@ -2242,7 +2274,7 @@ NewAlias = TypeAliasType(get_name(), int) # error: TypeAliasType name mus
Default level: error ·
Added in 0.0.1-alpha.29 ·
Related issues ·
-View source
+View source
@@ -2289,7 +2321,7 @@ Bar[int] # error: too few arguments
Default level: error ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -2319,7 +2351,7 @@ TYPE_CHECKING = ''
Default level: error ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -2349,7 +2381,7 @@ b: Annotated[int] # `Annotated` expects at least two arguments
Default level: error ·
Added in 0.0.1-alpha.11 ·
Related issues ·
-View source
+View source
@@ -2383,7 +2415,7 @@ f(10) # Error
Default level: error ·
Added in 0.0.1-alpha.11 ·
Related issues ·
-View source
+View source
@@ -2417,7 +2449,7 @@ class C:
Default level: error ·
Added in 0.0.15 ·
Related issues ·
-View source
+View source
@@ -2448,7 +2480,7 @@ def g[U, T: U](): ... # error: [invalid-type-variable-bound]
Default level: error ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -2495,7 +2527,7 @@ U = TypeVar('U', list[int], int) # valid constrained Type
Default level: error ·
Added in 0.0.16 ·
Related issues ·
-View source
+View source
@@ -2527,7 +2559,7 @@ U = TypeVar("U", int, str, default=bytes) # error: [invalid-type-variable-defau
Default level: error ·
Added in 0.0.14 ·
Related issues ·
-View source
+View source
@@ -2562,7 +2594,7 @@ def f(x: dict):
Default level: error ·
Added in 0.0.9 ·
Related issues ·
-View source
+View source
@@ -2593,7 +2625,7 @@ class Foo(TypedDict):
Default level: error ·
Added in 0.0.14 ·
Related issues ·
-View source
+View source
@@ -2648,7 +2680,7 @@ def h(arg2: type):
Default level: error ·
Added in 0.0.15 ·
Related issues ·
-View source
+View source
@@ -2691,7 +2723,7 @@ def g(arg: object):
Default level: error ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -2716,7 +2748,7 @@ func() # TypeError: func() missing 1 required positional argument: 'x'
Default level: error ·
Added in 0.0.1-alpha.20 ·
Related issues ·
-View source
+View source
@@ -2749,7 +2781,7 @@ alice["age"] # KeyError
Default level: error ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -2778,7 +2810,7 @@ func("string") # error: [no-matching-overload]
Default level: error ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -2804,7 +2836,7 @@ for i in 34: # TypeError: 'int' object is not iterable
Default level: error ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -2828,7 +2860,7 @@ Subscripting an object that does not support it will raise a `TypeError` at runt
Default level: error ·
Added in 0.0.1-alpha.29 ·
Related issues ·
-View source
+View source
@@ -2861,7 +2893,7 @@ class B(A):
Default level: error ·
Added in 0.0.16 ·
Related issues ·
-View source
+View source
@@ -2894,7 +2926,7 @@ class B(A):
Default level: error ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -2921,7 +2953,7 @@ f(1, x=2) # Error raised here
Default level: error ·
Added in 0.0.1-alpha.22 ·
Related issues ·
-View source
+View source
@@ -2948,7 +2980,7 @@ f(x=1) # Error raised here
Default level: warn ·
Added in 0.0.1-alpha.22 ·
Related issues ·
-View source
+View source
@@ -2976,7 +3008,7 @@ A.c # AttributeError: type object 'A' has no attribute 'c'
Default level: warn ·
Added in 0.0.1-alpha.22 ·
Related issues ·
-View source
+View source
@@ -3008,7 +3040,7 @@ A()[0] # TypeError: 'A' object is not subscriptable
Default level: ignore ·
Added in 0.0.1-alpha.22 ·
Related issues ·
-View source
+View source
@@ -3045,7 +3077,7 @@ from module import a # ImportError: cannot import name 'a' from 'module'
Default level: ignore ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -3109,7 +3141,7 @@ def test(): -> "int":
Default level: warn ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -3136,7 +3168,7 @@ cast(int, f()) # Redundant
Default level: warn ·
Added in 0.0.18 ·
Related issues ·
-View source
+View source
@@ -3168,7 +3200,7 @@ class C:
Default level: error ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -3198,7 +3230,7 @@ static_assert(int(2.0 * 3.0) == 6) # error: does not have a statically known tr
Default level: error ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -3227,7 +3259,7 @@ class B(A): ... # Error raised here
Default level: error ·
Added in 0.0.1-alpha.30 ·
Related issues ·
-View source
+View source
@@ -3261,7 +3293,7 @@ class F(NamedTuple):
Default level: error ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -3288,7 +3320,7 @@ f("foo") # Error raised here
Default level: error ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -3316,7 +3348,7 @@ def _(x: int):
Default level: error ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -3362,7 +3394,7 @@ class A:
Default level: warn ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -3386,7 +3418,7 @@ reveal_type(1) # NameError: name 'reveal_type' is not defined
Default level: error ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -3413,7 +3445,7 @@ f(x=1, y=2) # Error raised here
Default level: error ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -3441,7 +3473,7 @@ A().foo # AttributeError: 'A' object has no attribute 'foo'
Default level: warn ·
Added in 0.0.1-alpha.15 ·
Related issues ·
-View source
+View source
@@ -3499,7 +3531,7 @@ def g():
Default level: error ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -3524,7 +3556,7 @@ import foo # ModuleNotFoundError: No module named 'foo'
Default level: error ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -3549,7 +3581,7 @@ print(x) # NameError: name 'x' is not defined
Default level: warn ·
Added in 0.0.1-alpha.7 ·
Related issues ·
-View source
+View source
@@ -3588,7 +3620,7 @@ class D(C): ... # error: [unsupported-base]
Default level: error ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -3625,7 +3657,7 @@ b1 < b2 < b1 # exception raised here
Default level: ignore ·
Added in 0.0.12 ·
Related issues ·
-View source
+View source
@@ -3666,7 +3698,7 @@ def factory(base: type[Base]) -> type:
Default level: error ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -3767,7 +3799,7 @@ to `false`.
Default level: warn ·
Added in 0.0.1-alpha.22 ·
Related issues ·
-View source
+View source
@@ -3830,7 +3862,7 @@ def foo(x: int | str) -> int | str:
Default level: error ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
diff --git a/crates/ty_python_semantic/resources/mdtest/final.md b/crates/ty_python_semantic/resources/mdtest/final.md
index 1b6081908f339b..78149aabcd1bc0 100644
--- a/crates/ty_python_semantic/resources/mdtest/final.md
+++ b/crates/ty_python_semantic/resources/mdtest/final.md
@@ -428,6 +428,29 @@ class D(B): # error: [subclass-of-final-class]
def method(self): ... # error: [override-of-final-variable]
```
+## `@final` cannot be applied to non-method functions
+
+The `@final` decorator is only valid on methods and classes. Using it on a module-level or nested
+function is an error.
+
+```py
+from typing import final
+
+@final # error: [final-on-non-method] "`@final` cannot be applied to non-method function `func1`"
+def func1(): ...
+
+# Nested function decorated with `@final` is also invalid
+def outer():
+ @final # error: [final-on-non-method]
+ def inner(): ...
+
+# A function nested inside a method is also not a method
+class F:
+ def method(self):
+ @final # error: [final-on-non-method]
+ def not_a_method(): ...
+```
+
## An `@final` method is overridden by an implicit instance attribute
```py
diff --git a/crates/ty_python_semantic/src/types/diagnostic.rs b/crates/ty_python_semantic/src/types/diagnostic.rs
index 6fcfec2fcc7d43..0485f2fad1d400 100644
--- a/crates/ty_python_semantic/src/types/diagnostic.rs
+++ b/crates/ty_python_semantic/src/types/diagnostic.rs
@@ -119,6 +119,7 @@ pub(crate) fn register_lints(registry: &mut LintRegistryBuilder) {
registry.register_lint(&OVERRIDE_OF_FINAL_METHOD);
registry.register_lint(&OVERRIDE_OF_FINAL_VARIABLE);
registry.register_lint(&INEFFECTIVE_FINAL);
+ registry.register_lint(&FINAL_ON_NON_METHOD);
registry.register_lint(&FINAL_WITHOUT_VALUE);
registry.register_lint(&ABSTRACT_METHOD_IN_FINAL_CLASS);
registry.register_lint(&CALL_ABSTRACT_METHOD);
@@ -2162,6 +2163,32 @@ declare_lint! {
}
}
+declare_lint! {
+ /// ## What it does
+ /// Checks for `@final` decorators applied to non-method functions.
+ ///
+ /// ## Why is this bad?
+ /// The `@final` decorator is only meaningful on methods and classes.
+ /// Applying it to a module-level function or a nested function has no
+ /// effect and is likely a mistake.
+ ///
+ /// ## Example
+ ///
+ /// ```python
+ /// from typing import final
+ ///
+ /// # Error: @final is not allowed on non-method functions
+ /// @final
+ /// def my_function() -> int:
+ /// return 0
+ /// ```
+ pub(crate) static FINAL_ON_NON_METHOD = {
+ summary: "detects `@final` applied to non-method functions",
+ status: LintStatus::stable("0.0.20"),
+ default_level: Level::Error,
+ }
+}
+
declare_lint! {
/// ## What it does
/// Checks for `Final` symbols that are declared without a value and are never
diff --git a/crates/ty_python_semantic/src/types/infer/builder.rs b/crates/ty_python_semantic/src/types/infer/builder.rs
index cc66d51609d0e3..8d456ef5a410a8 100644
--- a/crates/ty_python_semantic/src/types/infer/builder.rs
+++ b/crates/ty_python_semantic/src/types/infer/builder.rs
@@ -73,21 +73,21 @@ use crate::types::diagnostic::{
self, ABSTRACT_METHOD_IN_FINAL_CLASS, CALL_NON_CALLABLE, CONFLICTING_DECLARATIONS,
CONFLICTING_METACLASS, CYCLIC_CLASS_DEFINITION, CYCLIC_TYPE_ALIAS_DEFINITION,
DATACLASS_FIELD_ORDER, DIVISION_BY_ZERO, DUPLICATE_BASE, DUPLICATE_KW_ONLY,
- FINAL_WITHOUT_VALUE, INCONSISTENT_MRO, INEFFECTIVE_FINAL, INVALID_ARGUMENT_TYPE,
- INVALID_ASSIGNMENT, INVALID_ATTRIBUTE_ACCESS, INVALID_BASE, INVALID_DATACLASS,
- INVALID_DECLARATION, INVALID_GENERIC_CLASS, INVALID_GENERIC_ENUM, INVALID_KEY,
- INVALID_LEGACY_POSITIONAL_PARAMETER, INVALID_LEGACY_TYPE_VARIABLE, INVALID_METACLASS,
- INVALID_NAMED_TUPLE, INVALID_NEWTYPE, INVALID_OVERLOAD, INVALID_PARAMETER_DEFAULT,
- INVALID_PARAMSPEC, INVALID_PROTOCOL, INVALID_TYPE_ALIAS_TYPE, INVALID_TYPE_ARGUMENTS,
- INVALID_TYPE_FORM, INVALID_TYPE_GUARD_CALL, INVALID_TYPE_GUARD_DEFINITION,
- INVALID_TYPE_VARIABLE_BOUND, INVALID_TYPE_VARIABLE_CONSTRAINTS, INVALID_TYPE_VARIABLE_DEFAULT,
- INVALID_TYPED_DICT_HEADER, INVALID_TYPED_DICT_STATEMENT, IncompatibleBases, MISSING_ARGUMENT,
- NO_MATCHING_OVERLOAD, NOT_SUBSCRIPTABLE, PARAMETER_ALREADY_ASSIGNED,
- POSSIBLY_MISSING_ATTRIBUTE, POSSIBLY_MISSING_IMPLICIT_CALL, POSSIBLY_MISSING_IMPORT,
- SUBCLASS_OF_FINAL_CLASS, TOO_MANY_POSITIONAL_ARGUMENTS, TypedDictDeleteErrorKind,
- UNDEFINED_REVEAL, UNKNOWN_ARGUMENT, UNRESOLVED_ATTRIBUTE, UNRESOLVED_GLOBAL, UNRESOLVED_IMPORT,
- UNRESOLVED_REFERENCE, UNSUPPORTED_DYNAMIC_BASE, UNSUPPORTED_OPERATOR, USELESS_OVERLOAD_BODY,
- hint_if_stdlib_attribute_exists_on_other_versions,
+ FINAL_ON_NON_METHOD, FINAL_WITHOUT_VALUE, INCONSISTENT_MRO, INEFFECTIVE_FINAL,
+ INVALID_ARGUMENT_TYPE, INVALID_ASSIGNMENT, INVALID_ATTRIBUTE_ACCESS, INVALID_BASE,
+ INVALID_DATACLASS, INVALID_DECLARATION, INVALID_GENERIC_CLASS, INVALID_GENERIC_ENUM,
+ INVALID_KEY, INVALID_LEGACY_POSITIONAL_PARAMETER, INVALID_LEGACY_TYPE_VARIABLE,
+ INVALID_METACLASS, INVALID_NAMED_TUPLE, INVALID_NEWTYPE, INVALID_OVERLOAD,
+ INVALID_PARAMETER_DEFAULT, INVALID_PARAMSPEC, INVALID_PROTOCOL, INVALID_TYPE_ALIAS_TYPE,
+ INVALID_TYPE_ARGUMENTS, INVALID_TYPE_FORM, INVALID_TYPE_GUARD_CALL,
+ INVALID_TYPE_GUARD_DEFINITION, INVALID_TYPE_VARIABLE_BOUND, INVALID_TYPE_VARIABLE_CONSTRAINTS,
+ INVALID_TYPE_VARIABLE_DEFAULT, INVALID_TYPED_DICT_HEADER, INVALID_TYPED_DICT_STATEMENT,
+ IncompatibleBases, MISSING_ARGUMENT, NO_MATCHING_OVERLOAD, NOT_SUBSCRIPTABLE,
+ PARAMETER_ALREADY_ASSIGNED, POSSIBLY_MISSING_ATTRIBUTE, POSSIBLY_MISSING_IMPLICIT_CALL,
+ POSSIBLY_MISSING_IMPORT, SUBCLASS_OF_FINAL_CLASS, TOO_MANY_POSITIONAL_ARGUMENTS,
+ TypedDictDeleteErrorKind, UNDEFINED_REVEAL, UNKNOWN_ARGUMENT, UNRESOLVED_ATTRIBUTE,
+ UNRESOLVED_GLOBAL, UNRESOLVED_IMPORT, UNRESOLVED_REFERENCE, UNSUPPORTED_DYNAMIC_BASE,
+ UNSUPPORTED_OPERATOR, USELESS_OVERLOAD_BODY, hint_if_stdlib_attribute_exists_on_other_versions,
hint_if_stdlib_submodule_exists_on_other_versions, report_attempted_protocol_instantiation,
report_bad_dunder_set_call, report_bad_frozen_dataclass_inheritance,
report_call_to_abstract_method, report_cannot_delete_typed_dict_key,
@@ -3186,6 +3186,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
let mut function_decorators = FunctionDecorators::empty();
let mut deprecated = None;
let mut dataclass_transformer_params = None;
+ let mut final_decorator = None;
for decorator in decorator_list {
let decorator_type = self.infer_decorator(decorator);
@@ -3194,14 +3195,19 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
function_decorators |= decorator_function_decorator;
match decorator_type {
- Type::FunctionLiteral(function) => {
- if let Some(KnownFunction::NoTypeCheck) = function.known(self.db()) {
+ Type::FunctionLiteral(function) => match function.known(self.db()) {
+ Some(KnownFunction::NoTypeCheck) => {
// If the function is decorated with the `no_type_check` decorator,
// we need to suppress any errors that come after the decorators.
self.context.set_in_no_type_check(InNoTypeCheck::Yes);
continue;
}
- }
+ Some(KnownFunction::Final) => {
+ final_decorator = Some(decorator);
+ continue;
+ }
+ _ => {}
+ },
Type::KnownInstance(KnownInstanceType::Deprecated(deprecated_inst)) => {
deprecated = Some(deprecated_inst);
}
@@ -3217,6 +3223,24 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
decorator_types_and_nodes.push((decorator_type, decorator));
}
+ // Check for `@final` applied to non-method functions.
+ // `@final` is only meaningful on methods and classes.
+ if let Some(final_decorator) = final_decorator
+ && !self
+ .index
+ .scope(self.scope().file_scope_id(self.db()))
+ .kind()
+ .is_class()
+ && let Some(builder) = self
+ .context
+ .report_lint(&FINAL_ON_NON_METHOD, final_decorator)
+ {
+ let mut diagnostic = builder.into_diagnostic(format_args!(
+ "`@final` cannot be applied to non-method function `{name}`",
+ ));
+ diagnostic.info("`@final` is only meaningful on methods and classes");
+ }
+
let has_defaults = parameters
.iter_non_variadic_params()
.any(|param| param.default.is_some());
diff --git a/ty.schema.json b/ty.schema.json
index cd9f861c8c7a3d..ff954b6d68d2ab 100644
--- a/ty.schema.json
+++ b/ty.schema.json
@@ -555,6 +555,16 @@
}
]
},
+ "final-on-non-method": {
+ "title": "detects `@final` applied to non-method functions",
+ "description": "## What it does\nChecks for `@final` decorators applied to non-method functions.\n\n## Why is this bad?\nThe `@final` decorator is only meaningful on methods and classes.\nApplying it to a module-level function or a nested function has no\neffect and is likely a mistake.\n\n## Example\n\n```python\nfrom typing import final\n\n# Error: @final is not allowed on non-method functions\n@final\ndef my_function() -> int:\n return 0\n```",
+ "default": "error",
+ "oneOf": [
+ {
+ "$ref": "#/definitions/Level"
+ }
+ ]
+ },
"final-without-value": {
"title": "detects `Final` declarations without a value",
"description": "## What it does\nChecks for `Final` symbols that are declared without a value and are never\nassigned a value in their scope.\n\n## Why is this bad?\nA `Final` symbol must be initialized with a value at the time of declaration\nor in a subsequent assignment. At module or function scope, the assignment must\noccur in the same scope. In a class body, the assignment may occur in `__init__`.\n\n## Examples\n```python\nfrom typing import Final\n\n# Error: `Final` symbol without a value\nMY_CONSTANT: Final[int]\n\n# OK: `Final` symbol with a value\nMY_CONSTANT: Final[int] = 1\n```",