[ty] Add a diagnostic for non-decorator uses of final (#22555)

## Summary

See:
https://github.com/astral-sh/ruff/pull/22499#discussion_r2687263390.
This commit is contained in:
Charlie Marsh
2026-01-14 09:14:59 -05:00
committed by GitHub
parent e41f045ec5
commit ba0736385d
5 changed files with 184 additions and 96 deletions

View File

@@ -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.

View File

@@ -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 {