diff options
-rw-r--r-- | astroid/brain/builtin_inference.py | 6 | ||||
-rw-r--r-- | astroid/objects.py | 43 | ||||
-rw-r--r-- | astroid/tests/unittest_inference.py | 28 |
3 files changed, 74 insertions, 3 deletions
diff --git a/astroid/brain/builtin_inference.py b/astroid/brain/builtin_inference.py index 022a0092..5f06dc32 100644 --- a/astroid/brain/builtin_inference.py +++ b/astroid/brain/builtin_inference.py @@ -452,9 +452,9 @@ def infer_slice(node, context=None): # Make sure we have 3 arguments. args.extend([None] * (3 - len(args))) - slice_node = nodes.Slice(lineno=node.lineno, - col_offset=node.col_offset, - parent=node.parent) + slice_node = objects.Slice(lineno=node.lineno, + col_offset=node.col_offset, + parent=node.parent) slice_node.postinit(*args) return slice_node diff --git a/astroid/objects.py b/astroid/objects.py index 02fecef8..5318f439 100644 --- a/astroid/objects.py +++ b/astroid/objects.py @@ -24,6 +24,23 @@ leads to an inferred FrozenSet: Call(func=Name('frozenset'), args=Tuple(...)) + +This distinction might help for understanding better where to use +an AST node and where to use an inference object: + + * if the AST node can be used on its own, e.g. List, Tuple, etc, + then the AST can be used. + They have a syntactic display and can be written on its own, + e.g. "[]" is a valid list. That's why the inference of their + respective builtins (list, tuple, set etc) returns an AST node + when inferring. + + * if the AST node is part of syntax and can't be used + on its own. This is the case for slices for instance, e.g "[2:3:4]". + In this case, trying to write "2:3:4" on its own will fail. + The respective builtin (slice) returns a custom object when inferring + and not an AST node per se. + """ import six @@ -55,6 +72,32 @@ class FrozenSet(node_classes._BaseContainer): return builtins.getattr('frozenset')[0] +class Slice(node_classes.Slice): + """Custom object for representing slices internally.""" + + @decorators.cachedproperty + def _proxied(self): + builtins = MANAGER.astroid_cache[BUILTINS] + return builtins.getattr('slice')[0] + + def pytype(self): + return '%s.slice' % BUILTINS + + def igetattr(self, attrname, context=None): + if attrname == 'start': + yield self.lower + elif attrname == 'stop': + yield self.upper + elif attrname == 'step': + yield self.step + else: + for value in self.getattr(attrname, context=context): + yield value + + def getattr(self, attrname, context=None): + return self._proxied.getattr(attrname, context) + + class Super(bases.NodeNG): """Proxy class over a super call. diff --git a/astroid/tests/unittest_inference.py b/astroid/tests/unittest_inference.py index 5b7d0166..0cef920b 100644 --- a/astroid/tests/unittest_inference.py +++ b/astroid/tests/unittest_inference.py @@ -3578,6 +3578,34 @@ class SliceTest(unittest.TestCase): for node in ast_nodes: self.assertRaises(InferenceError, next, node.infer()) + def test_slice_attributes(self): + ast_nodes = [ + ('slice(2, 3, 4)', (2, 3, 4)), + ('slice(None, None, 4)', (None, None, 4)), + ('slice(None, 1, None)', (None, 1, None)), + ] + for code, values in ast_nodes: + lower, upper, step = values + node = test_utils.extract_node(code) + inferred = next(node.infer()) + self.assertIsInstance(inferred, objects.Slice) + lower_value = next(inferred.igetattr('start')) + self.assertIsInstance(lower_value, nodes.Const) + self.assertEqual(lower_value.value, lower) + higher_value = next(inferred.igetattr('stop')) + self.assertIsInstance(higher_value, nodes.Const) + self.assertEqual(higher_value.value, upper) + step_value = next(inferred.igetattr('step')) + self.assertIsInstance(step_value, nodes.Const) + self.assertEqual(step_value.value, step) + self.assertEqual(inferred.pytype(), '%s.slice' % BUILTINS) + + def test_slice_type(self): + ast_node = test_utils.extract_node('type(slice(None, None, None))') + inferred = next(ast_node.infer()) + self.assertIsInstance(inferred, nodes.ClassDef) + self.assertEqual(inferred.name, 'slice') + if __name__ == '__main__': unittest.main() |