use crate::helpers::map_subscript; use crate::{self as ast, Constant, Expr, Stmt}; use bitflags::bitflags; bitflags! { #[derive(Default, Debug, Copy, Clone, PartialEq, Eq)] pub struct DunderAllFlags: u8 { /// The right-hand-side assignment to __all__ uses an invalid expression (i.e., an /// expression that doesn't evaluate to a container type, like `__all__ = 1`). const INVALID_FORMAT = 1 << 0; /// The right-hand-side assignment to __all__ contains an invalid member (i.e., a /// non-string, like `__all__ = [1]`). const INVALID_OBJECT = 1 << 1; } } /// Extract the names bound to a given __all__ assignment. /// /// Accepts a closure that determines whether a given name (e.g., `"list"`) is a Python builtin. pub fn extract_all_names(stmt: &Stmt, is_builtin: F) -> (Vec<&str>, DunderAllFlags) where F: Fn(&str) -> bool, { fn add_to_names<'a>(elts: &'a [Expr], names: &mut Vec<&'a str>, flags: &mut DunderAllFlags) { for elt in elts { if let Expr::Constant(ast::ExprConstant { value: Constant::Str(ast::StringConstant { value, .. }), .. }) = elt { names.push(value); } else { *flags |= DunderAllFlags::INVALID_OBJECT; } } } fn extract_elts(expr: &Expr, is_builtin: F) -> (Option<&[Expr]>, DunderAllFlags) where F: Fn(&str) -> bool, { match expr { Expr::List(ast::ExprList { elts, .. }) => { return (Some(elts), DunderAllFlags::empty()); } Expr::Tuple(ast::ExprTuple { elts, .. }) => { return (Some(elts), DunderAllFlags::empty()); } Expr::ListComp(_) => { // Allow comprehensions, even though we can't statically analyze them. return (None, DunderAllFlags::empty()); } Expr::Name(ast::ExprName { id, .. }) => { // Ex) `__all__ = __all__ + multiprocessing.__all__` if id == "__all__" { return (None, DunderAllFlags::empty()); } } Expr::Attribute(ast::ExprAttribute { attr, .. }) => { // Ex) `__all__ = __all__ + multiprocessing.__all__` if attr == "__all__" { return (None, DunderAllFlags::empty()); } } Expr::Call(ast::ExprCall { func, arguments: ast::Arguments { args, keywords, .. }, .. }) => { // Allow `tuple()`, `list()`, and their generic forms, like `list[int]()`. if keywords.is_empty() && args.len() <= 1 { if let Expr::Name(ast::ExprName { id, .. }) = map_subscript(func) { let id = id.as_str(); if matches!(id, "tuple" | "list") && is_builtin(id) { let [arg] = args.as_slice() else { return (None, DunderAllFlags::empty()); }; match arg { Expr::List(ast::ExprList { elts, .. }) | Expr::Set(ast::ExprSet { elts, .. }) | Expr::Tuple(ast::ExprTuple { elts, .. }) => { return (Some(elts), DunderAllFlags::empty()); } Expr::ListComp(_) | Expr::SetComp(_) | Expr::GeneratorExp(_) => { // Allow comprehensions, even though we can't statically analyze // them. return (None, DunderAllFlags::empty()); } _ => {} } } } } } _ => {} } (None, DunderAllFlags::INVALID_FORMAT) } let mut names: Vec<&str> = vec![]; let mut flags = DunderAllFlags::empty(); if let Some(value) = match stmt { Stmt::Assign(ast::StmtAssign { value, .. }) => Some(value), Stmt::AnnAssign(ast::StmtAnnAssign { value, .. }) => value.as_ref(), Stmt::AugAssign(ast::StmtAugAssign { value, .. }) => Some(value), _ => None, } { if let Expr::BinOp(ast::ExprBinOp { left, right, .. }) = value.as_ref() { let mut current_left = left; let mut current_right = right; loop { // Process the right side, which should be a "real" value. let (elts, new_flags) = extract_elts(current_right, |expr| is_builtin(expr)); flags |= new_flags; if let Some(elts) = elts { add_to_names(elts, &mut names, &mut flags); } // Process the left side, which can be a "real" value or the "rest" of the // binary operation. if let Expr::BinOp(ast::ExprBinOp { left, right, .. }) = current_left.as_ref() { current_left = left; current_right = right; } else { let (elts, new_flags) = extract_elts(current_left, |expr| is_builtin(expr)); flags |= new_flags; if let Some(elts) = elts { add_to_names(elts, &mut names, &mut flags); } break; } } } else { let (elts, new_flags) = extract_elts(value, |expr| is_builtin(expr)); flags |= new_flags; if let Some(elts) = elts { add_to_names(elts, &mut names, &mut flags); } } } (names, flags) }