Skip to content

Commit 06daa92

Browse files
committed
Fix false positive superfluous parens for walrus operator
Close #3383
1 parent ba0c794 commit 06daa92

File tree

4 files changed

+46
-4
lines changed

4 files changed

+46
-4
lines changed

ChangeLog

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,10 @@ Release date: TBA
2626

2727
* Add an faq detailing which messages to disable to avoid duplicates w/ other popular linters
2828

29+
* Fix superfluous-parens false-positive for the walrus operator
30+
31+
Close #3383
32+
2933
* Fix a bug with `ignore-docstrings` ignoring all lines in a module
3034

3135
* Fix `pre-commit` config that could lead to undetected duplicate lines of code

doc/whatsnew/2.6.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,3 +23,5 @@ Other Changes
2323
* The `no-space-check` option has been removed, it's no longer possible to consider empty line like a `trailing-whitespace` by using clever options.
2424

2525
* `mixed-indentation` has been removed, it is no longer useful since TabError is included directly in python3
26+
27+
* Fix superfluous-parens false-positive for the walrus operator

pylint/checkers/format.py

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -52,8 +52,8 @@
5252

5353
import tokenize
5454
from functools import reduce # pylint: disable=redefined-builtin
55-
from typing import List
5655
from tokenize import TokenInfo
56+
from typing import List
5757

5858
from astroid import nodes
5959

@@ -371,25 +371,40 @@ def _check_keyword_parentheses(self, tokens: List[TokenInfo], start: int) -> Non
371371
if tokens[start + 1].string != "(":
372372
return
373373
found_and_or = False
374+
contains_walrus_operator = False
375+
walrus_operator_depth = 0
374376
depth = 0
375377
keyword_token = str(tokens[start].string)
376378
line_num = tokens[start].start[0]
377379
for i in range(start, len(tokens) - 1):
378380
token = tokens[i]
381+
379382
# If we hit a newline, then assume any parens were for continuation.
380383
if token.type == tokenize.NL:
381384
return
385+
# Since the walrus operator doesn't exist below python3.8, the tokenizer
386+
# generates independent tokens
387+
if (
388+
token.string == ":=" # <-- python3.8+ path
389+
or token.string + tokens[i + 1].string == ":="
390+
):
391+
contains_walrus_operator = True
392+
walrus_operator_depth = depth
382393
if token.string == "(":
383394
depth += 1
384395
elif token.string == ")":
385396
depth -= 1
386397
if depth:
387398
continue
388399
# ')' can't happen after if (foo), since it would be a syntax error.
389-
if (tokens[i + 1].string in (":", ")", "]", "}", "in") or
390-
tokens[i + 1].type in
391-
(tokenize.NEWLINE, tokenize.ENDMARKER, tokenize.COMMENT)):
400+
if tokens[i + 1].string in (":", ")", "]", "}", "in") or tokens[
401+
i + 1
402+
].type in (tokenize.NEWLINE, tokenize.ENDMARKER, tokenize.COMMENT):
392403
# The empty tuple () is always accepted.
404+
if contains_walrus_operator and walrus_operator_depth - 1 == depth:
405+
# Reset variable for possible following expressions
406+
contains_walrus_operator = False
407+
continue
393408
if i == start + 2:
394409
return
395410
if keyword_token == "not":

tests/checkers/unittest_format.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,27 @@ def testCheckKeywordParensHandlesUnnecessaryParens(self):
184184
with self.assertAddsMessages(msg):
185185
self.checker._check_keyword_parentheses(_tokenize_str(code), offset)
186186

187+
def testNoSuperfluousParensWalrusOperatorIf(self):
188+
"""Parenthesis change the meaning of assignment in the walrus operator
189+
and so are not superfluous:"""
190+
code = "if (odd := is_odd(i))"
191+
offset = 0
192+
with self.assertNoMessages():
193+
self.checker._check_keyword_parentheses(_tokenize_str(code), offset)
194+
195+
def testPositiveSuperfluousParensWalrusOperatorIf(self):
196+
"""Test positive superfluous parens with the walrus operator"""
197+
code = "if ((odd := is_odd(i))):"
198+
msg = Message("superfluous-parens", line=1, args="if")
199+
with self.assertAddsMessages(msg):
200+
self.checker._check_keyword_parentheses(_tokenize_str(code), 0)
201+
202+
def testNoSuperfluousParensWalrusOperatorNot(self):
203+
"""Test superfluous-parens with the not operator"""
204+
code = "not (foo := 5)"
205+
with self.assertNoMessages():
206+
self.checker._check_keyword_parentheses(_tokenize_str(code), 0)
207+
187208
def testCheckIfArgsAreNotUnicode(self):
188209
cases = [("if (foo):", 0), ("assert (1 == 1)", 0)]
189210

0 commit comments

Comments
 (0)