Allow list() and tuple() calls in __all__ assignments (#2499)

This commit is contained in:
Charlie Marsh 2023-02-02 15:45:14 -05:00 committed by GitHub
parent 2b0de8ccd9
commit ee01e666c5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 151 additions and 41 deletions

View File

@ -0,0 +1,3 @@
a = 1
__all__ = list(["a", "b"])

View File

@ -6,6 +6,10 @@ __all__ += {"world"} # [invalid-all-format]
__all__ = {"world"} + ["Hello"] # [invalid-all-format] __all__ = {"world"} + ["Hello"] # [invalid-all-format]
__all__ = {"world"} + list(["Hello"]) # [invalid-all-format]
__all__ = list(["Hello"]) + {"world"} # [invalid-all-format]
__all__ = (x for x in ["Hello", "world"]) # [invalid-all-format] __all__ = (x for x in ["Hello", "world"]) # [invalid-all-format]
__all__ = {x for x in ["Hello", "world"]} # [invalid-all-format] __all__ = {x for x in ["Hello", "world"]} # [invalid-all-format]
@ -17,3 +21,11 @@ __all__ = ("Hello",)
__all__ = ["Hello"] + ("world",) __all__ = ["Hello"] + ("world",)
__all__ = [x for x in ["Hello", "world"]] __all__ = [x for x in ["Hello", "world"]]
__all__ = list(["Hello", "world"])
__all__ = list({"Hello", "world"})
__all__ = list(["Hello"]) + list(["world"])
__all__ = tuple(["Hello"]) + ("world",)

View File

@ -4,8 +4,12 @@ __all__ = (
Worm, Worm,
) )
__all__ = list([None, "Fruit", "Worm"]) # [invalid-all-object]
class Fruit: class Fruit:
pass pass
class Worm: class Worm:
pass pass

View File

@ -6,9 +6,10 @@ use rustpython_parser::lexer;
use rustpython_parser::lexer::Tok; use rustpython_parser::lexer::Tok;
use crate::ast::helpers::any_over_expr; use crate::ast::helpers::any_over_expr;
use crate::ast::types::{Binding, BindingKind, Scope}; use crate::ast::types::{BindingKind, Scope};
use crate::ast::visitor; use crate::ast::visitor;
use crate::ast::visitor::Visitor; use crate::ast::visitor::Visitor;
use crate::checkers::ast::Checker;
bitflags! { bitflags! {
#[derive(Default)] #[derive(Default)]
@ -20,9 +21,9 @@ bitflags! {
/// Extract the names bound to a given __all__ assignment. /// Extract the names bound to a given __all__ assignment.
pub fn extract_all_names( pub fn extract_all_names(
checker: &Checker,
stmt: &Stmt, stmt: &Stmt,
scope: &Scope, scope: &Scope,
bindings: &[Binding],
) -> (Vec<String>, AllNamesFlags) { ) -> (Vec<String>, AllNamesFlags) {
fn add_to_names(names: &mut Vec<String>, elts: &[Expr], flags: &mut AllNamesFlags) { fn add_to_names(names: &mut Vec<String>, elts: &[Expr], flags: &mut AllNamesFlags) {
for elt in elts { for elt in elts {
@ -38,13 +39,66 @@ pub fn extract_all_names(
} }
} }
fn extract_elts<'a>(
checker: &'a Checker,
expr: &'a Expr,
) -> (Option<&'a Vec<Expr>>, AllNamesFlags) {
match &expr.node {
ExprKind::List { elts, .. } => {
return (Some(elts), AllNamesFlags::empty());
}
ExprKind::Tuple { elts, .. } => {
return (Some(elts), AllNamesFlags::empty());
}
ExprKind::ListComp { .. } => {
// Allow comprehensions, even though we can't statically analyze them.
return (None, AllNamesFlags::empty());
}
ExprKind::Call {
func,
args,
keywords,
..
} => {
// Allow `tuple()` and `list()` calls.
if keywords.is_empty() && args.len() <= 1 {
if checker.resolve_call_path(func).map_or(false, |call_path| {
call_path.as_slice() == ["", "tuple"]
|| call_path.as_slice() == ["", "list"]
}) {
if args.is_empty() {
return (None, AllNamesFlags::empty());
}
match &args[0].node {
ExprKind::List { elts, .. }
| ExprKind::Set { elts, .. }
| ExprKind::Tuple { elts, .. } => {
return (Some(elts), AllNamesFlags::empty());
}
ExprKind::ListComp { .. }
| ExprKind::SetComp { .. }
| ExprKind::GeneratorExp { .. } => {
// Allow comprehensions, even though we can't statically analyze
// them.
return (None, AllNamesFlags::empty());
}
_ => {}
}
}
}
}
_ => {}
}
(None, AllNamesFlags::INVALID_FORMAT)
}
let mut names: Vec<String> = vec![]; let mut names: Vec<String> = vec![];
let mut flags = AllNamesFlags::empty(); let mut flags = AllNamesFlags::empty();
// Grab the existing bound __all__ values. // Grab the existing bound __all__ values.
if let StmtKind::AugAssign { .. } = &stmt.node { if let StmtKind::AugAssign { .. } = &stmt.node {
if let Some(index) = scope.values.get("__all__") { if let Some(index) = scope.values.get("__all__") {
if let BindingKind::Export(existing) = &bindings[*index].kind { if let BindingKind::Export(existing) = &checker.bindings[*index].kind {
names.extend_from_slice(existing); names.extend_from_slice(existing);
} }
} }
@ -56,45 +110,36 @@ pub fn extract_all_names(
StmtKind::AugAssign { value, .. } => Some(value), StmtKind::AugAssign { value, .. } => Some(value),
_ => None, _ => None,
} { } {
match &value.node { if let ExprKind::BinOp { left, right, .. } = &value.node {
ExprKind::List { elts, .. } | ExprKind::Tuple { elts, .. } => {
add_to_names(&mut names, elts, &mut flags);
}
ExprKind::BinOp { left, right, .. } => {
let mut current_left = left; let mut current_left = left;
let mut current_right = right; let mut current_right = right;
while let Some(elts) = match &current_right.node { loop {
ExprKind::List { elts, .. } => Some(elts), // Process the right side, which should be a "real" value.
ExprKind::Tuple { elts, .. } => Some(elts), let (elts, new_flags) = extract_elts(checker, current_right);
_ => { flags |= new_flags;
flags |= AllNamesFlags::INVALID_FORMAT; if let Some(elts) = elts {
None
}
} {
add_to_names(&mut names, elts, &mut flags); add_to_names(&mut names, elts, &mut flags);
match &current_left.node { }
ExprKind::BinOp { left, right, .. } => {
// Process the left side, which can be a "real" value or the "rest" of the
// binary operation.
if let ExprKind::BinOp { left, right, .. } = &current_left.node {
current_left = left; current_left = left;
current_right = right; current_right = right;
} } else {
ExprKind::List { elts, .. } | ExprKind::Tuple { elts, .. } => { let (elts, new_flags) = extract_elts(checker, current_left);
flags |= new_flags;
if let Some(elts) = elts {
add_to_names(&mut names, elts, &mut flags); add_to_names(&mut names, elts, &mut flags);
break;
} }
_ => {
flags |= AllNamesFlags::INVALID_FORMAT;
break; break;
} }
} }
} } else {
} let (elts, new_flags) = extract_elts(checker, value);
ExprKind::ListComp { .. } => { flags |= new_flags;
// Allow list comprehensions, even though we can't statically analyze them. if let Some(elts) = elts {
// TODO(charlie): Allow `list()` and `tuple()` calls too, and extract the members add_to_names(&mut names, elts, &mut flags);
// from them (even if, e.g., it's `list({...})`).
}
_ => {
flags |= AllNamesFlags::INVALID_FORMAT;
} }
} }
} }

View File

@ -4201,8 +4201,7 @@ impl<'a> Checker<'a> {
} }
_ => false, _ => false,
} { } {
let (all_names, all_names_flags) = let (all_names, all_names_flags) = extract_all_names(self, parent, current);
extract_all_names(parent, current, &self.bindings);
if self.settings.rules.enabled(&Rule::InvalidAllFormat) if self.settings.rules.enabled(&Rule::InvalidAllFormat)
&& matches!(all_names_flags, AllNamesFlags::INVALID_FORMAT) && matches!(all_names_flags, AllNamesFlags::INVALID_FORMAT)

View File

@ -98,7 +98,8 @@ mod tests {
#[test_case(Rule::UndefinedName, Path::new("F821_6.py"); "F821_6")] #[test_case(Rule::UndefinedName, Path::new("F821_6.py"); "F821_6")]
#[test_case(Rule::UndefinedName, Path::new("F821_7.py"); "F821_7")] #[test_case(Rule::UndefinedName, Path::new("F821_7.py"); "F821_7")]
#[test_case(Rule::UndefinedName, Path::new("F821_8.pyi"); "F821_8")] #[test_case(Rule::UndefinedName, Path::new("F821_8.pyi"); "F821_8")]
#[test_case(Rule::UndefinedExport, Path::new("F822.py"); "F822")] #[test_case(Rule::UndefinedExport, Path::new("F822_0.py"); "F822_0")]
#[test_case(Rule::UndefinedExport, Path::new("F822_1.py"); "F822_1")]
#[test_case(Rule::UndefinedLocal, Path::new("F823.py"); "F823")] #[test_case(Rule::UndefinedLocal, Path::new("F823.py"); "F823")]
#[test_case(Rule::UnusedVariable, Path::new("F841_0.py"); "F841_0")] #[test_case(Rule::UnusedVariable, Path::new("F841_0.py"); "F841_0")]
#[test_case(Rule::UnusedVariable, Path::new("F841_1.py"); "F841_1")] #[test_case(Rule::UnusedVariable, Path::new("F841_1.py"); "F841_1")]

View File

@ -0,0 +1,16 @@
---
source: src/rules/pyflakes/mod.rs
expression: diagnostics
---
- kind:
UndefinedExport:
name: b
location:
row: 3
column: 0
end_location:
row: 3
column: 7
fix: ~
parent: ~

View File

@ -12,4 +12,14 @@ expression: diagnostics
column: 7 column: 7
fix: ~ fix: ~
parent: ~ parent: ~
- kind:
InvalidAllObject: ~
location:
row: 7
column: 0
end_location:
row: 7
column: 7
fix: ~
parent: ~

View File

@ -62,4 +62,24 @@ expression: diagnostics
column: 7 column: 7
fix: ~ fix: ~
parent: ~ parent: ~
- kind:
InvalidAllFormat: ~
location:
row: 13
column: 0
end_location:
row: 13
column: 7
fix: ~
parent: ~
- kind:
InvalidAllFormat: ~
location:
row: 15
column: 0
end_location:
row: 15
column: 7
fix: ~
parent: ~