diff options
author | Claudiu Popa <pcmanticore@gmail.com> | 2020-03-10 10:33:35 +0100 |
---|---|---|
committer | Claudiu Popa <pcmanticore@gmail.com> | 2020-03-10 10:33:35 +0100 |
commit | 77e205dc64ead82e38bb901e08117ef6f6162c5c (patch) | |
tree | 8fbd409badeee2ed517ba368ee521a790c0f28fd | |
parent | 00bcf7b817ed459ee617b8af8843aca18d72fe04 (diff) | |
download | astroid-git-77e205dc64ead82e38bb901e08117ef6f6162c5c.tar.gz |
Add a new EvaluatedObject container
This container is used to store values that have already been evaluated.
For instance, 79d5a3a783cf0b5a729e4e467508e955a0cca55f 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.
-rw-r--r-- | astroid/as_string.py | 3 | ||||
-rw-r--r-- | astroid/brain/brain_builtin_inference.py | 11 | ||||
-rw-r--r-- | astroid/node_classes.py | 34 | ||||
-rw-r--r-- | astroid/nodes.py | 1 | ||||
-rw-r--r-- | tests/unittest_inference.py | 23 |
5 files changed, 65 insertions, 7 deletions
diff --git a/astroid/as_string.py b/astroid/as_string.py index 84038ba3..da677a79 100644 --- a/astroid/as_string.py +++ b/astroid/as_string.py @@ -608,6 +608,9 @@ class AsStringVisitor: def visit_property(self, node): return node.function.accept(self) + def visit_evaluatedobject(self, node): + return node.original.accept(self) + def _import_string(names): """return a list of (name, asname) formatted as a string""" diff --git a/astroid/brain/brain_builtin_inference.py b/astroid/brain/brain_builtin_inference.py index b8c2b0ea..7e18e820 100644 --- a/astroid/brain/brain_builtin_inference.py +++ b/astroid/brain/brain_builtin_inference.py @@ -177,9 +177,14 @@ def _container_generic_transform(arg, context, klass, iterables, build_elts): elts = [elt.value for elt in arg.elts] else: # TODO: Does not handle deduplication for sets. - elts = filter( - None, map(partial(helpers.safe_infer, context=context), arg.elts) - ) + elts = [] + for element in arg.elts: + inferred = helpers.safe_infer(element, context=context) + if inferred: + evaluated_object = nodes.EvaluatedObject( + original=element, value=inferred + ) + elts.append(evaluated_object) elif isinstance(arg, nodes.Dict): # Dicts need to have consts as strings already. if not all(isinstance(elt[0], nodes.Const) for elt in arg.items): diff --git a/astroid/node_classes.py b/astroid/node_classes.py index 2e03f4ea..f16b61bd 100644 --- a/astroid/node_classes.py +++ b/astroid/node_classes.py @@ -4726,6 +4726,40 @@ class Unknown(mixins.AssignTypeMixin, NodeNG): yield util.Uninferable +class EvaluatedObject(NodeNG): + """Contains an object that has already been inferred + + This class is useful to pre-evaluate a particular node, + with the resulting class acting as the non-evaluated node. + """ + + name = "EvaluatedObject" + _astroid_fields = ("original",) + _other_fields = ("value",) + + original = None + """The original node that has already been evaluated + + :type: NodeNG + """ + + value = None + """The inferred value + + :type: Union[Uninferable, NodeNG] + """ + + def __init__(self, original, value): + self.original = original + self.value = value + self.lineno = self.original.lineno + self.parent = self.original.parent + self.col_offset = self.original.col_offset + + def infer(self, context=None, **kwargs): + yield self.value + + # constants ############################################################## CONST_CLS = { diff --git a/astroid/nodes.py b/astroid/nodes.py index bf6911ac..82f425ca 100644 --- a/astroid/nodes.py +++ b/astroid/nodes.py @@ -86,6 +86,7 @@ from astroid.node_classes import ( # Node not present in the builtin ast module. DictUnpack, Unknown, + EvaluatedObject, ) from astroid.scoped_nodes import ( Module, diff --git a/tests/unittest_inference.py b/tests/unittest_inference.py index dd833e29..dfae1b8f 100644 --- a/tests/unittest_inference.py +++ b/tests/unittest_inference.py @@ -5171,10 +5171,25 @@ def test_builtin_inference_list_of_exceptions(): inferred = next(node.infer()) assert isinstance(inferred, nodes.Tuple) assert len(inferred.elts) == 2 - assert isinstance(inferred.elts[0], nodes.ClassDef) - assert inferred.elts[0].name == "ValueError" - assert isinstance(inferred.elts[1], nodes.ClassDef) - assert inferred.elts[1].name == "TypeError" + assert isinstance(inferred.elts[0], nodes.EvaluatedObject) + assert isinstance(inferred.elts[0].value, nodes.ClassDef) + assert inferred.elts[0].value.name == "ValueError" + assert isinstance(inferred.elts[1], nodes.EvaluatedObject) + assert isinstance(inferred.elts[1].value, nodes.ClassDef) + assert inferred.elts[1].value.name == "TypeError" + + # Test that inference of evaluated objects returns what is expected + first_elem = next(inferred.elts[0].infer()) + assert isinstance(first_elem, nodes.ClassDef) + assert first_elem.name == "ValueError" + + second_elem = next(inferred.elts[1].infer()) + assert isinstance(second_elem, nodes.ClassDef) + assert second_elem.name == "TypeError" + + # Test that as_string() also works + as_string = inferred.as_string() + assert as_string.strip() == "(ValueError, TypeError)" @test_utils.require_version(minver="3.6") |