mirror of
https://github.com/astral-sh/ruff.git
synced 2025-07-07 21:25:08 +00:00
[pylint
] Detect nested methods correctly (PLW1641
) (#15032)
Some checks are pending
CI / cargo fuzz build (push) Blocked by required conditions
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 / 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
Some checks are pending
CI / cargo fuzz build (push) Blocked by required conditions
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 / 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
Co-authored-by: Micha Reiser <micha@reiser.io>
This commit is contained in:
parent
42cc67a87c
commit
2a1aa29366
4 changed files with 485 additions and 43 deletions
|
@ -5,7 +5,10 @@ use crate::{BindingId, SemanticModel};
|
|||
use ruff_python_ast as ast;
|
||||
use ruff_python_ast::helpers::map_subscript;
|
||||
use ruff_python_ast::name::QualifiedName;
|
||||
use ruff_python_ast::{Expr, ExprName, ExprStarred, ExprSubscript, ExprTuple};
|
||||
use ruff_python_ast::{
|
||||
ExceptHandler, Expr, ExprName, ExprStarred, ExprSubscript, ExprTuple, Stmt, StmtFor, StmtIf,
|
||||
StmtMatch, StmtTry, StmtWhile, StmtWith,
|
||||
};
|
||||
|
||||
/// Return `true` if any base class matches a [`QualifiedName`] predicate.
|
||||
pub fn any_qualified_base_class(
|
||||
|
@ -109,6 +112,166 @@ pub fn any_super_class(
|
|||
inner(class_def, semantic, func, &mut FxHashSet::default())
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct ClassMemberDeclaration<'a> {
|
||||
kind: ClassMemberKind<'a>,
|
||||
boundness: ClassMemberBoundness,
|
||||
}
|
||||
|
||||
impl<'a> ClassMemberDeclaration<'a> {
|
||||
pub fn kind(&self) -> &ClassMemberKind<'a> {
|
||||
&self.kind
|
||||
}
|
||||
|
||||
pub fn boundness(&self) -> ClassMemberBoundness {
|
||||
self.boundness
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
|
||||
pub enum ClassMemberBoundness {
|
||||
PossiblyUnbound,
|
||||
Bound,
|
||||
}
|
||||
|
||||
impl ClassMemberBoundness {
|
||||
pub const fn is_bound(self) -> bool {
|
||||
matches!(self, Self::Bound)
|
||||
}
|
||||
|
||||
pub const fn is_possibly_unbound(self) -> bool {
|
||||
matches!(self, Self::PossiblyUnbound)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum ClassMemberKind<'a> {
|
||||
Assign(&'a ast::StmtAssign),
|
||||
AnnAssign(&'a ast::StmtAnnAssign),
|
||||
FunctionDef(&'a ast::StmtFunctionDef),
|
||||
}
|
||||
|
||||
pub fn any_member_declaration(
|
||||
class: &ast::StmtClassDef,
|
||||
func: &mut dyn FnMut(ClassMemberDeclaration) -> bool,
|
||||
) -> bool {
|
||||
fn any_stmt_in_body(
|
||||
body: &[Stmt],
|
||||
func: &mut dyn FnMut(ClassMemberDeclaration) -> bool,
|
||||
boundness: ClassMemberBoundness,
|
||||
) -> bool {
|
||||
body.iter().any(|stmt| {
|
||||
let kind = match stmt {
|
||||
Stmt::FunctionDef(function_def) => Some(ClassMemberKind::FunctionDef(function_def)),
|
||||
Stmt::Assign(assign) => Some(ClassMemberKind::Assign(assign)),
|
||||
Stmt::AnnAssign(assign) => Some(ClassMemberKind::AnnAssign(assign)),
|
||||
Stmt::With(StmtWith { body, .. }) => {
|
||||
if any_stmt_in_body(body, func, ClassMemberBoundness::PossiblyUnbound) {
|
||||
return true;
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
Stmt::For(StmtFor { body, orelse, .. })
|
||||
| Stmt::While(StmtWhile { body, orelse, .. }) => {
|
||||
if any_stmt_in_body(body, func, ClassMemberBoundness::PossiblyUnbound)
|
||||
|| any_stmt_in_body(orelse, func, ClassMemberBoundness::PossiblyUnbound)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
Stmt::If(StmtIf {
|
||||
body,
|
||||
elif_else_clauses,
|
||||
..
|
||||
}) => {
|
||||
if any_stmt_in_body(body, func, ClassMemberBoundness::PossiblyUnbound)
|
||||
|| elif_else_clauses.iter().any(|it| {
|
||||
any_stmt_in_body(&it.body, func, ClassMemberBoundness::PossiblyUnbound)
|
||||
})
|
||||
{
|
||||
return true;
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
Stmt::Match(StmtMatch { cases, .. }) => {
|
||||
if cases.iter().any(|it| {
|
||||
any_stmt_in_body(&it.body, func, ClassMemberBoundness::PossiblyUnbound)
|
||||
}) {
|
||||
return true;
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
Stmt::Try(StmtTry {
|
||||
body,
|
||||
handlers,
|
||||
orelse,
|
||||
finalbody,
|
||||
..
|
||||
}) => {
|
||||
if any_stmt_in_body(body, func, ClassMemberBoundness::PossiblyUnbound)
|
||||
|| any_stmt_in_body(orelse, func, ClassMemberBoundness::PossiblyUnbound)
|
||||
|| any_stmt_in_body(finalbody, func, ClassMemberBoundness::PossiblyUnbound)
|
||||
|| handlers.iter().any(|ExceptHandler::ExceptHandler(it)| {
|
||||
any_stmt_in_body(&it.body, func, ClassMemberBoundness::PossiblyUnbound)
|
||||
})
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
// Technically, a method can be defined using a few more methods:
|
||||
//
|
||||
// ```python
|
||||
// class C1:
|
||||
// # Import
|
||||
// import __eq__ # Callable module
|
||||
// # ImportFrom
|
||||
// from module import __eq__ # Top level callable
|
||||
// # ExprNamed
|
||||
// (__eq__ := lambda self, other: True)
|
||||
// ```
|
||||
//
|
||||
// Those cases are not yet supported because they're rare.
|
||||
Stmt::ClassDef(_)
|
||||
| Stmt::Return(_)
|
||||
| Stmt::Delete(_)
|
||||
| Stmt::AugAssign(_)
|
||||
| Stmt::TypeAlias(_)
|
||||
| Stmt::Raise(_)
|
||||
| Stmt::Assert(_)
|
||||
| Stmt::Import(_)
|
||||
| Stmt::ImportFrom(_)
|
||||
| Stmt::Global(_)
|
||||
| Stmt::Nonlocal(_)
|
||||
| Stmt::Expr(_)
|
||||
| Stmt::Pass(_)
|
||||
| Stmt::Break(_)
|
||||
| Stmt::Continue(_)
|
||||
| Stmt::IpyEscapeCommand(_) => None,
|
||||
};
|
||||
|
||||
if let Some(kind) = kind {
|
||||
if func(ClassMemberDeclaration { kind, boundness }) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
false
|
||||
})
|
||||
}
|
||||
|
||||
any_stmt_in_body(&class.body, func, ClassMemberBoundness::Bound)
|
||||
}
|
||||
|
||||
/// Return `true` if `class_def` is a class that has one or more enum classes in its mro
|
||||
pub fn is_enumeration(class_def: &ast::StmtClassDef, semantic: &SemanticModel) -> bool {
|
||||
any_qualified_base_class(class_def, semantic, &|qualified_name| {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue