diff --git a/crates/ty/docs/rules.md b/crates/ty/docs/rules.md
index 4f79aafd9f..7a482ad385 100644
--- a/crates/ty/docs/rules.md
+++ b/crates/ty/docs/rules.md
@@ -8,7 +8,7 @@
Default level: warn ·
Added in 0.0.1-alpha.20 ·
Related issues ·
-View source
+View source
@@ -80,7 +80,7 @@ def test(): -> "int":
Default level: error ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -104,7 +104,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
@@ -135,7 +135,7 @@ def f(x: object):
Default level: error ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -167,7 +167,7 @@ f(int) # error
Default level: error ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -198,7 +198,7 @@ a = 1
Default level: error ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -230,7 +230,7 @@ class C(A, B): ...
Default level: error ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -262,7 +262,7 @@ class B(A): ...
Default level: error ·
Preview (since 1.0.0) ·
Related issues ·
-View source
+View source
@@ -290,7 +290,7 @@ type B = A
Default level: warn ·
Added in 0.0.1-alpha.16 ·
Related issues ·
-View source
+View source
@@ -317,7 +317,7 @@ old_func() # emits [deprecated] diagnostic
Default level: ignore ·
Preview (since 0.0.1-alpha.1) ·
Related issues ·
-View source
+View source
@@ -346,7 +346,7 @@ false positives it can produce.
Default level: error ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -373,7 +373,7 @@ class B(A, A): ...
Default level: error ·
Added in 0.0.1-alpha.12 ·
Related issues ·
-View source
+View source
@@ -529,7 +529,7 @@ def test(): -> "Literal[5]":
Default level: error ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -559,7 +559,7 @@ class C(A, B): ...
Default level: error ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -579,13 +579,47 @@ t = (0, 1, 2)
t[3] # IndexError: tuple index out of range
```
+## `ineffective-final`
+
+
+Default level: warn ·
+Preview (since 0.0.1-alpha.33) ·
+Related issues ·
+View source
+
+
+
+**What it does**
+
+Checks for calls to `final()` that type checkers cannot interpret.
+
+**Why is this bad?**
+
+The `final()` function is designed to be used as a decorator. When called directly
+as a function (e.g., `final(type(...))`), type checkers will not understand the
+application of `final` and will not prevent subclassing.
+
+**Example**
+
+
+```python
+from typing import final
+
+# Incorrect: type checkers will not prevent subclassing
+MyClass = final(type("MyClass", (), {}))
+
+# Correct: use `final` as a decorator
+@final
+class MyClass: ...
+```
+
## `instance-layout-conflict`
Default level: error ·
Added in 0.0.1-alpha.12 ·
Related issues ·
-View source
+View source
@@ -674,7 +708,7 @@ an atypical memory layout.
Default level: error ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -701,7 +735,7 @@ func("foo") # error: [invalid-argument-type]
Default level: error ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -729,7 +763,7 @@ a: int = ''
Default level: error ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -763,7 +797,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
@@ -799,7 +833,7 @@ asyncio.run(main())
Default level: error ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -823,7 +857,7 @@ class A(42): ... # error: [invalid-base]
Default level: error ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -850,7 +884,7 @@ with 1:
Default level: error ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -879,7 +913,7 @@ a: str
Default level: error ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -923,7 +957,7 @@ except ZeroDivisionError:
Default level: error ·
Added in 0.0.1-alpha.28 ·
Related issues ·
-View source
+View source
@@ -965,7 +999,7 @@ class D(A):
Default level: error ·
Added in 0.0.1-alpha.35 ·
Related issues ·
-View source
+View source
@@ -1009,7 +1043,7 @@ class NonFrozenChild(FrozenBase): # Error raised here
Default level: error ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -1047,7 +1081,7 @@ class D(Generic[U, T]): ...
Default level: error ·
Added in 0.0.12 ·
Related issues ·
-View source
+View source
@@ -1126,7 +1160,7 @@ a = 20 / 0 # type: ignore
Default level: error ·
Added in 0.0.1-alpha.17 ·
Related issues ·
-View source
+View source
@@ -1165,7 +1199,7 @@ carol = Person(name="Carol", age=25) # typo!
Default level: error ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -1200,7 +1234,7 @@ def f(t: TypeVar("U")): ...
Default level: error ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -1234,7 +1268,7 @@ class B(metaclass=f): ...
Default level: error ·
Added in 0.0.1-alpha.20 ·
Related issues ·
-View source
+View source
@@ -1341,7 +1375,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
@@ -1395,7 +1429,7 @@ AttributeError: Cannot overwrite NamedTuple attribute _asdict
Default level: error ·
Preview (since 1.0.0) ·
Related issues ·
-View source
+View source
@@ -1425,7 +1459,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
@@ -1475,7 +1509,7 @@ def foo(x: int) -> int: ...
Default level: error ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -1501,7 +1535,7 @@ def f(a: int = ''): ...
Default level: error ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -1532,7 +1566,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
@@ -1566,7 +1600,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
@@ -1615,7 +1649,7 @@ def g():
Default level: error ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -1640,7 +1674,7 @@ def func() -> int:
Default level: error ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -1736,7 +1770,7 @@ class C: ...
Default level: error ·
Added in 0.0.10 ·
Related issues ·
-View source
+View source
@@ -1782,7 +1816,7 @@ class MyClass:
Default level: error ·
Added in 0.0.1-alpha.6 ·
Related issues ·
-View source
+View source
@@ -1809,7 +1843,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
@@ -1856,7 +1890,7 @@ Bar[int] # error: too few arguments
Default level: error ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -1886,7 +1920,7 @@ TYPE_CHECKING = ''
Default level: error ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -1916,7 +1950,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
@@ -1950,7 +1984,7 @@ f(10) # Error
Default level: error ·
Added in 0.0.1-alpha.11 ·
Related issues ·
-View source
+View source
@@ -1984,7 +2018,7 @@ class C:
Default level: error ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -2019,7 +2053,7 @@ T = TypeVar('T', bound=str) # valid bound TypeVar
Default level: error ·
Added in 0.0.9 ·
Related issues ·
-View source
+View source
@@ -2050,7 +2084,7 @@ class Foo(TypedDict):
Default level: error ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -2075,7 +2109,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
@@ -2108,7 +2142,7 @@ alice["age"] # KeyError
Default level: error ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -2137,7 +2171,7 @@ func("string") # error: [no-matching-overload]
Default level: error ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -2163,7 +2197,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
@@ -2187,7 +2221,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
@@ -2220,7 +2254,7 @@ class B(A):
Default level: error ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -2247,7 +2281,7 @@ f(1, x=2) # Error raised here
Default level: error ·
Added in 0.0.1-alpha.22 ·
Related issues ·
-View source
+View source
@@ -2274,7 +2308,7 @@ f(x=1) # Error raised here
Default level: warn ·
Added in 0.0.1-alpha.22 ·
Related issues ·
-View source
+View source
@@ -2302,7 +2336,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
@@ -2334,7 +2368,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
@@ -2371,7 +2405,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
@@ -2435,7 +2469,7 @@ def test(): -> "int":
Default level: warn ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -2462,7 +2496,7 @@ cast(int, f()) # Redundant
Default level: error ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -2492,7 +2526,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
@@ -2521,7 +2555,7 @@ class B(A): ... # Error raised here
Default level: error ·
Preview (since 0.0.1-alpha.30) ·
Related issues ·
-View source
+View source
@@ -2555,7 +2589,7 @@ class F(NamedTuple):
Default level: error ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -2582,7 +2616,7 @@ f("foo") # Error raised here
Default level: error ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -2610,7 +2644,7 @@ def _(x: int):
Default level: error ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -2656,7 +2690,7 @@ class A:
Default level: warn ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -2680,7 +2714,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
@@ -2707,7 +2741,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
@@ -2735,7 +2769,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
@@ -2793,7 +2827,7 @@ def g():
Default level: error ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -2818,7 +2852,7 @@ import foo # ModuleNotFoundError: No module named 'foo'
Default level: error ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -2843,7 +2877,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
@@ -2882,7 +2916,7 @@ class D(C): ... # error: [unsupported-base]
Default level: error ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -2919,7 +2953,7 @@ b1 < b2 < b1 # exception raised here
Default level: ignore ·
Preview (since 1.0.0) ·
Related issues ·
-View source
+View source
@@ -2960,7 +2994,7 @@ def factory(base: type[Base]) -> type:
Default level: error ·
Added in 0.0.1-alpha.1 ·
Related issues ·
-View source
+View source
@@ -3024,7 +3058,7 @@ to `false` to prevent this rule from reporting unused `type: ignore` comments.
Default level: warn ·
Added in 0.0.1-alpha.22 ·
Related issues ·
-View source
+View source
@@ -3087,7 +3121,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/call/type.md b/crates/ty_python_semantic/resources/mdtest/call/type.md
index b4139f2cf7..828e050202 100644
--- a/crates/ty_python_semantic/resources/mdtest/call/type.md
+++ b/crates/ty_python_semantic/resources/mdtest/call/type.md
@@ -1072,23 +1072,23 @@ reveal_type(Dynamic.custom_attr) # revealed: str
## `final()` on dynamic classes
-Using `final()` as a function (not a decorator) on dynamic classes has no effect. The class is
-passed through unchanged:
+Using `final()` as a function (not a decorator) on dynamic classes will not be understood by type
+checkers. The class is passed through unchanged, and subclassing will not be prevented:
```py
from typing import final
-# TODO: Add a diagnostic for ineffective use of `final()` here.
+# error: [ineffective-final]
FinalClass = final(type("FinalClass", (), {}))
reveal_type(FinalClass) # revealed:
-# Subclassing is allowed because `final()` as a function has no effect
+# Subclassing is allowed because type checkers don't understand `final()` called as a function
class Child(FinalClass): ...
# Same with base classes
class Base: ...
-# TODO: Add a diagnostic for ineffective use of `final()` here.
+# error: [ineffective-final]
FinalDerived = final(type("FinalDerived", (Base,), {}))
class Child2(FinalDerived): ...
diff --git a/crates/ty_python_semantic/src/types/diagnostic.rs b/crates/ty_python_semantic/src/types/diagnostic.rs
index e1bed92bc3..63965d7a0b 100644
--- a/crates/ty_python_semantic/src/types/diagnostic.rs
+++ b/crates/ty_python_semantic/src/types/diagnostic.rs
@@ -104,6 +104,7 @@ pub(crate) fn register_lints(registry: &mut LintRegistryBuilder) {
registry.register_lint(&POSSIBLY_UNRESOLVED_REFERENCE);
registry.register_lint(&SUBCLASS_OF_FINAL_CLASS);
registry.register_lint(&OVERRIDE_OF_FINAL_METHOD);
+ registry.register_lint(&INEFFECTIVE_FINAL);
registry.register_lint(&TYPE_ASSERTION_FAILURE);
registry.register_lint(&TOO_MANY_POSITIONAL_ARGUMENTS);
registry.register_lint(&UNAVAILABLE_IMPLICIT_SUPER_ARGUMENTS);
@@ -1789,6 +1790,34 @@ declare_lint! {
}
}
+declare_lint! {
+ /// ## What it does
+ /// Checks for calls to `final()` that type checkers cannot interpret.
+ ///
+ /// ## Why is this bad?
+ /// The `final()` function is designed to be used as a decorator. When called directly
+ /// as a function (e.g., `final(type(...))`), type checkers will not understand the
+ /// application of `final` and will not prevent subclassing.
+ ///
+ /// ## Example
+ ///
+ /// ```python
+ /// from typing import final
+ ///
+ /// # Incorrect: type checkers will not prevent subclassing
+ /// MyClass = final(type("MyClass", (), {}))
+ ///
+ /// # Correct: use `final` as a decorator
+ /// @final
+ /// class MyClass: ...
+ /// ```
+ pub(crate) static INEFFECTIVE_FINAL = {
+ summary: "detects calls to `final()` that type checkers cannot interpret",
+ status: LintStatus::preview("0.0.1-alpha.33"),
+ default_level: Level::Warn,
+ }
+}
+
declare_lint! {
/// ## What it does
/// Checks for methods that are decorated with `@override` but do not override any method in a superclass.
diff --git a/crates/ty_python_semantic/src/types/infer/builder.rs b/crates/ty_python_semantic/src/types/infer/builder.rs
index 7c7ce9ad3a..9ae32ee3a1 100644
--- a/crates/ty_python_semantic/src/types/infer/builder.rs
+++ b/crates/ty_python_semantic/src/types/infer/builder.rs
@@ -63,17 +63,18 @@ use crate::types::cyclic::CycleDetector;
use crate::types::diagnostic::{
self, CALL_NON_CALLABLE, CONFLICTING_DECLARATIONS, CONFLICTING_METACLASS,
CYCLIC_CLASS_DEFINITION, CYCLIC_TYPE_ALIAS_DEFINITION, DIVISION_BY_ZERO, DUPLICATE_BASE,
- DUPLICATE_KW_ONLY, INCONSISTENT_MRO, INVALID_ARGUMENT_TYPE, INVALID_ASSIGNMENT,
- INVALID_ATTRIBUTE_ACCESS, INVALID_BASE, INVALID_DECLARATION, INVALID_GENERIC_CLASS,
- INVALID_GENERIC_ENUM, INVALID_KEY, INVALID_LEGACY_TYPE_VARIABLE, INVALID_METACLASS,
- INVALID_NAMED_TUPLE, INVALID_NEWTYPE, INVALID_OVERLOAD, INVALID_PARAMETER_DEFAULT,
- INVALID_PARAMSPEC, INVALID_PROTOCOL, INVALID_TYPE_ARGUMENTS, INVALID_TYPE_FORM,
- INVALID_TYPE_GUARD_CALL, INVALID_TYPE_GUARD_DEFINITION, INVALID_TYPE_VARIABLE_CONSTRAINTS,
- INVALID_TYPED_DICT_STATEMENT, IncompatibleBases, NOT_SUBSCRIPTABLE, POSSIBLY_MISSING_ATTRIBUTE,
- POSSIBLY_MISSING_IMPLICIT_CALL, POSSIBLY_MISSING_IMPORT, SUBCLASS_OF_FINAL_CLASS,
- TypedDictDeleteErrorKind, UNDEFINED_REVEAL, UNRESOLVED_ATTRIBUTE, UNRESOLVED_GLOBAL,
- UNRESOLVED_IMPORT, UNRESOLVED_REFERENCE, UNSUPPORTED_DYNAMIC_BASE, UNSUPPORTED_OPERATOR,
- USELESS_OVERLOAD_BODY, hint_if_stdlib_attribute_exists_on_other_versions,
+ DUPLICATE_KW_ONLY, INCONSISTENT_MRO, INEFFECTIVE_FINAL, INVALID_ARGUMENT_TYPE,
+ INVALID_ASSIGNMENT, INVALID_ATTRIBUTE_ACCESS, INVALID_BASE, INVALID_DECLARATION,
+ INVALID_GENERIC_CLASS, INVALID_GENERIC_ENUM, INVALID_KEY, INVALID_LEGACY_TYPE_VARIABLE,
+ INVALID_METACLASS, INVALID_NAMED_TUPLE, INVALID_NEWTYPE, INVALID_OVERLOAD,
+ INVALID_PARAMETER_DEFAULT, INVALID_PARAMSPEC, INVALID_PROTOCOL, INVALID_TYPE_ARGUMENTS,
+ INVALID_TYPE_FORM, INVALID_TYPE_GUARD_CALL, INVALID_TYPE_GUARD_DEFINITION,
+ INVALID_TYPE_VARIABLE_CONSTRAINTS, INVALID_TYPED_DICT_STATEMENT, IncompatibleBases,
+ NOT_SUBSCRIPTABLE, POSSIBLY_MISSING_ATTRIBUTE, POSSIBLY_MISSING_IMPLICIT_CALL,
+ POSSIBLY_MISSING_IMPORT, SUBCLASS_OF_FINAL_CLASS, TypedDictDeleteErrorKind, UNDEFINED_REVEAL,
+ 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_cannot_delete_typed_dict_key, report_cannot_pop_required_field_on_typed_dict,
@@ -9738,6 +9739,20 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
{
self.called_functions.insert(function);
}
+
+ // Warn when `final()` is called as a function (not a decorator).
+ // Type checkers cannot interpret this usage and will not prevent subclassing.
+ if function.is_known(self.db(), KnownFunction::Final) {
+ if let Some(builder) = self
+ .context
+ .report_lint(&INEFFECTIVE_FINAL, call_expression)
+ {
+ let mut diagnostic = builder.into_diagnostic(
+ "Type checkers will not prevent subclassing when `final()` is called as a function",
+ );
+ diagnostic.info("Use `@final` as a decorator on a class or method instead");
+ }
+ }
}
let class = match callable_type {
diff --git a/ty.schema.json b/ty.schema.json
index 969aeb31a9..341e0e2761 100644
--- a/ty.schema.json
+++ b/ty.schema.json
@@ -520,6 +520,16 @@
}
]
},
+ "ineffective-final": {
+ "title": "detects calls to `final()` that type checkers cannot interpret",
+ "description": "## What it does\nChecks for calls to `final()` that type checkers cannot interpret.\n\n## Why is this bad?\nThe `final()` function is designed to be used as a decorator. When called directly\nas a function (e.g., `final(type(...))`), type checkers will not understand the\napplication of `final` and will not prevent subclassing.\n\n## Example\n\n```python\nfrom typing import final\n\n# Incorrect: type checkers will not prevent subclassing\nMyClass = final(type(\"MyClass\", (), {}))\n\n# Correct: use `final` as a decorator\n@final\nclass MyClass: ...\n```",
+ "default": "warn",
+ "oneOf": [
+ {
+ "$ref": "#/definitions/Level"
+ }
+ ]
+ },
"instance-layout-conflict": {
"title": "detects class definitions that raise `TypeError` due to instance layout conflict",
"description": "## What it does\nChecks for classes definitions which will fail at runtime due to\n\"instance memory layout conflicts\".\n\nThis error is usually caused by attempting to combine multiple classes\nthat define non-empty `__slots__` in a class's [Method Resolution Order]\n(MRO), or by attempting to combine multiple builtin classes in a class's\nMRO.\n\n## Why is this bad?\nInheriting from bases with conflicting instance memory layouts\nwill lead to a `TypeError` at runtime.\n\nAn instance memory layout conflict occurs when CPython cannot determine\nthe memory layout instances of a class should have, because the instance\nmemory layout of one of its bases conflicts with the instance memory layout\nof one or more of its other bases.\n\nFor example, if a Python class defines non-empty `__slots__`, this will\nimpact the memory layout of instances of that class. Multiple inheritance\nfrom more than one different class defining non-empty `__slots__` is not\nallowed:\n\n```python\nclass A:\n __slots__ = (\"a\", \"b\")\n\nclass B:\n __slots__ = (\"a\", \"b\") # Even if the values are the same\n\n# TypeError: multiple bases have instance lay-out conflict\nclass C(A, B): ...\n```\n\nAn instance layout conflict can also be caused by attempting to use\nmultiple inheritance with two builtin classes, due to the way that these\nclasses are implemented in a CPython C extension:\n\n```python\nclass A(int, float): ... # TypeError: multiple bases have instance lay-out conflict\n```\n\nNote that pure-Python classes with no `__slots__`, or pure-Python classes\nwith empty `__slots__`, are always compatible:\n\n```python\nclass A: ...\nclass B:\n __slots__ = ()\nclass C:\n __slots__ = (\"a\", \"b\")\n\n# fine\nclass D(A, B, C): ...\n```\n\n## Known problems\nClasses that have \"dynamic\" definitions of `__slots__` (definitions do not consist\nof string literals, or tuples of string literals) are not currently considered disjoint\nbases by ty.\n\nAdditionally, this check is not exhaustive: many C extensions (including several in\nthe standard library) define classes that use extended memory layouts and thus cannot\ncoexist in a single MRO. Since it is currently not possible to represent this fact in\nstub files, having a full knowledge of these classes is also impossible. When it comes\nto classes that do not define `__slots__` at the Python level, therefore, ty, currently\nonly hard-codes a number of cases where it knows that a class will produce instances with\nan atypical memory layout.\n\n## Further reading\n- [CPython documentation: `__slots__`](https://docs.python.org/3/reference/datamodel.html#slots)\n- [CPython documentation: Method Resolution Order](https://docs.python.org/3/glossary.html#term-method-resolution-order)\n\n[Method Resolution Order]: https://docs.python.org/3/glossary.html#term-method-resolution-order",