diff --git a/ChangeLog b/ChangeLog index 2dc3aba713e..eb5535b1d36 100644 --- a/ChangeLog +++ b/ChangeLog @@ -30,6 +30,11 @@ Release date: TBA * Fix `pre-commit` config that could lead to undetected duplicate lines of code +* Fix superfluous-parens false-positive for the walrus operator + + Close #3383 + + What's New in Pylint 2.5.3? =========================== diff --git a/pylint/checkers/format.py b/pylint/checkers/format.py index 130ddd0c5fb..ecf14dfb6d6 100644 --- a/pylint/checkers/format.py +++ b/pylint/checkers/format.py @@ -371,11 +371,18 @@ def _check_keyword_parentheses(self, tokens: List[TokenInfo], start: int) -> Non if tokens[start + 1].string != "(": return found_and_or = False + contains_walrus_operator = False + walrus_operator_depth = 0 depth = 0 keyword_token = str(tokens[start].string) line_num = tokens[start].start[0] for i in range(start, len(tokens) - 1): token = tokens[i] + + if token.string == ":=": + contains_walrus_operator = True + walrus_operator_depth = depth + # If we hit a newline, then assume any parens were for continuation. if token.type == tokenize.NL: return @@ -390,6 +397,11 @@ def _check_keyword_parentheses(self, tokens: List[TokenInfo], start: int) -> Non tokens[i + 1].type in (tokenize.NEWLINE, tokenize.ENDMARKER, tokenize.COMMENT)): # The empty tuple () is always accepted. + if contains_walrus_operator and walrus_operator_depth - 1 == depth: + # Reset variable for possible following expressions + contains_walrus_operator = False + walrus_operator_depth = None + continue if i == start + 2: return if keyword_token == "not": diff --git a/tests/checkers/unittest_format.py b/tests/checkers/unittest_format.py index 7c07ac9ed71..edd4efcd547 100644 --- a/tests/checkers/unittest_format.py +++ b/tests/checkers/unittest_format.py @@ -184,6 +184,27 @@ def testCheckKeywordParensHandlesUnnecessaryParens(self): with self.assertAddsMessages(msg): self.checker._check_keyword_parentheses(_tokenize_str(code), offset) + def testNoSuperfluousParensWalrusOperatorIf(self): + """Parenthesis change the meaning of assignment in the walrus operator + and so are not superfluous:""" + code = "[odd for i in range(99) if (odd := is_odd(i))]" + offset = 0 + with self.assertNoMessages(): + self.checker._check_keyword_parentheses(_tokenize_str(code), offset) + + def testPositiveSuperfluousParensWalrusOperatorIf(self): + """Test positive superfluous parens with the walrus operator""" + code = "[odd for i in range(99) if ((odd := is_odd(i)))]" + msg = Message("superfluous-parens", line=1, args="if") + with self.assertAddsMessages(msg): + self.checker._check_keyword_parentheses(_tokenize_str(code), 0) + + def testSuperfluousParensWalrusOperatorNot(self): + """Test superfluous-parens with the not operator""" + code = "not (foo := 5)" + with self.assertNoMessages(): + self.checker._check_keyword_parentheses(_tokenize_str(code), 0) + def testCheckIfArgsAreNotUnicode(self): cases = [("if (foo):", 0), ("assert (1 == 1)", 0)]