Repository: astral-sh/ruff
File: crates/ruff_python_ast/src/helpers.rs (Rust)

Code before:
                                    | Expr::Dict(_) => Self::from_expr(argument, is_builtin),
                                    // Recurse for comprehensions - they return Unknown in from_expr
                                    Expr::ListComp(_) | Expr::SetComp(_) | Expr::DictComp(_) => {
                                        Self::from_expr(argument, is_builtin)
                                    }
                                    // Recurse for variables and other expressions without definite truthiness
                                    Expr::Name(_)
                                    | Expr::Call(_)
                                    | Expr::Attribute(_)
                                    | Expr::Subscript(_)
                                    | Expr::BinOp(_)
                                    | Expr::UnaryOp(_)
                                    | Expr::If(_)
                                    | Expr::Starred(_)
                                    | Expr::Await(_)
                                    | Expr::Yield(_)
                                    | Expr::YieldFrom(_)
                                    | Expr::Compare(_) => Self::from_expr(argument, is_builtin),
                                    // Return Unknown for types with definite truthiness that don't make sense to recurse
                                    Expr::Lambda(_) | Expr::Generator(_) => {
                                        // Lambda and Generator are always truthy, but when passed to tuple/list/set,
                                        // they might result in empty iterables or raise errors. Return Unknown.
                                        Self::Unknown
                                    }
                                    // Default to Unknown for any other types
                                    _ => Self::Unknown,
                                }
                            }
                        } else {
                            Self::Unknown
                        }
                    } else {
                        Self::Unknown
                    }
                } else {
                    Self::Unknown
                }
            }
            _ => Self::Unknown,
        }
    }

    pub fn into_bool(self) -> Option<bool> {
        match self {
            Self::True | Self::Truthy => Some(true),
            Self::False | Self::Falsey => Some(false),
            Self::None => Some(false),
            Self::Unknown => None,
        }
    }
}

Review comment (suggestion):
I think it would be more clear if we grouped all of the expressions into one recursive arm and one `Self::Unknown` arm. Is the default `_` matching anything here? It seems like most cases are enumerated.

I'm kind of second-guessing my suggestion to list them all, but let's get the groups sorted out conclusively, and then we can decide to trim it down if it seems excessive.

Code after:
        }
    }
}

/// Returns `true` if the expression definitely resolves to a non-empty string, when used as an
/// f-string expression, or `false` if the expression may resolve to an empty string.
fn is_non_empty_f_string(expr: &ast::ExprFString) -> bool {
    fn inner(expr: &Expr) -> bool {
        match expr {
            // When stringified, these expressions are always non-empty.
            Expr::Lambda(_) => true,
            Expr::Dict(_) => true,
            Expr::Set(_) => true,
            Expr::ListComp(_) => true,
            Expr::SetComp(_) => true,
            Expr::DictComp(_) => true,
            Expr::Compare(_) => true,
            Expr::NumberLiteral(_) => true,
            Expr::BooleanLiteral(_) => true,
            Expr::NoneLiteral(_) => true,
            Expr::EllipsisLiteral(_) => true,
            Expr::List(_) => true,
            Expr::Tuple(_) => true,
            Expr::TString(_) => true,

            // These expressions must resolve to the inner expression.
            Expr::If(ast::ExprIf { body, orelse, .. }) => inner(body) && inner(orelse),
            Expr::Named(ast::ExprNamed { value, .. }) => inner(value),

            // These expressions are complex. We can't determine whether they're empty or not.
            Expr::BoolOp(ast::ExprBoolOp { .. }) => false,
            Expr::BinOp(ast::ExprBinOp { .. }) => false,
            Expr::UnaryOp(ast::ExprUnaryOp { .. }) => false,
            Expr::Generator(_) => false,
            Expr::Await(_) => false,
            Expr::Yield(_) => false,
            Expr::YieldFrom(_) => false,
            Expr::Call(_) => false,
            Expr::Attribute(_) => false,
            Expr::Subscript(_) => false,
            Expr::Starred(_) => false,
            Expr::Name(_) => false,
            Expr::Slice(_) => false,
            Expr::IpyEscapeCommand(_) => false,

            // These literals may or may not be empty.
            Expr::FString(f_string) => is_non_empty_f_string(f_string),
            // These literals may or may not be empty.
            Expr::StringLiteral(ast::ExprStringLiteral { value, .. }) => !value.is_empty(),
            // Confusingly, f"{b""}" renders as the string 'b""', which is non-empty.
            // Therefore, any bytes interpolation is guaranteed non-empty when stringified.