From 6d6a326e922a98c10516dd76e15db47ab7974d2e Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Thu, 25 Mar 2021 22:26:01 +0100 Subject: [PATCH] Improve handling of assignment expressions --- ChangeLog | 4 ++++ pylint/checkers/variables.py | 23 +++++++++++++++++++- pylint/constants.py | 1 + tests/functional/a/assignment_expression.py | 21 ++++++++++++++++++ tests/functional/a/assignment_expression.txt | 7 +++--- 5 files changed, 52 insertions(+), 4 deletions(-) diff --git a/ChangeLog b/ChangeLog index e9612696f9..c6914c618b 100644 --- a/ChangeLog +++ b/ChangeLog @@ -71,6 +71,10 @@ Release date: TBA Closes #4218 +* Improve handling of assignment expressions, better edge case handling + + Closes #3763, #4238 + What's New in Pylint 2.7.2? =========================== diff --git a/pylint/checkers/variables.py b/pylint/checkers/variables.py index 3d7eadc20d..f9c1f0fe4c 100644 --- a/pylint/checkers/variables.py +++ b/pylint/checkers/variables.py @@ -59,6 +59,7 @@ from pylint.checkers import BaseChecker, utils from pylint.checkers.utils import is_postponed_evaluation_enabled +from pylint.constants import PY39_PLUS from pylint.interfaces import HIGH, INFERENCE, INFERENCE_FAILURE, IAstroidChecker from pylint.utils import get_global_option @@ -1410,7 +1411,15 @@ def _is_variable_violation( # same line as the function definition maybee0601 = False elif ( - isinstance(defstmt, astroid.Assign) + isinstance( + defstmt, + ( + astroid.Assign, + astroid.AnnAssign, + astroid.AugAssign, + astroid.Expr, + ), + ) and isinstance(defstmt.value, astroid.IfExp) and frame is defframe and defframe.parent_of(node) @@ -1433,6 +1442,18 @@ def _is_variable_violation( and defnode.col_offset < node.col_offset ) or (defnode.lineno < node.lineno) + or ( + # Issue in the `ast` module until py39 + # Nodes in a multiline string have the same lineno + # Could be false-positive without check + not PY39_PLUS + and defnode.lineno == node.lineno + and isinstance( + defstmt, + (astroid.Assign, astroid.AnnAssign, astroid.AugAssign), + ) + and isinstance(defstmt.value, astroid.JoinedStr) + ) ) ): # Expressions, with assignment expressions diff --git a/pylint/constants.py b/pylint/constants.py index 929eed97ee..7181cb3550 100644 --- a/pylint/constants.py +++ b/pylint/constants.py @@ -8,6 +8,7 @@ from pylint.__pkginfo__ import version as pylint_version PY38_PLUS = sys.version_info[:2] >= (3, 8) +PY39_PLUS = sys.version_info[:2] >= (3, 9) PY310_PLUS = sys.version_info[:2] >= (3, 10) diff --git a/tests/functional/a/assignment_expression.py b/tests/functional/a/assignment_expression.py index ed8400163d..aa6a27874f 100644 --- a/tests/functional/a/assignment_expression.py +++ b/tests/functional/a/assignment_expression.py @@ -8,6 +8,9 @@ x = False x = b if (b := True) else False +x2: bool = b2 if (b2 := True) else False +x3 = 0 +x3 += b3 if (b3 := 4) else 6 a = ["a ", "b ", "c "] c = [text for el in a if (text := el.strip()) == "b"] @@ -49,6 +52,24 @@ def func(): print(function()) +# https://github.com/PyCQA/pylint/issues/3763 +foo if (foo := 3 - 2) > 0 else 0 # [pointless-statement] + + +# https://github.com/PyCQA/pylint/issues/4238 +l1 = f'The number {(count1 := 4)} ' \ + f'is equal to {count1}' +l2: str = ( + f'The number {(count2 := 4)} ' + f'is equal to {count2}' +) +l3 = "Hello " +l3 += ( + f'The number {(count3 := 4)} ' + f'is equal to {count3}' +) + + # check wrong usage assert err_a, (err_a := 2) # [used-before-assignment] print(err_b and (err_b := 2)) # [used-before-assignment] diff --git a/tests/functional/a/assignment_expression.txt b/tests/functional/a/assignment_expression.txt index df472fdd20..1be4c1389d 100644 --- a/tests/functional/a/assignment_expression.txt +++ b/tests/functional/a/assignment_expression.txt @@ -1,3 +1,4 @@ -used-before-assignment:53:7::Using variable 'err_a' before assignment -used-before-assignment:54:6::Using variable 'err_b' before assignment -used-before-assignment:56:13::Using variable 'err_d' before assignment +pointless-statement:56:0::Statement seems to have no effect +used-before-assignment:74:7::Using variable 'err_a' before assignment +used-before-assignment:75:6::Using variable 'err_b' before assignment +used-before-assignment:77:13::Using variable 'err_d' before assignment