From 0741949b8a9d373d5462e321f30ca59820cdde33 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sun, 8 Jan 2023 15:13:22 +0100 Subject: [PATCH] Fix `used-before-assignment` false positive for walrus operators in ifs (#8029) (#8033) Co-authored-by: Pierre Sassoulas (cherry picked from commit 6ac908ffe04e18b62419dabac9686b107bee278d) Co-authored-by: Zen Lee <53538590+zenlyj@users.noreply.github.com> --- doc/whatsnew/fragments/7779.false_positive | 4 ++ pylint/checkers/variables.py | 13 ++++-- .../u/used/used_before_assignment_ternary.py | 41 +++++++++++++++++++ .../u/used/used_before_assignment_ternary.rc | 2 + .../u/used/used_before_assignment_ternary.txt | 3 ++ 5 files changed, 60 insertions(+), 3 deletions(-) create mode 100644 doc/whatsnew/fragments/7779.false_positive create mode 100644 tests/functional/u/used/used_before_assignment_ternary.py create mode 100644 tests/functional/u/used/used_before_assignment_ternary.rc create mode 100644 tests/functional/u/used/used_before_assignment_ternary.txt diff --git a/doc/whatsnew/fragments/7779.false_positive b/doc/whatsnew/fragments/7779.false_positive new file mode 100644 index 0000000000..2591f17a4c --- /dev/null +++ b/doc/whatsnew/fragments/7779.false_positive @@ -0,0 +1,4 @@ +Fixes ``used-before-assignment`` false positive when the walrus operator +is used in a ternary operator. + +Closes #7779 diff --git a/pylint/checkers/variables.py b/pylint/checkers/variables.py index 1128577c40..3416834148 100644 --- a/pylint/checkers/variables.py +++ b/pylint/checkers/variables.py @@ -2055,9 +2055,16 @@ def _maybe_used_and_assigned_at_once(defstmt: nodes.Statement) -> bool: return True if isinstance(value, nodes.Lambda) and isinstance(value.body, nodes.IfExp): return True - return isinstance(value, nodes.Call) and ( - any(isinstance(kwarg.value, nodes.IfExp) for kwarg in value.keywords) - or any(isinstance(arg, nodes.IfExp) for arg in value.args) + if not isinstance(value, nodes.Call): + return False + return any( + any(isinstance(kwarg.value, nodes.IfExp) for kwarg in call.keywords) + or any(isinstance(arg, nodes.IfExp) for arg in call.args) + or ( + isinstance(call.func, nodes.Attribute) + and isinstance(call.func.expr, nodes.IfExp) + ) + for call in value.nodes_of_class(klass=nodes.Call) ) def _is_only_type_assignment( diff --git a/tests/functional/u/used/used_before_assignment_ternary.py b/tests/functional/u/used/used_before_assignment_ternary.py new file mode 100644 index 0000000000..2ee4440d82 --- /dev/null +++ b/tests/functional/u/used/used_before_assignment_ternary.py @@ -0,0 +1,41 @@ +"""Tests for used-before-assignment false positive from ternary expression with walrus operator""" +# pylint: disable=unnecessary-lambda-assignment, unused-variable, disallowed-name, invalid-name + +def invalid(): + """invalid cases that will trigger used-before-assignment""" + var = foo(a, '', '') # [used-before-assignment] + print(str(1 if (a:=-1) else 0)) + var = bar(b) # [used-before-assignment] + var = c*c # [used-before-assignment] + var = 1 if (b:=-1) else 0 + var = 1 if (c:=-1) else 0 + +def attribute_call_valid(): + """assignment with attribute calls""" + var = (a if (a:='a') else '').lower() + var = ('' if (b:='b') else b).lower() + var = (c if (c:='c') else c).upper().lower().replace('', '').strip() + var = ''.strip().replace('', '' + (e if (e:='e') else '').lower()) + +def function_call_arg_valid(): + """assignment as function call arguments""" + var = str(a if (a:='a') else '') + var = str('' if (b:='b') else b) + var = foo(1, c if (c:=1) else 0, 1) + print(foo('', '', foo('', str(int(d if (d:='1') else '')), ''))) + +def function_call_keyword_valid(): + """assignment as function call keywords""" + var = foo(x=a if (a:='1') else '', y='', z='') + var = foo(x='', y=foo(x='', y='', z=b if (b:='1') else ''), z='') + +def complex_valid(): + """assignment within complex call expression""" + var = str(bar(bar(a if (a:=1) else 0))).lower().upper() + print(foo(x=foo(''.replace('', str(b if (b:=1) else 0).upper()), '', z=''), y='', z='')) + +def foo(x, y, z): + """helper function for tests""" + return x+y+z + +bar = lambda x : x diff --git a/tests/functional/u/used/used_before_assignment_ternary.rc b/tests/functional/u/used/used_before_assignment_ternary.rc new file mode 100644 index 0000000000..85fc502b37 --- /dev/null +++ b/tests/functional/u/used/used_before_assignment_ternary.rc @@ -0,0 +1,2 @@ +[testoptions] +min_pyver=3.8 diff --git a/tests/functional/u/used/used_before_assignment_ternary.txt b/tests/functional/u/used/used_before_assignment_ternary.txt new file mode 100644 index 0000000000..d991970e4c --- /dev/null +++ b/tests/functional/u/used/used_before_assignment_ternary.txt @@ -0,0 +1,3 @@ +used-before-assignment:6:14:6:15:invalid:Using variable 'a' before assignment:HIGH +used-before-assignment:8:14:8:15:invalid:Using variable 'b' before assignment:HIGH +used-before-assignment:9:10:9:11:invalid:Using variable 'c' before assignment:HIGH