Skip to content

Commit 77e205d

Browse files
committed
Add a new EvaluatedObject container
This container is used to store values that have already been evaluated. For instance, 79d5a3a added support for inferring `tuple()` call arguments, but as a result, the `elts` of a `Tuple` can be objects not *references*. As a result, `Tuple.elts` can contain class objects rather than references (names) to class object. The `EvaluatedObject` helps with that, as we still have to call `.infer()` (albeit a no-op) to grab the inferred value of an element.
1 parent 00bcf7b commit 77e205d

File tree

5 files changed

+65
-7
lines changed

5 files changed

+65
-7
lines changed

astroid/as_string.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -608,6 +608,9 @@ def visit_uninferable(self, node):
608608
def visit_property(self, node):
609609
return node.function.accept(self)
610610

611+
def visit_evaluatedobject(self, node):
612+
return node.original.accept(self)
613+
611614

612615
def _import_string(names):
613616
"""return a list of (name, asname) formatted as a string"""

astroid/brain/brain_builtin_inference.py

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -177,9 +177,14 @@ def _container_generic_transform(arg, context, klass, iterables, build_elts):
177177
elts = [elt.value for elt in arg.elts]
178178
else:
179179
# TODO: Does not handle deduplication for sets.
180-
elts = filter(
181-
None, map(partial(helpers.safe_infer, context=context), arg.elts)
182-
)
180+
elts = []
181+
for element in arg.elts:
182+
inferred = helpers.safe_infer(element, context=context)
183+
if inferred:
184+
evaluated_object = nodes.EvaluatedObject(
185+
original=element, value=inferred
186+
)
187+
elts.append(evaluated_object)
183188
elif isinstance(arg, nodes.Dict):
184189
# Dicts need to have consts as strings already.
185190
if not all(isinstance(elt[0], nodes.Const) for elt in arg.items):

astroid/node_classes.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4726,6 +4726,40 @@ def infer(self, context=None, **kwargs):
47264726
yield util.Uninferable
47274727

47284728

4729+
class EvaluatedObject(NodeNG):
4730+
"""Contains an object that has already been inferred
4731+
4732+
This class is useful to pre-evaluate a particular node,
4733+
with the resulting class acting as the non-evaluated node.
4734+
"""
4735+
4736+
name = "EvaluatedObject"
4737+
_astroid_fields = ("original",)
4738+
_other_fields = ("value",)
4739+
4740+
original = None
4741+
"""The original node that has already been evaluated
4742+
4743+
:type: NodeNG
4744+
"""
4745+
4746+
value = None
4747+
"""The inferred value
4748+
4749+
:type: Union[Uninferable, NodeNG]
4750+
"""
4751+
4752+
def __init__(self, original, value):
4753+
self.original = original
4754+
self.value = value
4755+
self.lineno = self.original.lineno
4756+
self.parent = self.original.parent
4757+
self.col_offset = self.original.col_offset
4758+
4759+
def infer(self, context=None, **kwargs):
4760+
yield self.value
4761+
4762+
47294763
# constants ##############################################################
47304764

47314765
CONST_CLS = {

astroid/nodes.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@
8686
# Node not present in the builtin ast module.
8787
DictUnpack,
8888
Unknown,
89+
EvaluatedObject,
8990
)
9091
from astroid.scoped_nodes import (
9192
Module,

tests/unittest_inference.py

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5171,10 +5171,25 @@ def test_builtin_inference_list_of_exceptions():
51715171
inferred = next(node.infer())
51725172
assert isinstance(inferred, nodes.Tuple)
51735173
assert len(inferred.elts) == 2
5174-
assert isinstance(inferred.elts[0], nodes.ClassDef)
5175-
assert inferred.elts[0].name == "ValueError"
5176-
assert isinstance(inferred.elts[1], nodes.ClassDef)
5177-
assert inferred.elts[1].name == "TypeError"
5174+
assert isinstance(inferred.elts[0], nodes.EvaluatedObject)
5175+
assert isinstance(inferred.elts[0].value, nodes.ClassDef)
5176+
assert inferred.elts[0].value.name == "ValueError"
5177+
assert isinstance(inferred.elts[1], nodes.EvaluatedObject)
5178+
assert isinstance(inferred.elts[1].value, nodes.ClassDef)
5179+
assert inferred.elts[1].value.name == "TypeError"
5180+
5181+
# Test that inference of evaluated objects returns what is expected
5182+
first_elem = next(inferred.elts[0].infer())
5183+
assert isinstance(first_elem, nodes.ClassDef)
5184+
assert first_elem.name == "ValueError"
5185+
5186+
second_elem = next(inferred.elts[1].infer())
5187+
assert isinstance(second_elem, nodes.ClassDef)
5188+
assert second_elem.name == "TypeError"
5189+
5190+
# Test that as_string() also works
5191+
as_string = inferred.as_string()
5192+
assert as_string.strip() == "(ValueError, TypeError)"
51785193

51795194

51805195
@test_utils.require_version(minver="3.6")

0 commit comments

Comments
 (0)