Skip to content

Commit

Permalink
Fix false positive superfluous parens for walrus operator
Browse files Browse the repository at this point in the history
  • Loading branch information
brycepg committed Jun 16, 2020
1 parent ba0c794 commit e11a1b1
Show file tree
Hide file tree
Showing 3 changed files with 44 additions and 4 deletions.
5 changes: 5 additions & 0 deletions ChangeLog
Expand Up @@ -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?
===========================

Expand Down
22 changes: 18 additions & 4 deletions pylint/checkers/format.py
Expand Up @@ -52,8 +52,8 @@

import tokenize
from functools import reduce # pylint: disable=redefined-builtin
from typing import List
from tokenize import TokenInfo
from typing import List

from astroid import nodes

Expand Down Expand Up @@ -371,25 +371,39 @@ 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]
tokenlen = len(tokens)
for i in range(start, len(tokens) - 1):
token = tokens[i]

# If we hit a newline, then assume any parens were for continuation.
if token.type == tokenize.NL:
return
# Since the walrus operator doesn't exist below python3.8, the tokenizer
# generates independent tokens
if (token.string == ":=" or # <-- python3.8+ path
token.string == ":" + tokens[i+1].string == ":="):
contains_walrus_operator = True
walrus_operator_depth = depth
if token.string == "(":
depth += 1
elif token.string == ")":
depth -= 1
if depth:
continue
# ')' can't happen after if (foo), since it would be a syntax error.
if (tokens[i + 1].string in (":", ")", "]", "}", "in") or
tokens[i + 1].type in
(tokenize.NEWLINE, tokenize.ENDMARKER, tokenize.COMMENT)):
if tokens[i + 1].string in (":", ")", "]", "}", "in") or 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
continue
if i == start + 2:
return
if keyword_token == "not":
Expand Down
21 changes: 21 additions & 0 deletions tests/checkers/unittest_format.py
Expand Up @@ -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 = "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 = "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 testNoSuperfluousParensWalrusOperatorNot(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)]

Expand Down

0 comments on commit e11a1b1

Please sign in to comment.