Avoid A003 violations for explicitly overridden methods (#6076)

## Summary

If a method is annotated with `@typing_extensions.override`, we should
avoid flagging A003 on it. This isn't part of the standard library yet,
but it's used to explicitly mark methods as overrides.
This commit is contained in:
Charlie Marsh 2023-07-25 12:21:23 -04:00 committed by GitHub
parent f5c69c1b34
commit 9171bd4c28
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 65 additions and 3 deletions

View file

@ -39,3 +39,15 @@ class CustomFilter(Filter):
def str(self) -> None: def str(self) -> None:
... ...
from typing_extensions import override
class MyClass:
@override
def str(self):
pass
def int(self):
pass

View file

@ -332,10 +332,11 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
} }
if let ScopeKind::Class(class_def) = checker.semantic.scope().kind { if let ScopeKind::Class(class_def) = checker.semantic.scope().kind {
if checker.enabled(Rule::BuiltinAttributeShadowing) { if checker.enabled(Rule::BuiltinAttributeShadowing) {
flake8_builtins::rules::builtin_attribute_shadowing( flake8_builtins::rules::builtin_method_shadowing(
checker, checker,
class_def, class_def,
name, name,
decorator_list,
name.range(), name.range(),
); );
} }

View file

@ -1,5 +1,6 @@
use ruff_text_size::TextRange; use ruff_text_size::TextRange;
use rustpython_parser::ast; use rustpython_parser::ast;
use rustpython_parser::ast::Decorator;
use ruff_diagnostics::Diagnostic; use ruff_diagnostics::Diagnostic;
use ruff_diagnostics::Violation; use ruff_diagnostics::Violation;
@ -10,7 +11,8 @@ use crate::checkers::ast::Checker;
use crate::rules::flake8_builtins::helpers::shadows_builtin; use crate::rules::flake8_builtins::helpers::shadows_builtin;
/// ## What it does /// ## What it does
/// Checks for any class attributes that use the same name as a builtin. /// Checks for any class attributes or methods that use the same name as a
/// builtin.
/// ///
/// ## Why is this bad? /// ## Why is this bad?
/// Reusing a builtin name for the name of an attribute increases the /// Reusing a builtin name for the name of an attribute increases the
@ -20,7 +22,9 @@ use crate::rules::flake8_builtins::helpers::shadows_builtin;
/// ///
/// Builtins can be marked as exceptions to this rule via the /// Builtins can be marked as exceptions to this rule via the
/// [`flake8-builtins.builtins-ignorelist`] configuration option, or /// [`flake8-builtins.builtins-ignorelist`] configuration option, or
/// converted to the appropriate dunder method. /// converted to the appropriate dunder method. Methods decorated with
/// `@typing.override` or `@typing_extensions.override` are also
/// ignored.
/// ///
/// ## Example /// ## Example
/// ```python /// ```python
@ -81,12 +85,39 @@ pub(crate) fn builtin_attribute_shadowing(
return; return;
} }
checker.diagnostics.push(Diagnostic::new(
BuiltinAttributeShadowing {
name: name.to_string(),
},
range,
));
}
}
/// A003
pub(crate) fn builtin_method_shadowing(
checker: &mut Checker,
class_def: &ast::StmtClassDef,
name: &str,
decorator_list: &[Decorator],
range: TextRange,
) {
if shadows_builtin(name, &checker.settings.flake8_builtins.builtins_ignorelist) {
// Ignore some standard-library methods. Ideally, we'd ignore all overridden methods, since // Ignore some standard-library methods. Ideally, we'd ignore all overridden methods, since
// those should be flagged on the superclass, but that's more difficult. // those should be flagged on the superclass, but that's more difficult.
if is_standard_library_override(name, class_def, checker.semantic()) { if is_standard_library_override(name, class_def, checker.semantic()) {
return; return;
} }
// Ignore explicit overrides.
if decorator_list.iter().any(|decorator| {
checker
.semantic()
.match_typing_expr(&decorator.expression, "override")
}) {
return;
}
checker.diagnostics.push(Diagnostic::new( checker.diagnostics.push(Diagnostic::new(
BuiltinAttributeShadowing { BuiltinAttributeShadowing {
name: name.to_string(), name: name.to_string(),

View file

@ -56,4 +56,13 @@ A003.py:40:9: A003 Class attribute `str` is shadowing a Python builtin
41 | ... 41 | ...
| |
A003.py:52:9: A003 Class attribute `int` is shadowing a Python builtin
|
50 | pass
51 |
52 | def int(self):
| ^^^ A003
53 | pass
|

View file

@ -37,4 +37,13 @@ A003.py:40:9: A003 Class attribute `str` is shadowing a Python builtin
41 | ... 41 | ...
| |
A003.py:52:9: A003 Class attribute `int` is shadowing a Python builtin
|
50 | pass
51 |
52 | def int(self):
| ^^^ A003
53 | pass
|