[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

Co-authored-by: Micha Reiser <micha@reiser.io>
This commit is contained in:
InSync 2024-12-30 22:55:14 +07:00 committed by GitHub
parent 42cc67a87c
commit 2a1aa29366
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 485 additions and 43 deletions

View file

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