diff --git a/crates/ruff_linter/resources/test/fixtures/pylint/len_as_condition.py b/crates/ruff_linter/resources/test/fixtures/pylint/len_as_condition.py new file mode 100644 index 0000000000000..44570868bece1 --- /dev/null +++ b/crates/ruff_linter/resources/test/fixtures/pylint/len_as_condition.py @@ -0,0 +1,231 @@ +if len('TEST'): # [PLC1802] + pass + +if not len('TEST'): # [PLC1802] + pass + +z = [] +if z and len(['T', 'E', 'S', 'T']): # [PLC1802] + pass + +if True or len('TEST'): # [PLC1802] + pass + +if len('TEST') == 0: # Should be fine + pass + +if len('TEST') < 1: # Should be fine + pass + +if len('TEST') <= 0: # Should be fine + pass + +if 1 > len('TEST'): # Should be fine + pass + +if 0 >= len('TEST'): # Should be fine + pass + +if z and len('TEST') == 0: # Should be fine + pass + +if 0 == len('TEST') < 10: # Should be fine + pass + +# Should be fine +if 0 < 1 <= len('TEST') < 10: # [comparison-of-constants] + pass + +if 10 > len('TEST') != 0: # Should be fine + pass + +if 10 > len('TEST') > 1 > 0: # Should be fine + pass + +if 0 <= len('TEST') < 100: # Should be fine + pass + +if z or 10 > len('TEST') != 0: # Should be fine + pass + +if z: + pass +elif len('TEST'): # [PLC1802] + pass + +if z: + pass +elif not len('TEST'): # [PLC1802] + pass + +while len('TEST'): # [PLC1802] + pass + +while not len('TEST'): # [PLC1802] + pass + +while z and len('TEST'): # [PLC1802] + pass + +while not len('TEST') and z: # [PLC1802] + pass + +assert len('TEST') > 0 # Should be fine + +x = 1 if len('TEST') != 0 else 2 # Should be fine + +f_o_o = len('TEST') or 42 # Should be fine + +a = x and len(x) # Should be fine + +def some_func(): + return len('TEST') > 0 # Should be fine + +def github_issue_1325(): + l = [1, 2, 3] + length = len(l) if l else 0 # Should be fine + return length + +def github_issue_1331(*args): + assert False, len(args) # Should be fine + +def github_issue_1331_v2(*args): + assert len(args), args # [PLC1802] + +def github_issue_1331_v3(*args): + assert len(args) or z, args # [PLC1802] + +def github_issue_1331_v4(*args): + assert z and len(args), args # [PLC1802] + +b = bool(len(z)) # [PLC1802] +c = bool(len('TEST') or 42) # [PLC1802] + +def github_issue_1879(): + + class ClassWithBool(list): + def __bool__(self): + return True + + class ClassWithoutBool(list): + pass + + class ChildClassWithBool(ClassWithBool): + pass + + class ChildClassWithoutBool(ClassWithoutBool): + pass + + assert len(ClassWithBool()) + assert len(ChildClassWithBool()) + assert len(ClassWithoutBool()) # unintuitive?, in pylint: [PLC1802] + assert len(ChildClassWithoutBool()) # unintuitive?, in pylint: [PLC1802] + assert len(range(0)) # in pylint does throw [PLC1802] (stronger type inference), not here + assert len([t + 1 for t in []]) # [PLC1802] + assert len(u + 1 for u in []) # [PLC1802] + assert len({"1":(v + 1) for v in {}}) # [PLC1802] + assert len(set((w + 1) for w in set())) # [PLC1802] + + + import numpy + numpy_array = numpy.array([0]) + if len(numpy_array) > 0: + print('numpy_array') + if len(numpy_array): + print('numpy_array') + if numpy_array: + print('b') + + import pandas as pd + pandas_df = pd.DataFrame() + if len(pandas_df): + print("this works, but pylint tells me not to use len() without comparison") + if len(pandas_df) > 0: + print("this works and pylint likes it, but it's not the solution intended by PEP-8") + if pandas_df: + print("this does not work (truth value of dataframe is ambiguous)") + + def function_returning_list(r): + if r==1: + return [1] + return [2] + + def function_returning_int(r): + if r==1: + return 1 + return 2 + + def function_returning_generator(r): + for i in [r, 1, 2, 3]: + yield i + + def function_returning_comprehension(r): + return [x+1 for x in [r, 1, 2, 3]] + + def function_returning_function(r): + return function_returning_generator(r) + + assert len(function_returning_list(z)) # [PLC1802] differs from pylint + assert len(function_returning_int(z)) + # This should raise a PLC1802 once astroid can infer it + # See https://github.com/pylint-dev/pylint/pull/3821#issuecomment-743771514 + assert len(function_returning_generator(z)) + assert len(function_returning_comprehension(z)) + assert len(function_returning_function(z)) + + +def github_issue_4215(): + # Test undefined variables + # https://github.com/pylint-dev/pylint/issues/4215 + if len(undefined_var): # [undefined-variable] + pass + if len(undefined_var2[0]): # [undefined-variable] + pass + + +def f(cond:bool): + x = [1,2,3] + if cond: + x = [4,5,6] + if len(x): # this should be addressed + print(x) + +def g(cond:bool): + x = [1,2,3] + if cond: + x = [4,5,6] + if len(x): # this should be addressed + print(x) + del x + +def h(cond:bool): + x = [1,2,3] + x = 123 + if len(x): # ok + print(x) + +def outer(): + x = [1,2,3] + def inner(x:int): + return x+1 + if len(x): # [PLC1802] + print(x) + +def redefined(): + x = 123 + x = [1, 2, 3] + if len(x): # this should be addressed + print(x) + +global_seq = [1, 2, 3] + +def i(): + global global_seq + if len(global_seq): # ok + print(global_seq) + +def j(): + if False: + x = [1, 2, 3] + if len(x): # [PLC1802] should be fine + print(x) diff --git a/crates/ruff_linter/src/checkers/ast/analyze/expression.rs b/crates/ruff_linter/src/checkers/ast/analyze/expression.rs index 6cdc7725c62e7..7e389c79195d7 100644 --- a/crates/ruff_linter/src/checkers/ast/analyze/expression.rs +++ b/crates/ruff_linter/src/checkers/ast/analyze/expression.rs @@ -475,6 +475,9 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) { if checker.enabled(Rule::SuperWithoutBrackets) { pylint::rules::super_without_brackets(checker, func); } + if checker.enabled(Rule::LenAsCondition) { + pylint::rules::len_as_condition(checker, call); + } if checker.enabled(Rule::BitCount) { refurb::rules::bit_count(checker, call); } diff --git a/crates/ruff_linter/src/codes.rs b/crates/ruff_linter/src/codes.rs index be27d11fc6b1d..f216294c24ffc 100644 --- a/crates/ruff_linter/src/codes.rs +++ b/crates/ruff_linter/src/codes.rs @@ -191,6 +191,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> { (Pylint, "C0208") => (RuleGroup::Stable, rules::pylint::rules::IterationOverSet), (Pylint, "C0414") => (RuleGroup::Stable, rules::pylint::rules::UselessImportAlias), (Pylint, "C0415") => (RuleGroup::Preview, rules::pylint::rules::ImportOutsideTopLevel), + (Pylint, "C1802") => (RuleGroup::Preview, rules::pylint::rules::LenAsCondition), (Pylint, "C1901") => (RuleGroup::Preview, rules::pylint::rules::CompareToEmptyString), (Pylint, "C2401") => (RuleGroup::Stable, rules::pylint::rules::NonAsciiName), (Pylint, "C2403") => (RuleGroup::Stable, rules::pylint::rules::NonAsciiImportName), diff --git a/crates/ruff_linter/src/rules/pylint/mod.rs b/crates/ruff_linter/src/rules/pylint/mod.rs index 09392c125d243..8e34356c2baa7 100644 --- a/crates/ruff_linter/src/rules/pylint/mod.rs +++ b/crates/ruff_linter/src/rules/pylint/mod.rs @@ -219,6 +219,7 @@ mod tests { Rule::BadStaticmethodArgument, Path::new("bad_staticmethod_argument.py") )] + #[test_case(Rule::LenAsCondition, Path::new("len_as_condition.py"))] fn rules(rule_code: Rule, path: &Path) -> Result<()> { let snapshot = format!("{}_{}", rule_code.noqa_code(), path.to_string_lossy()); let diagnostics = test_path( diff --git a/crates/ruff_linter/src/rules/pylint/rules/len_as_condition.rs b/crates/ruff_linter/src/rules/pylint/rules/len_as_condition.rs new file mode 100644 index 0000000000000..50d6d0a66c174 --- /dev/null +++ b/crates/ruff_linter/src/rules/pylint/rules/len_as_condition.rs @@ -0,0 +1,168 @@ +use crate::checkers::ast::Checker; +use crate::fix::snippet::SourceCodeSnippet; +use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix}; +use ruff_macros::{derive_message_formats, violation}; +use ruff_python_ast::{self as ast, Expr, ExprCall, Parameter}; +use ruff_python_semantic::analyze::type_inference::{PythonType, ResolvedPythonType}; +use ruff_python_semantic::analyze::typing::find_binding_value; +use ruff_python_semantic::{BindingId, SemanticModel}; +use ruff_text_size::Ranged; + +/// ## What it does +/// Checks for usage of call of 'len' on sequences +/// in boolean test context. +/// +/// ## Why is this bad? +/// Empty sequences are considered false in a boolean context. +/// You can either remove the call to 'len' +/// or compare the length against a scalar. +/// +/// ## Example +/// ```python +/// fruits = ["orange", "apple"] +/// vegetables = [] +/// +/// if len(fruits): +/// print(fruits) +/// +/// if not len(vegetables): +/// print(vegetables) +/// ``` +/// +/// Use instead: +/// ```python +/// fruits = ["orange", "apple"] +/// +/// if fruits: +/// print(fruits) +/// +/// if not vegetables: +/// print(vegetables) +/// ``` +/// +/// ## References +/// [PEP 8: Programming Recommendations](https://peps.python.org/pep-0008/#programming-recommendations) +#[violation] +pub struct LenAsCondition { + expression: SourceCodeSnippet, +} + +impl AlwaysFixableViolation for LenAsCondition { + #[derive_message_formats] + fn message(&self) -> String { + if let Some(expression) = self.expression.full_display() { + format!("`len({expression})` used as condition without comparison") + } else { + "`len(SEQUENCE)` used as condition without comparison".to_string() + } + } + + fn fix_title(&self) -> String { + "Remove `len`".to_string() + } +} + +/// PLC1802 +pub(crate) fn len_as_condition(checker: &mut Checker, call: &ExprCall) { + let ExprCall { + func, arguments, .. + } = call; + let semantic = checker.semantic(); + + if !semantic.in_boolean_test() { + return; + } + + if !semantic.match_builtin_expr(func, "len") { + return; + } + + // Single argument and no keyword arguments + let [argument] = &*arguments.args else { return }; + if !arguments.keywords.is_empty() { + return; + } + + // Simple inferred sequence type (e.g., list, set, dict, tuple, string, generator) or a vararg. + if !is_sequence(argument, semantic) && !is_indirect_sequence(argument, semantic) { + return; + } + + let replacement = checker.locator().slice(argument.range()).to_string(); + + let mut diagnostic = Diagnostic::new( + LenAsCondition { + expression: SourceCodeSnippet::new(replacement.clone()), + }, + call.range(), + ); + + diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement( + // Generator without parentheses would create syntax error + if argument.is_generator_expr() { + format!("({replacement})") + } else { + replacement + }, + call.range(), + ))); + + checker.diagnostics.push(diagnostic); +} + +fn is_indirect_sequence(expr: &Expr, semantic: &SemanticModel) -> bool { + let Expr::Name(ast::ExprName { id: name, .. }) = expr else { + return false; + }; + + let scope = semantic.current_scope(); + let bindings: Vec = scope.get_all(name).collect(); + let [binding_id] = bindings.as_slice() else { + return false; + }; + + let binding = semantic.binding(*binding_id); + + // Attempt to find the binding's value + let Some(binding_value) = find_binding_value(binding, semantic) else { + // check for `vararg` + return binding.kind.is_argument() + && binding + .statement(semantic) + .and_then(|statement| statement.as_function_def_stmt()) + .and_then(|function| function.parameters.vararg.as_deref()) + .map_or(false, |Parameter { name: var_arg, .. }| { + var_arg.id() == name + }); + }; + + // If `binding_value` is found, check if it is a sequence + is_sequence(binding_value, semantic) +} + +fn is_sequence(expr: &Expr, semantic: &SemanticModel) -> bool { + // Check if the expression type is a direct sequence match (dict, list, set, tuple, generator, string) + if matches!( + ResolvedPythonType::from(expr), + ResolvedPythonType::Atom( + PythonType::Dict + | PythonType::List + | PythonType::Set + | PythonType::Tuple + | PythonType::Generator + | PythonType::String + ) + ) { + return true; + } + + // Check if the expression is a function call to a built-in sequence constructor + let Some(ExprCall { func, .. }) = expr.as_call_expr() else { + return false; + }; + + // Match against specific built-in constructors that return sequences + return semantic + .resolve_builtin_symbol(func) + .is_some_and(|func| matches!(func, "list" | "set" | "dict" | "tuple")); +} diff --git a/crates/ruff_linter/src/rules/pylint/rules/mod.rs b/crates/ruff_linter/src/rules/pylint/rules/mod.rs index 753c7d9a439d9..bc65223c075a4 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/mod.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/mod.rs @@ -39,6 +39,7 @@ pub(crate) use invalid_length_return::*; pub(crate) use invalid_str_return::*; pub(crate) use invalid_string_characters::*; pub(crate) use iteration_over_set::*; +pub(crate) use len_as_condition::*; pub(crate) use literal_membership::*; pub(crate) use load_before_global_declaration::*; pub(crate) use logging::*; @@ -143,6 +144,7 @@ mod invalid_length_return; mod invalid_str_return; mod invalid_string_characters; mod iteration_over_set; +mod len_as_condition; mod literal_membership; mod load_before_global_declaration; mod logging; diff --git a/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__PLC1802_len_as_condition.py.snap b/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__PLC1802_len_as_condition.py.snap new file mode 100644 index 0000000000000..9e9c1d6d2c322 --- /dev/null +++ b/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__PLC1802_len_as_condition.py.snap @@ -0,0 +1,416 @@ +--- +source: crates/ruff_linter/src/rules/pylint/mod.rs +snapshot_kind: text +--- +len_as_condition.py:1:4: PLC1802 [*] `len('TEST')` used as condition without comparison + | +1 | if len('TEST'): # [PLC1802] + | ^^^^^^^^^^^ PLC1802 +2 | pass + | + = help: Remove `len` + +ℹ Safe fix +1 |-if len('TEST'): # [PLC1802] + 1 |+if 'TEST': # [PLC1802] +2 2 | pass +3 3 | +4 4 | if not len('TEST'): # [PLC1802] + +len_as_condition.py:4:8: PLC1802 [*] `len('TEST')` used as condition without comparison + | +2 | pass +3 | +4 | if not len('TEST'): # [PLC1802] + | ^^^^^^^^^^^ PLC1802 +5 | pass + | + = help: Remove `len` + +ℹ Safe fix +1 1 | if len('TEST'): # [PLC1802] +2 2 | pass +3 3 | +4 |-if not len('TEST'): # [PLC1802] + 4 |+if not 'TEST': # [PLC1802] +5 5 | pass +6 6 | +7 7 | z = [] + +len_as_condition.py:8:10: PLC1802 [*] `len(['T', 'E', 'S', 'T'])` used as condition without comparison + | +7 | z = [] +8 | if z and len(['T', 'E', 'S', 'T']): # [PLC1802] + | ^^^^^^^^^^^^^^^^^^^^^^^^^ PLC1802 +9 | pass + | + = help: Remove `len` + +ℹ Safe fix +5 5 | pass +6 6 | +7 7 | z = [] +8 |-if z and len(['T', 'E', 'S', 'T']): # [PLC1802] + 8 |+if z and ['T', 'E', 'S', 'T']: # [PLC1802] +9 9 | pass +10 10 | +11 11 | if True or len('TEST'): # [PLC1802] + +len_as_condition.py:11:12: PLC1802 [*] `len('TEST')` used as condition without comparison + | + 9 | pass +10 | +11 | if True or len('TEST'): # [PLC1802] + | ^^^^^^^^^^^ PLC1802 +12 | pass + | + = help: Remove `len` + +ℹ Safe fix +8 8 | if z and len(['T', 'E', 'S', 'T']): # [PLC1802] +9 9 | pass +10 10 | +11 |-if True or len('TEST'): # [PLC1802] + 11 |+if True or 'TEST': # [PLC1802] +12 12 | pass +13 13 | +14 14 | if len('TEST') == 0: # Should be fine + +len_as_condition.py:53:6: PLC1802 [*] `len('TEST')` used as condition without comparison + | +51 | if z: +52 | pass +53 | elif len('TEST'): # [PLC1802] + | ^^^^^^^^^^^ PLC1802 +54 | pass + | + = help: Remove `len` + +ℹ Safe fix +50 50 | +51 51 | if z: +52 52 | pass +53 |-elif len('TEST'): # [PLC1802] + 53 |+elif 'TEST': # [PLC1802] +54 54 | pass +55 55 | +56 56 | if z: + +len_as_condition.py:58:10: PLC1802 [*] `len('TEST')` used as condition without comparison + | +56 | if z: +57 | pass +58 | elif not len('TEST'): # [PLC1802] + | ^^^^^^^^^^^ PLC1802 +59 | pass + | + = help: Remove `len` + +ℹ Safe fix +55 55 | +56 56 | if z: +57 57 | pass +58 |-elif not len('TEST'): # [PLC1802] + 58 |+elif not 'TEST': # [PLC1802] +59 59 | pass +60 60 | +61 61 | while len('TEST'): # [PLC1802] + +len_as_condition.py:61:7: PLC1802 [*] `len('TEST')` used as condition without comparison + | +59 | pass +60 | +61 | while len('TEST'): # [PLC1802] + | ^^^^^^^^^^^ PLC1802 +62 | pass + | + = help: Remove `len` + +ℹ Safe fix +58 58 | elif not len('TEST'): # [PLC1802] +59 59 | pass +60 60 | +61 |-while len('TEST'): # [PLC1802] + 61 |+while 'TEST': # [PLC1802] +62 62 | pass +63 63 | +64 64 | while not len('TEST'): # [PLC1802] + +len_as_condition.py:64:11: PLC1802 [*] `len('TEST')` used as condition without comparison + | +62 | pass +63 | +64 | while not len('TEST'): # [PLC1802] + | ^^^^^^^^^^^ PLC1802 +65 | pass + | + = help: Remove `len` + +ℹ Safe fix +61 61 | while len('TEST'): # [PLC1802] +62 62 | pass +63 63 | +64 |-while not len('TEST'): # [PLC1802] + 64 |+while not 'TEST': # [PLC1802] +65 65 | pass +66 66 | +67 67 | while z and len('TEST'): # [PLC1802] + +len_as_condition.py:67:13: PLC1802 [*] `len('TEST')` used as condition without comparison + | +65 | pass +66 | +67 | while z and len('TEST'): # [PLC1802] + | ^^^^^^^^^^^ PLC1802 +68 | pass + | + = help: Remove `len` + +ℹ Safe fix +64 64 | while not len('TEST'): # [PLC1802] +65 65 | pass +66 66 | +67 |-while z and len('TEST'): # [PLC1802] + 67 |+while z and 'TEST': # [PLC1802] +68 68 | pass +69 69 | +70 70 | while not len('TEST') and z: # [PLC1802] + +len_as_condition.py:70:11: PLC1802 [*] `len('TEST')` used as condition without comparison + | +68 | pass +69 | +70 | while not len('TEST') and z: # [PLC1802] + | ^^^^^^^^^^^ PLC1802 +71 | pass + | + = help: Remove `len` + +ℹ Safe fix +67 67 | while z and len('TEST'): # [PLC1802] +68 68 | pass +69 69 | +70 |-while not len('TEST') and z: # [PLC1802] + 70 |+while not 'TEST' and z: # [PLC1802] +71 71 | pass +72 72 | +73 73 | assert len('TEST') > 0 # Should be fine + +len_as_condition.py:93:12: PLC1802 [*] `len(args)` used as condition without comparison + | +92 | def github_issue_1331_v2(*args): +93 | assert len(args), args # [PLC1802] + | ^^^^^^^^^ PLC1802 +94 | +95 | def github_issue_1331_v3(*args): + | + = help: Remove `len` + +ℹ Safe fix +90 90 | assert False, len(args) # Should be fine +91 91 | +92 92 | def github_issue_1331_v2(*args): +93 |- assert len(args), args # [PLC1802] + 93 |+ assert args, args # [PLC1802] +94 94 | +95 95 | def github_issue_1331_v3(*args): +96 96 | assert len(args) or z, args # [PLC1802] + +len_as_condition.py:96:12: PLC1802 [*] `len(args)` used as condition without comparison + | +95 | def github_issue_1331_v3(*args): +96 | assert len(args) or z, args # [PLC1802] + | ^^^^^^^^^ PLC1802 +97 | +98 | def github_issue_1331_v4(*args): + | + = help: Remove `len` + +ℹ Safe fix +93 93 | assert len(args), args # [PLC1802] +94 94 | +95 95 | def github_issue_1331_v3(*args): +96 |- assert len(args) or z, args # [PLC1802] + 96 |+ assert args or z, args # [PLC1802] +97 97 | +98 98 | def github_issue_1331_v4(*args): +99 99 | assert z and len(args), args # [PLC1802] + +len_as_condition.py:99:18: PLC1802 [*] `len(args)` used as condition without comparison + | + 98 | def github_issue_1331_v4(*args): + 99 | assert z and len(args), args # [PLC1802] + | ^^^^^^^^^ PLC1802 +100 | +101 | b = bool(len(z)) # [PLC1802] + | + = help: Remove `len` + +ℹ Safe fix +96 96 | assert len(args) or z, args # [PLC1802] +97 97 | +98 98 | def github_issue_1331_v4(*args): +99 |- assert z and len(args), args # [PLC1802] + 99 |+ assert z and args, args # [PLC1802] +100 100 | +101 101 | b = bool(len(z)) # [PLC1802] +102 102 | c = bool(len('TEST') or 42) # [PLC1802] + +len_as_condition.py:101:10: PLC1802 [*] `len(z)` used as condition without comparison + | + 99 | assert z and len(args), args # [PLC1802] +100 | +101 | b = bool(len(z)) # [PLC1802] + | ^^^^^^ PLC1802 +102 | c = bool(len('TEST') or 42) # [PLC1802] + | + = help: Remove `len` + +ℹ Safe fix +98 98 | def github_issue_1331_v4(*args): +99 99 | assert z and len(args), args # [PLC1802] +100 100 | +101 |-b = bool(len(z)) # [PLC1802] + 101 |+b = bool(z) # [PLC1802] +102 102 | c = bool(len('TEST') or 42) # [PLC1802] +103 103 | +104 104 | def github_issue_1879(): + +len_as_condition.py:102:10: PLC1802 [*] `len('TEST')` used as condition without comparison + | +101 | b = bool(len(z)) # [PLC1802] +102 | c = bool(len('TEST') or 42) # [PLC1802] + | ^^^^^^^^^^^ PLC1802 +103 | +104 | def github_issue_1879(): + | + = help: Remove `len` + +ℹ Safe fix +99 99 | assert z and len(args), args # [PLC1802] +100 100 | +101 101 | b = bool(len(z)) # [PLC1802] +102 |-c = bool(len('TEST') or 42) # [PLC1802] + 102 |+c = bool('TEST' or 42) # [PLC1802] +103 103 | +104 104 | def github_issue_1879(): +105 105 | + +len_as_condition.py:124:12: PLC1802 [*] `len([t + 1 for t in []])` used as condition without comparison + | +122 | assert len(ChildClassWithoutBool()) # unintuitive?, in pylint: [PLC1802] +123 | assert len(range(0)) # in pylint does throw [PLC1802] (stronger type inference), not here +124 | assert len([t + 1 for t in []]) # [PLC1802] + | ^^^^^^^^^^^^^^^^^^^^^^^^ PLC1802 +125 | assert len(u + 1 for u in []) # [PLC1802] +126 | assert len({"1":(v + 1) for v in {}}) # [PLC1802] + | + = help: Remove `len` + +ℹ Safe fix +121 121 | assert len(ClassWithoutBool()) # unintuitive?, in pylint: [PLC1802] +122 122 | assert len(ChildClassWithoutBool()) # unintuitive?, in pylint: [PLC1802] +123 123 | assert len(range(0)) # in pylint does throw [PLC1802] (stronger type inference), not here +124 |- assert len([t + 1 for t in []]) # [PLC1802] + 124 |+ assert [t + 1 for t in []] # [PLC1802] +125 125 | assert len(u + 1 for u in []) # [PLC1802] +126 126 | assert len({"1":(v + 1) for v in {}}) # [PLC1802] +127 127 | assert len(set((w + 1) for w in set())) # [PLC1802] + +len_as_condition.py:125:12: PLC1802 [*] `len(u + 1 for u in [])` used as condition without comparison + | +123 | assert len(range(0)) # in pylint does throw [PLC1802] (stronger type inference), not here +124 | assert len([t + 1 for t in []]) # [PLC1802] +125 | assert len(u + 1 for u in []) # [PLC1802] + | ^^^^^^^^^^^^^^^^^^^^^^ PLC1802 +126 | assert len({"1":(v + 1) for v in {}}) # [PLC1802] +127 | assert len(set((w + 1) for w in set())) # [PLC1802] + | + = help: Remove `len` + +ℹ Safe fix +122 122 | assert len(ChildClassWithoutBool()) # unintuitive?, in pylint: [PLC1802] +123 123 | assert len(range(0)) # in pylint does throw [PLC1802] (stronger type inference), not here +124 124 | assert len([t + 1 for t in []]) # [PLC1802] +125 |- assert len(u + 1 for u in []) # [PLC1802] + 125 |+ assert (u + 1 for u in []) # [PLC1802] +126 126 | assert len({"1":(v + 1) for v in {}}) # [PLC1802] +127 127 | assert len(set((w + 1) for w in set())) # [PLC1802] +128 128 | + +len_as_condition.py:126:12: PLC1802 [*] `len({"1":(v + 1) for v in {}})` used as condition without comparison + | +124 | assert len([t + 1 for t in []]) # [PLC1802] +125 | assert len(u + 1 for u in []) # [PLC1802] +126 | assert len({"1":(v + 1) for v in {}}) # [PLC1802] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PLC1802 +127 | assert len(set((w + 1) for w in set())) # [PLC1802] + | + = help: Remove `len` + +ℹ Safe fix +123 123 | assert len(range(0)) # in pylint does throw [PLC1802] (stronger type inference), not here +124 124 | assert len([t + 1 for t in []]) # [PLC1802] +125 125 | assert len(u + 1 for u in []) # [PLC1802] +126 |- assert len({"1":(v + 1) for v in {}}) # [PLC1802] + 126 |+ assert {"1":(v + 1) for v in {}} # [PLC1802] +127 127 | assert len(set((w + 1) for w in set())) # [PLC1802] +128 128 | +129 129 | + +len_as_condition.py:127:12: PLC1802 [*] `len(set((w + 1) for w in set()))` used as condition without comparison + | +125 | assert len(u + 1 for u in []) # [PLC1802] +126 | assert len({"1":(v + 1) for v in {}}) # [PLC1802] +127 | assert len(set((w + 1) for w in set())) # [PLC1802] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PLC1802 + | + = help: Remove `len` + +ℹ Safe fix +124 124 | assert len([t + 1 for t in []]) # [PLC1802] +125 125 | assert len(u + 1 for u in []) # [PLC1802] +126 126 | assert len({"1":(v + 1) for v in {}}) # [PLC1802] +127 |- assert len(set((w + 1) for w in set())) # [PLC1802] + 127 |+ assert set((w + 1) for w in set()) # [PLC1802] +128 128 | +129 129 | +130 130 | import numpy + +len_as_condition.py:211:8: PLC1802 [*] `len(x)` used as condition without comparison + | +209 | def inner(x:int): +210 | return x+1 +211 | if len(x): # [PLC1802] + | ^^^^^^ PLC1802 +212 | print(x) + | + = help: Remove `len` + +ℹ Safe fix +208 208 | x = [1,2,3] +209 209 | def inner(x:int): +210 210 | return x+1 +211 |- if len(x): # [PLC1802] + 211 |+ if x: # [PLC1802] +212 212 | print(x) +213 213 | +214 214 | def redefined(): + +len_as_condition.py:230:8: PLC1802 [*] `len(x)` used as condition without comparison + | +228 | if False: +229 | x = [1, 2, 3] +230 | if len(x): # [PLC1802] should be fine + | ^^^^^^ PLC1802 +231 | print(x) + | + = help: Remove `len` + +ℹ Safe fix +227 227 | def j(): +228 228 | if False: +229 229 | x = [1, 2, 3] +230 |- if len(x): # [PLC1802] should be fine + 230 |+ if x: # [PLC1802] should be fine +231 231 | print(x) diff --git a/ruff.schema.json b/ruff.schema.json index b1e24b4776030..d008fc07ad31e 100644 --- a/ruff.schema.json +++ b/ruff.schema.json @@ -3410,6 +3410,9 @@ "PLC0414", "PLC0415", "PLC1", + "PLC18", + "PLC180", + "PLC1802", "PLC19", "PLC190", "PLC1901",