Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: PyCQA/flake8-bugbear
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: 23.11.26
Choose a base ref
...
head repository: PyCQA/flake8-bugbear
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: 23.11.28
Choose a head ref
  • 2 commits
  • 3 files changed
  • 2 contributors

Commits on Nov 28, 2023

  1. Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    e0760c2 View commit details

Commits on Nov 29, 2023

  1. Copy the full SHA
    eef0ee7 View commit details
Showing with 55 additions and 3 deletions.
  1. +5 −0 README.rst
  2. +34 −3 bugbear.py
  3. +16 −0 tests/b035.py
5 changes: 5 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
@@ -340,6 +340,11 @@ MIT
Change Log
----------

23.11.28
~~~~~~~~

* B035: Fix false positive when named expressions are used (#430)

23.11.26
~~~~~~~~

37 changes: 34 additions & 3 deletions bugbear.py
Original file line number Diff line number Diff line change
@@ -15,7 +15,7 @@
import attr
import pycodestyle

__version__ = "23.11.26"
__version__ = "23.11.28"

LOG = logging.getLogger("flake8.bugbear")
CONTEXTFUL_NODES = (
@@ -962,13 +962,18 @@ def _get_names_from_tuple(self, node: ast.Tuple):
elif isinstance(dim, ast.Tuple):
yield from self._get_names_from_tuple(dim)

def _get_dict_comp_loop_var_names(self, node: ast.DictComp):
def _get_dict_comp_loop_and_named_expr_var_names(self, node: ast.DictComp):
finder = NamedExprFinder()
for gen in node.generators:
if isinstance(gen.target, ast.Name):
yield gen.target.id
elif isinstance(gen.target, ast.Tuple):
yield from self._get_names_from_tuple(gen.target)

finder.visit(gen.ifs)

yield from finder.names.keys()

def check_for_b035(self, node: ast.DictComp):
"""Check that a static key isn't used in a dict comprehension.
@@ -980,7 +985,9 @@ def check_for_b035(self, node: ast.DictComp):
B035(node.key.lineno, node.key.col_offset, vars=(node.key.value,))
)
elif isinstance(node.key, ast.Name):
if node.key.id not in self._get_dict_comp_loop_var_names(node):
if node.key.id not in self._get_dict_comp_loop_and_named_expr_var_names(
node
):
self.errors.append(
B035(node.key.lineno, node.key.col_offset, vars=(node.key.id,))
)
@@ -1539,6 +1546,30 @@ def visit(self, node):
return node


@attr.s
class NamedExprFinder(ast.NodeVisitor):
"""Finds names defined through an ast.NamedExpr.
After `.visit(node)` is called, `found` is a dict with all name nodes inside,
key is name string, value is the node (useful for location purposes).
"""

names: Dict[str, List[ast.Name]] = attr.ib(default=attr.Factory(dict))

def visit_NamedExpr(self, node: ast.NamedExpr):
self.names.setdefault(node.target.id, []).append(node.target)
self.generic_visit(node)

def visit(self, node):
"""Like super-visit but supports iteration over lists."""
if not isinstance(node, list):
return super().visit(node)

for elem in node:
super().visit(elem)
return node


class FuntionDefDefaultsVisitor(ast.NodeVisitor):
def __init__(self, b008_extend_immutable_calls=None):
self.b008_extend_immutable_calls = b008_extend_immutable_calls or set()
16 changes: 16 additions & 0 deletions tests/b035.py
Original file line number Diff line number Diff line change
@@ -33,3 +33,19 @@
# bad - variabe not from generator
v3 = 1
bad_var_not_from_nested_tuple = {v3: k for k, (v1, v2) in {"a": (1, 2)}.items()}

# OK - variable from named expression
var_from_named_expr = {
k: v
for v in {"key": "foo", "data": {}}
if (k := v.get("key")) is not None
}

# nested generators with named expressions
var_from_named_expr_nested = {
k: v
for v in {"keys": [{"key": "foo"}], "data": {}}
if (keys := v.get("keys")) is not None
for item in keys
if (k := item.get("key")) is not None
}