summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorClaudiu Popa <pcmanticore@gmail.com>2020-03-10 10:33:35 +0100
committerClaudiu Popa <pcmanticore@gmail.com>2020-03-10 10:33:35 +0100
commit77e205dc64ead82e38bb901e08117ef6f6162c5c (patch)
tree8fbd409badeee2ed517ba368ee521a790c0f28fd
parent00bcf7b817ed459ee617b8af8843aca18d72fe04 (diff)
downloadastroid-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.py3
-rw-r--r--astroid/brain/brain_builtin_inference.py11
-rw-r--r--astroid/node_classes.py34
-rw-r--r--astroid/nodes.py1
-rw-r--r--tests/unittest_inference.py23
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")