Skip to content
Merged
Original file line number Diff line number Diff line change
Expand Up @@ -216,3 +216,12 @@ def get_items_list():

def get_items_set():
return tuple({item for item in items}) or None # OK


# https://github.com/astral-sh/ruff/issues/21473
tuple("") or True # OK
tuple(t"") or True # OK
tuple(0) or True # OK
tuple(1) or True # OK
tuple(False) or True # OK
tuple(None) or True # OK
Original file line number Diff line number Diff line change
Expand Up @@ -157,3 +157,12 @@

# https://github.com/astral-sh/ruff/issues/7127
def f(a: "'' and 'b'"): ...


# https://github.com/astral-sh/ruff/issues/21473
tuple("") and False # OK
tuple(t"") and False # OK
tuple(0) and False # OK
tuple(1) and False # OK
tuple(False) and False # OK
tuple(None) and False # OK
Original file line number Diff line number Diff line change
Expand Up @@ -174,14 +174,3 @@ PT015 Assertion always fails, replace with `pytest.fail()`
| ^^^^^^^^^^^^^^^^^
25 | assert tuple("")
|

PT015 Assertion always fails, replace with `pytest.fail()`
--> PT015.py:25:5
|
23 | assert list([])
24 | assert set(set())
25 | assert tuple("")
| ^^^^^^^^^^^^^^^^
26 |
27 | # https://github.com/astral-sh/ruff/issues/19935
|
58 changes: 57 additions & 1 deletion crates/ruff_python_ast/src/helpers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1329,7 +1329,63 @@ impl Truthiness {
if argument.is_generator_expr() {
Self::Unknown
} else {
Self::from_expr(argument, is_builtin)
// Return Unknown for types with definite truthiness that might result
// in empty iterables or will raise a type error. Explicitly list types
// we recurse for (types without definite truthiness or where Self::from_expr
// correctly handles the truthiness).
match argument {
// Types that raise TypeError when passed to tuple/list/set
Expr::NumberLiteral(_)
| Expr::BooleanLiteral(_)
| Expr::NoneLiteral(_)
| Expr::EllipsisLiteral(_) => {
// For non-iterable arguments like numbers, booleans, None, etc.,
// we can't assume truthiness. For example, tuple(0) raises TypeError.
Self::Unknown
}
// Types with definite truthiness that might result in empty iterables
Expr::StringLiteral(_)
| Expr::TString(_)
| Expr::FString(_)
| Expr::BytesLiteral(_) => {
// For strings/t-strings/f-strings/bytes, tuple("") creates an empty tuple
// which is falsy, but tuple("a") creates a non-empty tuple which
// is truthy. However, we can't assume truthiness matches because
// we conflate truthiness with non-emptiness. For SIM222/SIM223,
// we should be conservative and return Unknown to avoid false positives.
Self::Unknown
}
// Recurse for collections - we need to check if they're empty
Expr::List(_)
| Expr::Set(_)
| Expr::Tuple(_)
| 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
Expand Down