Skip to content

Commit 76aa16b

Browse files
authored
Add B032: Check for possible unintentional type annotations instead of assignments. (#350)
1 parent a7c7ac9 commit 76aa16b

File tree

4 files changed

+75
-0
lines changed

4 files changed

+75
-0
lines changed

README.rst

+3
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,8 @@ It is therefore recommended to use a stacklevel of 2 or greater to provide more
184184
**B031**: Using the generator returned from `itertools.groupby()` more than once will do nothing on the
185185
second usage. Save the result to a list if the result is needed multiple times.
186186

187+
**B032**: Possible unintentional type annotation (using ``:``). Did you mean to assign (using ``=``)?
188+
187189
Opinionated warnings
188190
~~~~~~~~~~~~~~~~~~~~
189191

@@ -333,6 +335,7 @@ Future
333335
* B016: Warn when raising f-strings.
334336
* Add B028: Check for an explicit stacklevel keyword argument on the warn method from the warnings module.
335337
* Add B029: Check when trying to use ``except`` with an empty tuple i.e. ``except: ()``.
338+
* Add B032: Check for possible unintentional type annotations instead of assignments.
336339

337340
23.1.20
338341
~~~~~~~~~

bugbear.py

+26
Original file line numberDiff line numberDiff line change
@@ -482,6 +482,10 @@ def visit_JoinedStr(self, node):
482482
self.check_for_b907(node)
483483
self.generic_visit(node)
484484

485+
def visit_AnnAssign(self, node):
486+
self.check_for_b032(node)
487+
self.generic_visit(node)
488+
485489
def check_for_b005(self, node):
486490
if node.func.attr not in B005.methods:
487491
return # method name doesn't match
@@ -1235,6 +1239,21 @@ def check_for_b028(self, node):
12351239
):
12361240
self.errors.append(B028(node.lineno, node.col_offset))
12371241

1242+
def check_for_b032(self, node):
1243+
if (
1244+
node.value is None
1245+
and hasattr(node.target, "value")
1246+
and isinstance(node.target.value, ast.Name)
1247+
and (
1248+
isinstance(node.target, ast.Subscript)
1249+
or (
1250+
isinstance(node.target, ast.Attribute)
1251+
and node.target.value.id != "self"
1252+
)
1253+
)
1254+
):
1255+
self.errors.append(B032(node.lineno, node.col_offset))
1256+
12381257

12391258
def compose_call_path(node):
12401259
if isinstance(node, ast.Attribute):
@@ -1625,6 +1644,13 @@ def visit_Lambda(self, node):
16251644
)
16261645
)
16271646

1647+
B032 = Error(
1648+
message=(
1649+
"B032 Possible unintentional type annotation (using `:`). Did you mean to"
1650+
" assign (using `=`)?"
1651+
)
1652+
)
1653+
16281654
# Warnings disabled by default.
16291655
B901 = Error(
16301656
message=(

tests/b032.py

+29
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
"""
2+
Should emit:
3+
B032 - on lines 9, 10, 12, 13, 16-19
4+
"""
5+
6+
# Flag these
7+
dct = {"a": 1}
8+
9+
dct["b"]: 2
10+
dct.b: 2
11+
12+
dct["b"]: "test"
13+
dct.b: "test"
14+
15+
test = "test"
16+
dct["b"]: test
17+
dct["b"]: test.lower()
18+
dct.b: test
19+
dct.b: test.lower()
20+
21+
# Do not flag below
22+
typed_dct: dict[str, int] = {"a": 1}
23+
typed_dct["b"] = 2
24+
typed_dct.b = 2
25+
26+
27+
class TestClass:
28+
def test_self(self):
29+
self.test: int

tests/test_bugbear.py

+17
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
B029,
4444
B030,
4545
B031,
46+
B032,
4647
B901,
4748
B902,
4849
B903,
@@ -471,6 +472,22 @@ def test_b031(self):
471472
)
472473
self.assertEqual(errors, expected)
473474

475+
def test_b032(self):
476+
filename = Path(__file__).absolute().parent / "b032.py"
477+
bbc = BugBearChecker(filename=str(filename))
478+
errors = list(bbc.run())
479+
expected = self.errors(
480+
B032(9, 0),
481+
B032(10, 0),
482+
B032(12, 0),
483+
B032(13, 0),
484+
B032(16, 0),
485+
B032(17, 0),
486+
B032(18, 0),
487+
B032(19, 0),
488+
)
489+
self.assertEqual(errors, expected)
490+
474491
@unittest.skipIf(sys.version_info < (3, 8), "not implemented for <3.8")
475492
def test_b907(self):
476493
filename = Path(__file__).absolute().parent / "b907.py"

0 commit comments

Comments
 (0)