[flake8-simplify] Fix truthiness assumption for non-iterable arguments in tuple/list/set calls (SIM222, SIM223) (#21479)

## Summary

Fixes false positives in SIM222 and SIM223 where truthiness was
incorrectly assumed for `tuple(x)`, `list(x)`, `set(x)` when `x` is not
iterable.

Fixes #21473.

## Problem

`Truthiness::from_expr` recursively called itself on arguments to
iterable initializers (`tuple`, `list`, `set`) without checking if the
argument is iterable, causing false positives for cases like `tuple(0)
or True` and `tuple("") or True`.

## Approach

Added `is_definitely_not_iterable` helper and updated
`Truthiness::from_expr` to return `Unknown` for non-iterable arguments
(numbers, booleans, None) and string literals when called with iterable
initializers, preventing incorrect truthiness assumptions.

## Test Plan

Added test cases to `SIM222.py` and `SIM223.py` for `tuple("")`,
`tuple(0)`, `tuple(1)`, `tuple(False)`, and `tuple(None)` with `or True`
and `and False` patterns.

---------

Co-authored-by: Brent Westbrook <brentrwestbrook@gmail.com>
This commit is contained in:
Dan Parizher
2025-12-01 16:57:51 -05:00
committed by GitHub
parent bc44dc2afb
commit f052bd644c
5 changed files with 80 additions and 8 deletions

View File

@@ -1322,14 +1322,22 @@ impl Truthiness {
&& arguments.keywords.is_empty()
{
// Ex) `list([1, 2, 3])`
// For tuple(generator), we can't determine statically if the result will
// be empty or not, so return Unknown. The generator itself is truthy, but
// tuple(empty_generator) is falsy. ListComp and SetComp are handled by
// recursing into Self::from_expr below, which returns Unknown for them.
if argument.is_generator_expr() {
Self::Unknown
} else {
Self::from_expr(argument, is_builtin)
match argument {
// Return Unknown for types with definite truthiness that might
// result in empty iterables (t-strings and generators) or will
// raise a type error (non-iterable types like numbers, booleans,
// None, etc.).
Expr::NumberLiteral(_)
| Expr::BooleanLiteral(_)
| Expr::NoneLiteral(_)
| Expr::EllipsisLiteral(_)
| Expr::TString(_)
| Expr::Lambda(_)
| Expr::Generator(_) => Self::Unknown,
// Recurse for all other types - collections, comprehensions, variables, etc.
// StringLiteral, FString, and BytesLiteral recurse because Self::from_expr
// correctly handles their truthiness (checking if empty or not).
_ => Self::from_expr(argument, is_builtin),
}
} else {
Self::Unknown