Skip to content

Commit bf31997

Browse files
Avoid reporting unary/binary op type errors for ambiguous inference (#2468)
1 parent 98e626c commit bf31997

File tree

3 files changed

+64
-27
lines changed

3 files changed

+64
-27
lines changed

ChangeLog

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,10 @@ What's New in astroid 3.2.4?
1818
============================
1919
Release date: TBA
2020

21+
* Avoid reporting unary/binary op type errors when inference is ambiguous.
22+
23+
Closes #2467
24+
2125

2226
What's New in astroid 3.2.3?
2327
============================

astroid/nodes/node_classes.py

Lines changed: 33 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1380,24 +1380,26 @@ def postinit(self, target: Name | Attribute | Subscript, value: NodeNG) -> None:
13801380
See astroid/protocols.py for actual implementation.
13811381
"""
13821382

1383-
def type_errors(self, context: InferenceContext | None = None):
1383+
def type_errors(
1384+
self, context: InferenceContext | None = None
1385+
) -> list[util.BadBinaryOperationMessage]:
13841386
"""Get a list of type errors which can occur during inference.
13851387
13861388
Each TypeError is represented by a :class:`BadBinaryOperationMessage` ,
13871389
which holds the original exception.
13881390
1389-
:returns: The list of possible type errors.
1390-
:rtype: list(BadBinaryOperationMessage)
1391+
If any inferred result is uninferable, an empty list is returned.
13911392
"""
1393+
bad = []
13921394
try:
1393-
results = self._infer_augassign(context=context)
1394-
return [
1395-
result
1396-
for result in results
1397-
if isinstance(result, util.BadBinaryOperationMessage)
1398-
]
1395+
for result in self._infer_augassign(context=context):
1396+
if result is util.Uninferable:
1397+
raise InferenceError
1398+
if isinstance(result, util.BadBinaryOperationMessage):
1399+
bad.append(result)
13991400
except InferenceError:
14001401
return []
1402+
return bad
14011403

14021404
def get_children(self):
14031405
yield self.target
@@ -1496,24 +1498,26 @@ def postinit(self, left: NodeNG, right: NodeNG) -> None:
14961498
self.left = left
14971499
self.right = right
14981500

1499-
def type_errors(self, context: InferenceContext | None = None):
1501+
def type_errors(
1502+
self, context: InferenceContext | None = None
1503+
) -> list[util.BadBinaryOperationMessage]:
15001504
"""Get a list of type errors which can occur during inference.
15011505
15021506
Each TypeError is represented by a :class:`BadBinaryOperationMessage`,
15031507
which holds the original exception.
15041508
1505-
:returns: The list of possible type errors.
1506-
:rtype: list(BadBinaryOperationMessage)
1509+
If any inferred result is uninferable, an empty list is returned.
15071510
"""
1511+
bad = []
15081512
try:
1509-
results = self._infer_binop(context=context)
1510-
return [
1511-
result
1512-
for result in results
1513-
if isinstance(result, util.BadBinaryOperationMessage)
1514-
]
1513+
for result in self._infer_binop(context=context):
1514+
if result is util.Uninferable:
1515+
raise InferenceError
1516+
if isinstance(result, util.BadBinaryOperationMessage):
1517+
bad.append(result)
15151518
except InferenceError:
15161519
return []
1520+
return bad
15171521

15181522
def get_children(self):
15191523
yield self.left
@@ -4261,24 +4265,26 @@ def __init__(
42614265
def postinit(self, operand: NodeNG) -> None:
42624266
self.operand = operand
42634267

4264-
def type_errors(self, context: InferenceContext | None = None):
4268+
def type_errors(
4269+
self, context: InferenceContext | None = None
4270+
) -> list[util.BadUnaryOperationMessage]:
42654271
"""Get a list of type errors which can occur during inference.
42664272
42674273
Each TypeError is represented by a :class:`BadUnaryOperationMessage`,
42684274
which holds the original exception.
42694275
4270-
:returns: The list of possible type errors.
4271-
:rtype: list(BadUnaryOperationMessage)
4276+
If any inferred result is uninferable, an empty list is returned.
42724277
"""
4278+
bad = []
42734279
try:
4274-
results = self._infer_unaryop(context=context)
4275-
return [
4276-
result
4277-
for result in results
4278-
if isinstance(result, util.BadUnaryOperationMessage)
4279-
]
4280+
for result in self._infer_unaryop(context=context):
4281+
if result is util.Uninferable:
4282+
raise InferenceError
4283+
if isinstance(result, util.BadUnaryOperationMessage):
4284+
bad.append(result)
42804285
except InferenceError:
42814286
return []
4287+
return bad
42824288

42834289
def get_children(self):
42844290
yield self.operand

tests/test_inference.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2738,6 +2738,15 @@ def __radd__(self, other):
27382738
error = errors[0]
27392739
self.assertEqual(str(error), expected_value)
27402740

2741+
def test_binary_type_errors_partially_uninferable(self) -> None:
2742+
def patched_infer_binop(context):
2743+
return iter([util.BadBinaryOperationMessage(None, None, None), Uninferable])
2744+
2745+
binary_op_node = extract_node("0 + 0")
2746+
binary_op_node._infer_binop = patched_infer_binop
2747+
errors = binary_op_node.type_errors()
2748+
self.assertEqual(errors, [])
2749+
27412750
def test_unary_type_errors(self) -> None:
27422751
ast_nodes = extract_node(
27432752
"""
@@ -2805,6 +2814,15 @@ def test_unary_type_errors_for_non_instance_objects(self) -> None:
28052814
self.assertEqual(len(errors), 1)
28062815
self.assertEqual(str(errors[0]), "bad operand type for unary ~: slice")
28072816

2817+
def test_unary_type_errors_partially_uninferable(self) -> None:
2818+
def patched_infer_unary_op(context):
2819+
return iter([util.BadUnaryOperationMessage(None, None, "msg"), Uninferable])
2820+
2821+
unary_op_node = extract_node("~0")
2822+
unary_op_node._infer_unaryop = patched_infer_unary_op
2823+
errors = unary_op_node.type_errors()
2824+
self.assertEqual(errors, [])
2825+
28082826
def test_bool_value_recursive(self) -> None:
28092827
pairs = [
28102828
("{}", False),
@@ -3523,6 +3541,15 @@ def __radd__(self, other): return NotImplemented
35233541
self.assertIsInstance(inferred, Instance)
35243542
self.assertEqual(inferred.name, "B")
35253543

3544+
def test_augop_type_errors_partially_uninferable(self) -> None:
3545+
def patched_infer_augassign(context) -> None:
3546+
return iter([util.BadBinaryOperationMessage(None, None, None), Uninferable])
3547+
3548+
aug_op_node = extract_node("__name__ += 'test'")
3549+
aug_op_node._infer_augassign = patched_infer_augassign
3550+
errors = aug_op_node.type_errors()
3551+
self.assertEqual(errors, [])
3552+
35263553
def test_string_interpolation(self):
35273554
ast_nodes = extract_node(
35283555
"""

0 commit comments

Comments
 (0)