Avoid including literal shell=True for truthy, non-True diagnostics (#8359)

## Summary

If the value of `shell` wasn't literally `True`, we now show a message
describing it as truthy, rather than the (misleading) `shell=True`
literal in the diagnostic.

Closes https://github.com/astral-sh/ruff/issues/8310.
This commit is contained in:
Charlie Marsh 2023-10-30 08:44:38 -07:00 committed by GitHub
parent daea870c3c
commit 161c093c06
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 161 additions and 94 deletions

View file

@ -1037,53 +1037,74 @@ pub fn is_unpacking_assignment(parent: &Stmt, child: &Expr) -> bool {
#[derive(Copy, Clone, Debug, PartialEq, is_macro::Is)]
pub enum Truthiness {
// An expression evaluates to `False`.
/// The expression is `True`.
True,
/// The expression is `False`.
False,
/// The expression evaluates to a `False`-like value (e.g., `None`, `0`, `[]`, `""`).
Falsey,
// An expression evaluates to `True`.
/// The expression evaluates to a `True`-like value (e.g., `1`, `"foo"`).
Truthy,
// An expression evaluates to an unknown value (e.g., a variable `x` of unknown type).
/// The expression evaluates to an unknown value (e.g., a variable `x` of unknown type).
Unknown,
}
impl From<Option<bool>> for Truthiness {
fn from(value: Option<bool>) -> Self {
match value {
Some(true) => Truthiness::Truthy,
Some(false) => Truthiness::Falsey,
None => Truthiness::Unknown,
}
}
}
impl From<Truthiness> for Option<bool> {
fn from(truthiness: Truthiness) -> Self {
match truthiness {
Truthiness::Truthy => Some(true),
Truthiness::Falsey => Some(false),
Truthiness::Unknown => None,
}
}
}
impl Truthiness {
/// Return the truthiness of an expression.
pub fn from_expr<F>(expr: &Expr, is_builtin: F) -> Self
where
F: Fn(&str) -> bool,
{
match expr {
Expr::StringLiteral(ast::ExprStringLiteral { value, .. }) => Some(!value.is_empty()),
Expr::BytesLiteral(ast::ExprBytesLiteral { value, .. }) => Some(!value.is_empty()),
Expr::StringLiteral(ast::ExprStringLiteral { value, .. }) => {
if value.is_empty() {
Self::Falsey
} else {
Self::Truthy
}
}
Expr::BytesLiteral(ast::ExprBytesLiteral { value, .. }) => {
if value.is_empty() {
Self::Falsey
} else {
Self::Truthy
}
}
Expr::NumberLiteral(ast::ExprNumberLiteral { value, .. }) => match value {
ast::Number::Int(int) => Some(*int != 0),
ast::Number::Float(float) => Some(*float != 0.0),
ast::Number::Complex { real, imag, .. } => Some(*real != 0.0 || *imag != 0.0),
ast::Number::Int(int) => {
if *int == 0 {
Self::Falsey
} else {
Self::Truthy
}
}
ast::Number::Float(float) => {
if *float == 0.0 {
Self::Falsey
} else {
Self::Truthy
}
}
ast::Number::Complex { real, imag, .. } => {
if *real == 0.0 && *imag == 0.0 {
Self::Falsey
} else {
Self::Truthy
}
}
},
Expr::BooleanLiteral(ast::ExprBooleanLiteral { value, .. }) => Some(*value),
Expr::NoneLiteral(_) => Some(false),
Expr::EllipsisLiteral(_) => Some(true),
Expr::BooleanLiteral(ast::ExprBooleanLiteral { value, .. }) => {
if *value {
Self::True
} else {
Self::False
}
}
Expr::NoneLiteral(_) => Self::Falsey,
Expr::EllipsisLiteral(_) => Self::Truthy,
Expr::FString(ast::ExprFString { values, .. }) => {
if values.is_empty() {
Some(false)
Self::Falsey
} else if values.iter().any(|value| {
if let Expr::StringLiteral(ast::ExprStringLiteral { value, .. }) = &value {
!value.is_empty()
@ -1091,15 +1112,27 @@ impl Truthiness {
false
}
}) {
Some(true)
Self::Truthy
} else {
None
Self::Unknown
}
}
Expr::List(ast::ExprList { elts, .. })
| Expr::Set(ast::ExprSet { elts, .. })
| Expr::Tuple(ast::ExprTuple { elts, .. }) => Some(!elts.is_empty()),
Expr::Dict(ast::ExprDict { keys, .. }) => Some(!keys.is_empty()),
| Expr::Tuple(ast::ExprTuple { elts, .. }) => {
if elts.is_empty() {
Self::Falsey
} else {
Self::Truthy
}
}
Expr::Dict(ast::ExprDict { keys, .. }) => {
if keys.is_empty() {
Self::Falsey
} else {
Self::Truthy
}
}
Expr::Call(ast::ExprCall {
func,
arguments: Arguments { args, keywords, .. },
@ -1109,23 +1142,30 @@ impl Truthiness {
if is_iterable_initializer(id.as_str(), |id| is_builtin(id)) {
if args.is_empty() && keywords.is_empty() {
// Ex) `list()`
Some(false)
Self::Falsey
} else if args.len() == 1 && keywords.is_empty() {
// Ex) `list([1, 2, 3])`
Self::from_expr(&args[0], is_builtin).into()
Self::from_expr(&args[0], is_builtin)
} else {
None
Self::Unknown
}
} else {
None
Self::Unknown
}
} else {
None
Self::Unknown
}
}
_ => None,
_ => Self::Unknown,
}
}
pub fn into_bool(self) -> Option<bool> {
match self {
Self::True | Self::Truthy => Some(true),
Self::False | Self::Falsey => Some(false),
Self::Unknown => None,
}
.into()
}
}