Skip to content

Commit

Permalink
Avoid reporting unary/binary op type errors for ambiguous inference (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
github-actions[bot] authored Jul 20, 2024
1 parent 30ea720 commit 5d7e9f3
Show file tree
Hide file tree
Showing 3 changed files with 64 additions and 27 deletions.
4 changes: 4 additions & 0 deletions ChangeLog
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ What's New in astroid 3.2.4?
============================
Release date: TBA

* Avoid reporting unary/binary op type errors when inference is ambiguous.

Closes #2467



What's New in astroid 3.2.3?
Expand Down
60 changes: 33 additions & 27 deletions astroid/nodes/node_classes.py
Original file line number Diff line number Diff line change
Expand Up @@ -1381,24 +1381,26 @@ def postinit(self, target: Name | Attribute | Subscript, value: NodeNG) -> None:
See astroid/protocols.py for actual implementation.
"""

def type_errors(self, context: InferenceContext | None = None):
def type_errors(
self, context: InferenceContext | None = None
) -> list[util.BadBinaryOperationMessage]:
"""Get a list of type errors which can occur during inference.
Each TypeError is represented by a :class:`BadBinaryOperationMessage` ,
which holds the original exception.
:returns: The list of possible type errors.
:rtype: list(BadBinaryOperationMessage)
If any inferred result is uninferable, an empty list is returned.
"""
bad = []
try:
results = self._infer_augassign(context=context)
return [
result
for result in results
if isinstance(result, util.BadBinaryOperationMessage)
]
for result in self._infer_augassign(context=context):
if result is util.Uninferable:
raise InferenceError
if isinstance(result, util.BadBinaryOperationMessage):
bad.append(result)
except InferenceError:
return []
return bad

def get_children(self):
yield self.target
Expand Down Expand Up @@ -1497,24 +1499,26 @@ def postinit(self, left: NodeNG, right: NodeNG) -> None:
self.left = left
self.right = right

def type_errors(self, context: InferenceContext | None = None):
def type_errors(
self, context: InferenceContext | None = None
) -> list[util.BadBinaryOperationMessage]:
"""Get a list of type errors which can occur during inference.
Each TypeError is represented by a :class:`BadBinaryOperationMessage`,
which holds the original exception.
:returns: The list of possible type errors.
:rtype: list(BadBinaryOperationMessage)
If any inferred result is uninferable, an empty list is returned.
"""
bad = []
try:
results = self._infer_binop(context=context)
return [
result
for result in results
if isinstance(result, util.BadBinaryOperationMessage)
]
for result in self._infer_binop(context=context):
if result is util.Uninferable:
raise InferenceError
if isinstance(result, util.BadBinaryOperationMessage):
bad.append(result)
except InferenceError:
return []
return bad

def get_children(self):
yield self.left
Expand Down Expand Up @@ -4262,24 +4266,26 @@ def __init__(
def postinit(self, operand: NodeNG) -> None:
self.operand = operand

def type_errors(self, context: InferenceContext | None = None):
def type_errors(
self, context: InferenceContext | None = None
) -> list[util.BadUnaryOperationMessage]:
"""Get a list of type errors which can occur during inference.
Each TypeError is represented by a :class:`BadUnaryOperationMessage`,
which holds the original exception.
:returns: The list of possible type errors.
:rtype: list(BadUnaryOperationMessage)
If any inferred result is uninferable, an empty list is returned.
"""
bad = []
try:
results = self._infer_unaryop(context=context)
return [
result
for result in results
if isinstance(result, util.BadUnaryOperationMessage)
]
for result in self._infer_unaryop(context=context):
if result is util.Uninferable:
raise InferenceError
if isinstance(result, util.BadUnaryOperationMessage):
bad.append(result)
except InferenceError:
return []
return bad

def get_children(self):
yield self.operand
Expand Down
27 changes: 27 additions & 0 deletions tests/test_inference.py
Original file line number Diff line number Diff line change
Expand Up @@ -2743,6 +2743,15 @@ def __radd__(self, other):
error = errors[0]
self.assertEqual(str(error), expected_value)

def test_binary_type_errors_partially_uninferable(self) -> None:
def patched_infer_binop(context):
return iter([util.BadBinaryOperationMessage(None, None, None), Uninferable])

binary_op_node = extract_node("0 + 0")
binary_op_node._infer_binop = patched_infer_binop
errors = binary_op_node.type_errors()
self.assertEqual(errors, [])

def test_unary_type_errors(self) -> None:
ast_nodes = extract_node(
"""
Expand Down Expand Up @@ -2810,6 +2819,15 @@ def test_unary_type_errors_for_non_instance_objects(self) -> None:
self.assertEqual(len(errors), 1)
self.assertEqual(str(errors[0]), "bad operand type for unary ~: slice")

def test_unary_type_errors_partially_uninferable(self) -> None:
def patched_infer_unary_op(context):
return iter([util.BadUnaryOperationMessage(None, None, "msg"), Uninferable])

unary_op_node = extract_node("~0")
unary_op_node._infer_unaryop = patched_infer_unary_op
errors = unary_op_node.type_errors()
self.assertEqual(errors, [])

def test_bool_value_recursive(self) -> None:
pairs = [
("{}", False),
Expand Down Expand Up @@ -3528,6 +3546,15 @@ def __radd__(self, other): return NotImplemented
self.assertIsInstance(inferred, Instance)
self.assertEqual(inferred.name, "B")

def test_augop_type_errors_partially_uninferable(self) -> None:
def patched_infer_augassign(context) -> None:
return iter([util.BadBinaryOperationMessage(None, None, None), Uninferable])

aug_op_node = extract_node("__name__ += 'test'")
aug_op_node._infer_augassign = patched_infer_augassign
errors = aug_op_node.type_errors()
self.assertEqual(errors, [])

def test_string_interpolation(self):
ast_nodes = extract_node(
"""
Expand Down

0 comments on commit 5d7e9f3

Please sign in to comment.