From 2c7e350d5a6acf6acc9f59d1d51c8863ea77f2d5 Mon Sep 17 00:00:00 2001 From: Brent Westbrook Date: Wed, 15 Jul 2026 17:02:37 -0400 Subject: [PATCH 1/3] [`flake8-pyi`] Fix false positive in `__all__` (`PYI053`) Summary -- This PR fixes a false positive on long strings in `__all__`. These strings correspond to existing exported symbols and are out of the stub author's control. I initially hoped that we could use the existing `Semantic::in_dunder_all_definition`, but this is only set in `Checker::visit_exports` and used for adding global references, so the flag is no longer set when the rule runs in `Checker::visit_expr`. I found another similar piece of existing code in `Checker::handle_node_store` and factored that out into a `Checker` helper method. Test Plan -- New mdtest From 05081aeff259c616c6944c59b44a4d25963d39c1 Mon Sep 17 00:00:00 2001 From: Brent Westbrook Date: Wed, 15 Jul 2026 16:46:32 -0400 Subject: [PATCH 2/3] failing test case --- .../flake8-pyi/string-or-bytes-too-long.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 crates/ruff_linter/resources/mdtest/flake8-pyi/string-or-bytes-too-long.md diff --git a/crates/ruff_linter/resources/mdtest/flake8-pyi/string-or-bytes-too-long.md b/crates/ruff_linter/resources/mdtest/flake8-pyi/string-or-bytes-too-long.md new file mode 100644 index 0000000000000..bcdbea77caaa8 --- /dev/null +++ b/crates/ruff_linter/resources/mdtest/flake8-pyi/string-or-bytes-too-long.md @@ -0,0 +1,18 @@ +# `string-or-bytes-too-long` (`PYI053`) + +```toml +[lint] +select = ["PYI053"] +``` + +## Long name in `__all__` + +Strings in `__all__` correspond to exported names and should be exempt from the rule. + +```pyi +__all__ = [ + # TODO + # error: [string-or-bytes-too-long] + "aaaaaaaaaabbbbbbbbbbccccccccccddddddddddeeeeeeeeeef", +] +``` From 89ee64c3ce5708f761de058496732efd78aeb082 Mon Sep 17 00:00:00 2001 From: Brent Westbrook Date: Wed, 15 Jul 2026 17:02:32 -0400 Subject: [PATCH 3/3] fix --- .../flake8-pyi/string-or-bytes-too-long.md | 2 - crates/ruff_linter/src/checkers/ast/mod.rs | 64 +++++++++++-------- .../rules/string_or_bytes_too_long.rs | 11 +++- 3 files changed, 46 insertions(+), 31 deletions(-) diff --git a/crates/ruff_linter/resources/mdtest/flake8-pyi/string-or-bytes-too-long.md b/crates/ruff_linter/resources/mdtest/flake8-pyi/string-or-bytes-too-long.md index bcdbea77caaa8..c01174839c9af 100644 --- a/crates/ruff_linter/resources/mdtest/flake8-pyi/string-or-bytes-too-long.md +++ b/crates/ruff_linter/resources/mdtest/flake8-pyi/string-or-bytes-too-long.md @@ -11,8 +11,6 @@ Strings in `__all__` correspond to exported names and should be exempt from the ```pyi __all__ = [ - # TODO - # error: [string-or-bytes-too-long] "aaaaaaaaaabbbbbbbbbbccccccccccddddddddddeeeeeeeeeef", ] ``` diff --git a/crates/ruff_linter/src/checkers/ast/mod.rs b/crates/ruff_linter/src/checkers/ast/mod.rs index afdf134f1e0f1..23d6f8f5d64ba 100644 --- a/crates/ruff_linter/src/checkers/ast/mod.rs +++ b/crates/ruff_linter/src/checkers/ast/mod.rs @@ -2792,34 +2792,7 @@ impl<'a> Checker<'a> { _ => {} } - let scope = self.semantic.current_scope(); - - if scope.kind.is_module() - && match parent { - Stmt::Assign(ast::StmtAssign { targets, .. }) => { - if let Some(Expr::Name(ast::ExprName { id, .. })) = targets.first() { - id == "__all__" - } else { - false - } - } - Stmt::AugAssign(ast::StmtAugAssign { target, .. }) => { - if let Expr::Name(ast::ExprName { id, .. }) = target.as_ref() { - id == "__all__" - } else { - false - } - } - Stmt::AnnAssign(ast::StmtAnnAssign { target, .. }) => { - if let Expr::Name(ast::ExprName { id, .. }) = target.as_ref() { - id == "__all__" - } else { - false - } - } - _ => false, - } - { + if self.in_dunder_all_assignment(parent) { let (all_names, all_flags) = self.semantic.extract_dunder_all_names(parent); if all_flags.intersects(DunderAllFlags::INVALID_OBJECT) { @@ -3285,6 +3258,41 @@ impl<'a> Checker<'a> { self.semantic.restore(snapshot); } + + /// Report whether a module-level `__all__` assignment is being visited. + /// + /// This differs from [`SemanticModel::in_dunder_all_definition`], which is set only while + /// adding bindings for the entries in `__all__`. + pub(crate) fn in_dunder_all_assignment(&self, parent: &Stmt) -> bool { + if !self.semantic.current_scope().kind.is_module() { + return false; + } + + match parent { + Stmt::Assign(ast::StmtAssign { targets, .. }) => { + if let Some(Expr::Name(ast::ExprName { id, .. })) = targets.first() { + id == "__all__" + } else { + false + } + } + Stmt::AugAssign(ast::StmtAugAssign { target, .. }) => { + if let Expr::Name(ast::ExprName { id, .. }) = target.as_ref() { + id == "__all__" + } else { + false + } + } + Stmt::AnnAssign(ast::StmtAnnAssign { target, .. }) => { + if let Expr::Name(ast::ExprName { id, .. }) = target.as_ref() { + id == "__all__" + } else { + false + } + } + _ => false, + } + } } struct ParsedAnnotationsCache<'a> { diff --git a/crates/ruff_linter/src/rules/flake8_pyi/rules/string_or_bytes_too_long.rs b/crates/ruff_linter/src/rules/flake8_pyi/rules/string_or_bytes_too_long.rs index 0b0580663bc6e..d4fd623c4db56 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/rules/string_or_bytes_too_long.rs +++ b/crates/ruff_linter/src/rules/flake8_pyi/rules/string_or_bytes_too_long.rs @@ -21,6 +21,9 @@ use crate::{AlwaysFixableViolation, Edit, Fix}; /// checkers, the primary consumers of stub files. Replace very long constants /// with ellipses (`...`) to simplify the stub. /// +/// The rule does not apply to long entries in `__all__`, which are assumed to +/// be outside the stub author's control. +/// /// ## Example /// /// ```pyi @@ -51,8 +54,10 @@ impl AlwaysFixableViolation for StringOrBytesTooLong { pub(crate) fn string_or_bytes_too_long(checker: &Checker, string: StringLike) { let semantic = checker.semantic(); + let parent = semantic.current_statement(); + // Ignore docstrings. - if is_docstring_stmt(semantic.current_statement()) { + if is_docstring_stmt(parent) { return; } @@ -64,6 +69,10 @@ pub(crate) fn string_or_bytes_too_long(checker: &Checker, string: StringLike) { return; } + if checker.in_dunder_all_assignment(parent) { + return; + } + let length = match string { StringLike::String(ast::ExprStringLiteral { value, .. }) => value.chars().count(), StringLike::Bytes(ast::ExprBytesLiteral { value, .. }) => value.len(),