Skip to content

Commit 5d7e9f3

Browse files
Avoid reporting unary/binary op type errors for ambiguous inference (#2468) (#2471)
1 parent 30ea720 commit 5d7e9f3

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
@@ -13,6 +13,10 @@ What's New in astroid 3.2.4?
1313
============================
1414
Release date: TBA
1515

16+
* Avoid reporting unary/binary op type errors when inference is ambiguous.
17+
18+
Closes #2467
19+
1620

1721

1822
What's New in astroid 3.2.3?

astroid/nodes/node_classes.py

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

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

14031405
def get_children(self):
14041406
yield self.target
@@ -1497,24 +1499,26 @@ def postinit(self, left: NodeNG, right: NodeNG) -> None:
14971499
self.left = left
14981500
self.right = right
14991501

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

15191523
def get_children(self):
15201524
yield self.left
@@ -4262,24 +4266,26 @@ def __init__(
42624266
def postinit(self, operand: NodeNG) -> None:
42634267
self.operand = operand
42644268

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

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

tests/test_inference.py

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

2746+
def test_binary_type_errors_partially_uninferable(self) -> None:
2747+
def patched_infer_binop(context):
2748+
return iter([util.BadBinaryOperationMessage(None, None, None), Uninferable])
2749+
2750+
binary_op_node = extract_node("0 + 0")
2751+
binary_op_node._infer_binop = patched_infer_binop
2752+
errors = binary_op_node.type_errors()
2753+
self.assertEqual(errors, [])
2754+
27462755
def test_unary_type_errors(self) -> None:
27472756
ast_nodes = extract_node(
27482757
"""
@@ -2810,6 +2819,15 @@ def test_unary_type_errors_for_non_instance_objects(self) -> None:
28102819
self.assertEqual(len(errors), 1)
28112820
self.assertEqual(str(errors[0]), "bad operand type for unary ~: slice")
28122821

2822+
def test_unary_type_errors_partially_uninferable(self) -> None:
2823+
def patched_infer_unary_op(context):
2824+
return iter([util.BadUnaryOperationMessage(None, None, "msg"), Uninferable])
2825+
2826+
unary_op_node = extract_node("~0")
2827+
unary_op_node._infer_unaryop = patched_infer_unary_op
2828+
errors = unary_op_node.type_errors()
2829+
self.assertEqual(errors, [])
2830+
28132831
def test_bool_value_recursive(self) -> None:
28142832
pairs = [
28152833
("{}", False),
@@ -3528,6 +3546,15 @@ def __radd__(self, other): return NotImplemented
35283546
self.assertIsInstance(inferred, Instance)
35293547
self.assertEqual(inferred.name, "B")
35303548

3549+
def test_augop_type_errors_partially_uninferable(self) -> None:
3550+
def patched_infer_augassign(context) -> None:
3551+
return iter([util.BadBinaryOperationMessage(None, None, None), Uninferable])
3552+
3553+
aug_op_node = extract_node("__name__ += 'test'")
3554+
aug_op_node._infer_augassign = patched_infer_augassign
3555+
errors = aug_op_node.type_errors()
3556+
self.assertEqual(errors, [])
3557+
35313558
def test_string_interpolation(self):
35323559
ast_nodes = extract_node(
35333560
"""

0 commit comments

Comments
 (0)