Skip to content

Commit 9e01763

Browse files
authored
[pep8-naming] Avoid false positive for class Bar(type(foo)) (N804) (#14683)
1 parent 56ae73a commit 9e01763

File tree

4 files changed

+43
-16
lines changed

4 files changed

+43
-16
lines changed

crates/ruff_linter/resources/test/fixtures/pep8_naming/N804.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,3 +76,8 @@ def bad_method(this):
7676

7777
def func(x):
7878
return x
79+
80+
foo = {}
81+
class Bar(type(foo)):
82+
def foo_method(self):
83+
pass

crates/ruff_linter/src/rules/flake8_pyi/rules/non_self_return_type.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ pub(crate) fn non_self_return_type(
126126
};
127127

128128
// PEP 673 forbids the use of `typing(_extensions).Self` in metaclasses.
129-
if analyze::class::is_metaclass(class_def, semantic) {
129+
if analyze::class::is_metaclass(class_def, semantic).into() {
130130
return;
131131
}
132132

crates/ruff_linter/src/rules/pep8_naming/rules/invalid_first_argument_name.rs

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use ruff_macros::{derive_message_formats, ViolationMetadata};
55
use ruff_python_ast as ast;
66
use ruff_python_ast::ParameterWithDefault;
77
use ruff_python_codegen::Stylist;
8-
use ruff_python_semantic::analyze::class::is_metaclass;
8+
use ruff_python_semantic::analyze::class::{is_metaclass, IsMetaclass};
99
use ruff_python_semantic::analyze::function_type;
1010
use ruff_python_semantic::{Scope, ScopeKind, SemanticModel};
1111
use ruff_text_size::Ranged;
@@ -212,13 +212,11 @@ pub(crate) fn invalid_first_argument_name(
212212
function_type::FunctionType::Function | function_type::FunctionType::StaticMethod => {
213213
return;
214214
}
215-
function_type::FunctionType::Method => {
216-
if is_metaclass(parent, semantic) {
217-
FunctionType::ClassMethod
218-
} else {
219-
FunctionType::Method
220-
}
221-
}
215+
function_type::FunctionType::Method => match is_metaclass(parent, semantic) {
216+
IsMetaclass::Yes => FunctionType::ClassMethod,
217+
IsMetaclass::No => FunctionType::Method,
218+
IsMetaclass::Maybe => return,
219+
},
222220
function_type::FunctionType::ClassMethod => FunctionType::ClassMethod,
223221
};
224222
if !checker.enabled(function_type.rule()) {

crates/ruff_python_semantic/src/analyze/class.rs

Lines changed: 31 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ pub fn any_qualified_base_class(
1212
semantic: &SemanticModel,
1313
func: &dyn Fn(QualifiedName) -> bool,
1414
) -> bool {
15-
any_base_class(class_def, semantic, &|expr| {
15+
any_base_class(class_def, semantic, &mut |expr| {
1616
semantic
1717
.resolve_qualified_name(map_subscript(expr))
1818
.is_some_and(func)
@@ -23,12 +23,12 @@ pub fn any_qualified_base_class(
2323
pub fn any_base_class(
2424
class_def: &ast::StmtClassDef,
2525
semantic: &SemanticModel,
26-
func: &dyn Fn(&Expr) -> bool,
26+
func: &mut dyn FnMut(&Expr) -> bool,
2727
) -> bool {
2828
fn inner(
2929
class_def: &ast::StmtClassDef,
3030
semantic: &SemanticModel,
31-
func: &dyn Fn(&Expr) -> bool,
31+
func: &mut dyn FnMut(&Expr) -> bool,
3232
seen: &mut FxHashSet<BindingId>,
3333
) -> bool {
3434
class_def.bases().iter().any(|expr| {
@@ -121,12 +121,30 @@ pub fn is_enumeration(class_def: &ast::StmtClassDef, semantic: &SemanticModel) -
121121
})
122122
}
123123

124-
/// Returns `true` if the given class is a metaclass.
125-
pub fn is_metaclass(class_def: &ast::StmtClassDef, semantic: &SemanticModel) -> bool {
126-
any_base_class(class_def, semantic, &|expr| match expr {
124+
/// Whether or not a class is a metaclass. Constructed by [`is_metaclass`].
125+
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
126+
pub enum IsMetaclass {
127+
Yes,
128+
No,
129+
Maybe,
130+
}
131+
132+
impl From<IsMetaclass> for bool {
133+
fn from(value: IsMetaclass) -> Self {
134+
matches!(value, IsMetaclass::Yes)
135+
}
136+
}
137+
138+
/// Returns `IsMetaclass::Yes` if the given class is definitely a metaclass,
139+
/// `IsMetaclass::No` if it's definitely *not* a metaclass, and
140+
/// `IsMetaclass::Maybe` otherwise.
141+
pub fn is_metaclass(class_def: &ast::StmtClassDef, semantic: &SemanticModel) -> IsMetaclass {
142+
let mut maybe = false;
143+
let is_base_class = any_base_class(class_def, semantic, &mut |expr| match expr {
127144
Expr::Call(ast::ExprCall {
128145
func, arguments, ..
129146
}) => {
147+
maybe = true;
130148
// Ex) `class Foo(type(Protocol)): ...`
131149
arguments.len() == 1 && semantic.match_builtin_expr(func.as_ref(), "type")
132150
}
@@ -144,5 +162,11 @@ pub fn is_metaclass(class_def: &ast::StmtClassDef, semantic: &SemanticModel) ->
144162
| ["enum", "EnumMeta" | "EnumType"]
145163
)
146164
}),
147-
})
165+
});
166+
167+
match (is_base_class, maybe) {
168+
(true, true) => IsMetaclass::Maybe,
169+
(true, false) => IsMetaclass::Yes,
170+
(false, _) => IsMetaclass::No,
171+
}
148172
}

0 commit comments

Comments
 (0)