summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--astroid/brain/builtin_inference.py6
-rw-r--r--astroid/objects.py43
-rw-r--r--astroid/tests/unittest_inference.py28
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()