[pep8-naming] Avoid false positive for class Bar(type(foo)) (N804) (#14683)
Some checks are pending
CI / Determine changes (push) Waiting to run
CI / cargo fmt (push) Waiting to run
CI / cargo clippy (push) Blocked by required conditions
CI / cargo test (linux) (push) Blocked by required conditions
CI / cargo test (linux, release) (push) Blocked by required conditions
CI / cargo test (windows) (push) Blocked by required conditions
CI / cargo test (wasm) (push) Blocked by required conditions
CI / cargo build (release) (push) Waiting to run
CI / cargo build (msrv) (push) Blocked by required conditions
CI / cargo fuzz build (push) Blocked by required conditions
CI / fuzz parser (push) Blocked by required conditions
CI / test scripts (push) Blocked by required conditions
CI / ecosystem (push) Blocked by required conditions
CI / cargo shear (push) Blocked by required conditions
CI / python package (push) Waiting to run
CI / pre-commit (push) Waiting to run
CI / mkdocs (push) Waiting to run
CI / formatter instabilities and black similarity (push) Blocked by required conditions
CI / test ruff-lsp (push) Blocked by required conditions
CI / benchmarks (push) Blocked by required conditions

This commit is contained in:
Brent Westbrook 2024-11-30 17:37:28 -05:00 committed by GitHub
parent 56ae73a925
commit 9e017634cb
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 43 additions and 16 deletions

View file

@ -76,3 +76,8 @@ class RenamingInMethodBodyClass(ABCMeta):
def func(x): def func(x):
return x return x
foo = {}
class Bar(type(foo)):
def foo_method(self):
pass

View file

@ -126,7 +126,7 @@ pub(crate) fn non_self_return_type(
}; };
// PEP 673 forbids the use of `typing(_extensions).Self` in metaclasses. // PEP 673 forbids the use of `typing(_extensions).Self` in metaclasses.
if analyze::class::is_metaclass(class_def, semantic) { if analyze::class::is_metaclass(class_def, semantic).into() {
return; return;
} }

View file

@ -5,7 +5,7 @@ use ruff_macros::{derive_message_formats, ViolationMetadata};
use ruff_python_ast as ast; use ruff_python_ast as ast;
use ruff_python_ast::ParameterWithDefault; use ruff_python_ast::ParameterWithDefault;
use ruff_python_codegen::Stylist; use ruff_python_codegen::Stylist;
use ruff_python_semantic::analyze::class::is_metaclass; use ruff_python_semantic::analyze::class::{is_metaclass, IsMetaclass};
use ruff_python_semantic::analyze::function_type; use ruff_python_semantic::analyze::function_type;
use ruff_python_semantic::{Scope, ScopeKind, SemanticModel}; use ruff_python_semantic::{Scope, ScopeKind, SemanticModel};
use ruff_text_size::Ranged; use ruff_text_size::Ranged;
@ -212,13 +212,11 @@ pub(crate) fn invalid_first_argument_name(
function_type::FunctionType::Function | function_type::FunctionType::StaticMethod => { function_type::FunctionType::Function | function_type::FunctionType::StaticMethod => {
return; return;
} }
function_type::FunctionType::Method => { function_type::FunctionType::Method => match is_metaclass(parent, semantic) {
if is_metaclass(parent, semantic) { IsMetaclass::Yes => FunctionType::ClassMethod,
FunctionType::ClassMethod IsMetaclass::No => FunctionType::Method,
} else { IsMetaclass::Maybe => return,
FunctionType::Method },
}
}
function_type::FunctionType::ClassMethod => FunctionType::ClassMethod, function_type::FunctionType::ClassMethod => FunctionType::ClassMethod,
}; };
if !checker.enabled(function_type.rule()) { if !checker.enabled(function_type.rule()) {

View file

@ -12,7 +12,7 @@ pub fn any_qualified_base_class(
semantic: &SemanticModel, semantic: &SemanticModel,
func: &dyn Fn(QualifiedName) -> bool, func: &dyn Fn(QualifiedName) -> bool,
) -> bool { ) -> bool {
any_base_class(class_def, semantic, &|expr| { any_base_class(class_def, semantic, &mut |expr| {
semantic semantic
.resolve_qualified_name(map_subscript(expr)) .resolve_qualified_name(map_subscript(expr))
.is_some_and(func) .is_some_and(func)
@ -23,12 +23,12 @@ pub fn any_qualified_base_class(
pub fn any_base_class( pub fn any_base_class(
class_def: &ast::StmtClassDef, class_def: &ast::StmtClassDef,
semantic: &SemanticModel, semantic: &SemanticModel,
func: &dyn Fn(&Expr) -> bool, func: &mut dyn FnMut(&Expr) -> bool,
) -> bool { ) -> bool {
fn inner( fn inner(
class_def: &ast::StmtClassDef, class_def: &ast::StmtClassDef,
semantic: &SemanticModel, semantic: &SemanticModel,
func: &dyn Fn(&Expr) -> bool, func: &mut dyn FnMut(&Expr) -> bool,
seen: &mut FxHashSet<BindingId>, seen: &mut FxHashSet<BindingId>,
) -> bool { ) -> bool {
class_def.bases().iter().any(|expr| { class_def.bases().iter().any(|expr| {
@ -121,12 +121,30 @@ pub fn is_enumeration(class_def: &ast::StmtClassDef, semantic: &SemanticModel) -
}) })
} }
/// Returns `true` if the given class is a metaclass. /// Whether or not a class is a metaclass. Constructed by [`is_metaclass`].
pub fn is_metaclass(class_def: &ast::StmtClassDef, semantic: &SemanticModel) -> bool { #[derive(Debug, PartialEq, Eq, Clone, Copy)]
any_base_class(class_def, semantic, &|expr| match expr { pub enum IsMetaclass {
Yes,
No,
Maybe,
}
impl From<IsMetaclass> for bool {
fn from(value: IsMetaclass) -> Self {
matches!(value, IsMetaclass::Yes)
}
}
/// Returns `IsMetaclass::Yes` if the given class is definitely a metaclass,
/// `IsMetaclass::No` if it's definitely *not* a metaclass, and
/// `IsMetaclass::Maybe` otherwise.
pub fn is_metaclass(class_def: &ast::StmtClassDef, semantic: &SemanticModel) -> IsMetaclass {
let mut maybe = false;
let is_base_class = any_base_class(class_def, semantic, &mut |expr| match expr {
Expr::Call(ast::ExprCall { Expr::Call(ast::ExprCall {
func, arguments, .. func, arguments, ..
}) => { }) => {
maybe = true;
// Ex) `class Foo(type(Protocol)): ...` // Ex) `class Foo(type(Protocol)): ...`
arguments.len() == 1 && semantic.match_builtin_expr(func.as_ref(), "type") arguments.len() == 1 && semantic.match_builtin_expr(func.as_ref(), "type")
} }
@ -144,5 +162,11 @@ pub fn is_metaclass(class_def: &ast::StmtClassDef, semantic: &SemanticModel) ->
| ["enum", "EnumMeta" | "EnumType"] | ["enum", "EnumMeta" | "EnumType"]
) )
}), }),
}) });
match (is_base_class, maybe) {
(true, true) => IsMetaclass::Maybe,
(true, false) => IsMetaclass::Yes,
(false, _) => IsMetaclass::No,
}
} }