Skip to content

Commit

Permalink
only error if both keys and values are the same
Browse files Browse the repository at this point in the history
  • Loading branch information
yangdanny97 committed Oct 30, 2024
1 parent 2cfe2fc commit 40bb666
Show file tree
Hide file tree
Showing 4 changed files with 46 additions and 85 deletions.
2 changes: 1 addition & 1 deletion README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,7 @@ second usage. Save the result to a list if the result is needed multiple times.

**B040**: Caught exception with call to ``add_note`` not used. Did you forget to ``raise`` it?

**B041**: Repeated key in dictionary literal. The last value will override any previous values.
**B041**: Repeated key-value pair in dictionary literal.

Opinionated warnings
~~~~~~~~~~~~~~~~~~~~
Expand Down
15 changes: 9 additions & 6 deletions bugbear.py
Original file line number Diff line number Diff line change
Expand Up @@ -649,7 +649,7 @@ def visit_Dict(self, node) -> None:
self.generic_visit(node)

def check_for_b041(self, node) -> None:
# Complain if there are duplicate keys in a dictionary literal.
# Complain if there are duplicate key-value pairs in a dictionary literal.
def convert_to_value(item):
if isinstance(item, ast.Constant):
return item.value
Expand All @@ -668,9 +668,13 @@ def convert_to_value(item):
]
for key in duplicate_keys:
key_indices = [i for i, i_key in enumerate(keys) if i_key == key]
for key_index in key_indices:
key_node = node.keys[key_index]
self.errors.append(B041(key_node.lineno, key_node.col_offset))
seen = set()
for index in key_indices:
value = convert_to_value(node.values[index])
if value in seen:
key_node = node.keys[index]
self.errors.append(B041(key_node.lineno, key_node.col_offset))
seen.add(value)

def check_for_b005(self, node) -> None:
if isinstance(node, ast.Import):
Expand Down Expand Up @@ -2368,8 +2372,7 @@ def visit_Lambda(self, node) -> None:

B041 = Error(
message=(
"B041 Repeated key in dictionary literal. The last value will override "
"any previous values."
"B041 Repeated key-value pair in dictionary literal."
)
)

Expand Down
96 changes: 18 additions & 78 deletions tests/b041.py
Original file line number Diff line number Diff line change
@@ -1,84 +1,24 @@
# Simple case
{'yes': 1, 'yes': 2} # error

# Duplicate keys with bytes vs unicode in Python 3
{b'a': 1, u'a': 2} # error
{1: b'a', 1: u'a'} # error

# Duplicate keys with bytes vs unicode in Python 2
{b'a': 1, u'a': 2} # error

# Duplicate values in Python 2
{1: b'a', 1: u'a'} # error

# Multiple duplicate keys
{'yes': 1, 'yes': 2, 'no': 2, 'no': 3} # error

# Duplicate keys in function call
def f(thing):
pass
f({'yes': 1, 'yes': 2}) # error

# Duplicate keys in lambda function
(lambda x: {(0, 1): 1, (0, 1): 2}) # error

# Duplicate keys with tuples
{(0, 1): 1, (0, 1): 2} # error

# Duplicate keys with int and float
{1: 1, 1.0: 2} # error

# Duplicate keys with booleans
{True: 1, True: 2} # error

# Duplicate keys with None
{None: 1, None: 2} # error

# Duplicate keys with variables
a = 1
{a: 1, a: 2} # error

# Duplicate values with variables
a = 1
b = 2
{1: a, 1: b} # error

# Duplicate values with same variable value
a = 1
test = {'yes': 1, 'yes': 1}
test = {'yes': 1, 'yes': 1, 'no': 2, 'no': 2}
test = {'yes': 1, 'yes': 1, 'yes': 1}
test = {1: 1, 1.0: 1}
test = {True: 1, True: 1}
test = {None: 1, None: 1}
test = {a: a, a: a}

# no error if either keys or values are different
test = {'yes': 1, 'yes': 2}
test = {1: 1, 2: 1}
test = {(0, 1): 1, (0, 2): 1}
test = {(0, 1): 1, (0, 1): 2}
b = 1
{1: a, 1: b} # error

# Duplicate keys with same values
{'yes': 1, 'yes': 1} # error

# Non-duplicate keys
{'yes': 1, 'no': 2} # safe

# Non-duplicate keys with tuples having the same first element
{(0, 1): 1, (0, 2): 1} # safe

# Non-duplicate keys in function call
def test_func(thing):
pass
test_func({True: 1, None: 2, False: 1}) # safe

# Non-duplicate keys with bool and None
{True: 1, None: 2, False: 1} # safe

# Non-duplicate keys with different ints
{1: 1, 2: 1} # safe

# Duplicate keys with differently-named variables
test = 'yes'
rest = 'yes'
{test: 1, rest: 2} # safe

# Non-duplicate tuple keys
{(0, 1): 1, (0, 2): 1} # safe

# Duplicate keys with instance attributes
test = {a: a, b: a}
test = {a: a, a: b}
class TestClass:
pass
f = TestClass()
f.a = 1
{f.a: 1, f.a: 1} # safe
test = {f.a: 1, f.a: 1}


18 changes: 18 additions & 0 deletions tests/test_bugbear.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
B037,
B039,
B040,
B041,
B901,
B902,
B903,
Expand Down Expand Up @@ -666,6 +667,23 @@ def test_b040(self) -> None:
)
self.assertEqual(errors, expected)

def test_b041(self) -> None:
filename = Path(__file__).absolute().parent / "b041.py"
bbc = BugBearChecker(filename=str(filename))
errors = list(bbc.run())
expected = self.errors(
B041(2, 18),
B041(3, 18),
B041(3, 37),
B041(4, 18),
B041(4, 28),
B041(5, 14),
B041(6, 17),
B041(7, 17),
B041(8, 14),
)
self.assertEqual(errors, expected)

def test_b908(self):
filename = Path(__file__).absolute().parent / "b908.py"
bbc = BugBearChecker(filename=str(filename))
Expand Down

0 comments on commit 40bb666

Please sign in to comment.