Repository: serge-sans-paille/gast

Issue:
Ellipsis symbol missing
Hi, it seems that the 0.3 release is missing some symbols like `Ellipsis`. Is this intentional?

Discussion:

Patch:
diff --git a/README.rst b/README.rst
index ab73bce..ea1cd7d 100644
--- a/README.rst
+++ b/README.rst
@@ -60,14 +60,14 @@ The AST used by GAST is the same as the one used in Python3.8, with the
 notable exception of ``ast.arg`` being replaced by an ``ast.Name`` with an
 ``ast.Param`` context.
 
-For minor version before 3.8, please note that ``Num``, ``Str``, ``Bytes`` and
-``NamedConstant`` are represented as ``Constant``.
+For minor version before 3.8, please note that ``Ellipsis``, ``Num``, ``Str``,
+``Bytes`` and ``NamedConstant`` are represented as ``Constant``.
 
 Python2
 *******
 
 To cope with Python3 features, several nodes from the Python2 AST are extended
-with some new attributes/children.
+with some new attributes/children, or represented by different nodes
 
 - ``ModuleDef`` nodes have a ``type_ignores`` attribute.
 
@@ -96,7 +96,11 @@ with some new attributes/children.
 - ``comprehension`` nodes have an ``async`` attribute (that is always set
   to 0).
 
-- ``Num`` and ``Str`` nodes are represented as ``Constant``.
+- ``Ellipsis``, ``Num`` and ``Str`` nodes are represented as ``Constant``.
+
+- ``Subscript`` which don't have any ``Slice`` node as ``slice`` attribute (and
+  ``Ellipsis`` are not ``Slice`` nodes) no longer hold an ``ExtSlice`` but an
+  ``Index(Tuple(...))`` instead.
 
 
 Pit Falls
diff --git a/gast/ast2.py b/gast/ast2.py
index f57d147..4449612 100644
--- a/gast/ast2.py
+++ b/gast/ast2.py
@@ -140,6 +140,44 @@ class Ast2ToGAst(AstToGAst):
         new_node.end_lineno = new_node.end_col_offset = None
         return new_node
 
+    def visit_Subscript(self, node):
+        new_slice = self._visit(node.slice)
+        if isinstance(node.slice, ast.Ellipsis):
+            new_slice = gast.Index(new_slice)
+
+        new_node = gast.Subscript(
+            self._visit(node.value),
+            new_slice,
+            self._visit(node.ctx),
+        )
+        gast.copy_location(new_node, node)
+        new_node.end_lineno = new_node.end_col_offset = None
+        return new_node
+
+    def visit_Ellipsis(self, node):
+        new_node = gast.Constant(
+            Ellipsis,
+            None,
+        )
+        gast.copy_location(new_node, node)
+        new_node.end_lineno = new_node.end_col_offset = None
+        return new_node
+
+    def visit_ExtSlice(self, node):
+        has_ellipsis = any(isinstance(d, ast.Ellipsis) for d in node.dims)
+        has_slice = any(isinstance(d, ast.Slice) for d in node.dims)
+        new_dims = self._visit(node.dims)
+        if has_ellipsis and not has_slice:
+            new_dims = [nd.value if isinstance(nd, gast.Index) else nd
+                        for nd in new_dims]
+            new_node = gast.Index(gast.Tuple(new_dims,
+                                             gast.Load()))
+        else:
+            new_node = gast.ExtSlice(new_dims)
+        gast.copy_location(new_node, node)
+        new_node.end_lineno = new_node.end_col_offset = None
+        return new_node
+
     def visit_Str(self, node):
         new_node = gast.Constant(
             node.s,
@@ -309,11 +347,30 @@ class GAstToAst2(GAstToAst):
     def visit_Constant(self, node):
         if isinstance(node.value, (bool, int, long, float, complex)):
             new_node = ast.Num(node.value)
+        elif node.value is Ellipsis:
+            new_node = ast.Ellipsis()
         else:
             new_node = ast.Str(node.value)
         ast.copy_location(new_node, node)
         return new_node
 
+    def visit_Index(self, node):
+        new_value = self._visit(node.value)
+        if isinstance(new_value, ast.Ellipsis):
+            new_node = new_value
+        elif isinstance(new_value, ast.Tuple):
+            if any(isinstance(elt, ast.Ellipsis) for elt in new_value.elts):
+                new_elts = [elt if isinstance(elt, (ast.Ellipsis, ast.Slice))
+                            else ast.Index(elt)
+                            for elt in new_value.elts]
+                new_node = ast.ExtSlice(new_elts)
+            else:
+                new_node = ast.Index(new_value)
+        else:
+            new_node = ast.Index(new_value)
+        ast.copy_location(new_node, node)
+        return new_node
+
     def visit_Call(self, node):
         if node.args and isinstance(node.args[-1], gast.Starred):
             args = node.args[:-1]
diff --git a/gast/ast3.py b/gast/ast3.py
index b895edd..74d8d23 100644
--- a/gast/ast3.py
+++ b/gast/ast3.py
@@ -22,6 +22,15 @@ class Ast3ToGAst(AstToGAst):
             gast.copy_location(new_node, node)
             return new_node
 
+        def visit_Ellipsis(self, node):
+            new_node = gast.Constant(
+                Ellipsis,
+                None,
+            )
+            gast.copy_location(new_node, node)
+            new_node.end_lineno = new_node.end_col_offset = None
+            return new_node
+
         def visit_Str(self, node):
             new_node = gast.Constant(
                 node.s,
@@ -209,6 +218,8 @@ class GAstToAst3(GAstToAst):
         def visit_Constant(self, node):
             if node.value is None:
                 new_node = ast.NameConstant(node.value)
+            elif node.value is Ellipsis:
+                new_node = ast.Ellipsis()
             elif isinstance(node.value, bool):
                 new_node = ast.NameConstant(node.value)
             elif isinstance(node.value, (int, float, complex)):


Test patch:
diff --git a/tests/test_compat.py b/tests/test_compat.py
index 832f8d5..d463268 100644
--- a/tests/test_compat.py
+++ b/tests/test_compat.py
@@ -229,6 +229,81 @@ class CompatTestCase(unittest.TestCase):
                 "type_ignores=[])")
         self.assertEqual(gast.dump(tree), norm)
 
+    def test_Index(self):
+        code = 'def foo(a): a[1]'
+        tree = gast.parse(code)
+        compile(gast.gast_to_ast(tree), '<test>', 'exec')
+        norm = ("Module(body=[FunctionDef(name='foo', args=arguments(args=["
+                "Name(id='a', ctx=Param(), annotation=None, type_comment=None)"
+                "], posonlyargs=[], vararg=None, kwonlyargs=[], kw_defaults=[]"
+                ", kwarg=None, defaults=[]), body=[Expr(value=Subscript(value="
+                "Name(id='a', ctx=Load(), annotation=None, type_comment=None)"
+                ", slice=Index(value=Constant(value=1, kind=None)), ctx=Load()"
+                "))], decorator_list=[], returns=None, type_comment=None)]"
+                ", type_ignores=[])")
+        self.assertEqual(gast.dump(tree), norm)
+
+    def test_ExtSlice(self):
+        code = 'def foo(a): a[:,:]'
+        tree = gast.parse(code)
+        compile(gast.gast_to_ast(tree), '<test>', 'exec')
+        norm = ("Module(body=[FunctionDef(name='foo', args=arguments(args=["
+                "Name(id='a', ctx=Param(), annotation=None, type_comment=None)"
+                "], posonlyargs=[], vararg=None, kwonlyargs=[], kw_defaults=[]"
+                ", kwarg=None, defaults=[]), body=[Expr(value=Subscript(value="
+                "Name(id='a', ctx=Load(), annotation=None, type_comment=None)"
+                ", slice=ExtSlice(dims=[Slice(lower=None, upper=None, step="
+                "None), Slice(lower=None, upper=None, step=None)]), ctx=Load()"
+                "))], decorator_list=[], returns=None, type_comment=None)], "
+                "type_ignores=[])")
+        self.assertEqual(gast.dump(tree), norm)
+
+    def test_ExtSlices(self):
+        code = 'def foo(a): a[1,:]'
+        tree = gast.parse(code)
+        compile(gast.gast_to_ast(tree), '<test>', 'exec')
+        norm = ("Module(body=[FunctionDef(name='foo', args=arguments(args=["
+                "Name(id='a', ctx=Param(), annotation=None, type_comment=None)"
+                "], posonlyargs=[], vararg=None, kwonlyargs=[], kw_defaults=[]"
+                ", kwarg=None, defaults=[]), body=[Expr(value=Subscript(value="
+                "Name(id='a', ctx=Load(), annotation=None, type_comment=None)"
+                ", slice=ExtSlice(dims=[Index(value=Constant(value=1, kind="
+                "None)), Slice(lower=None, upper=None, step=None)]), ctx=Load"
+                "()))], decorator_list=[], returns=None, type_comment=None)]"
+                ", type_ignores=[])")
+        self.assertEqual(gast.dump(tree), norm)
+
+    def test_Ellipsis(self):
+        self.maxDiff = None
+        code = 'def foo(a): a[...]'
+        tree = gast.parse(code)
+        compile(gast.gast_to_ast(tree), '<test>', 'exec')
+        norm = ("Module(body=[FunctionDef(name='foo', args=arguments(args=["
+                "Name(id='a', ctx=Param(), annotation=None, type_comment=None)"
+                "], posonlyargs=[], vararg=None, kwonlyargs=[], kw_defaults=[]"
+                ", kwarg=None, defaults=[]), body=[Expr(value=Subscript(value="
+                "Name(id='a', ctx=Load(), annotation=None, type_comment=None)"
+                ", slice=Index(value=Constant(value=Ellipsis, kind=None)), ctx"
+                "=Load()))], decorator_list=[], returns=None, type_comment="
+                "None)], type_ignores=[])")
+        self.assertEqual(gast.dump(tree), norm)
+
+    def test_ExtSliceEllipsis(self):
+        self.maxDiff = None
+        code = 'def foo(a): a[1, ...]'
+        tree = gast.parse(code)
+        compile(gast.gast_to_ast(tree), '<test>', 'exec')
+        norm = ("Module(body=[FunctionDef(name='foo', args=arguments(args=["
+                "Name(id='a', ctx=Param(), annotation=None, type_comment=None)"
+                "], posonlyargs=[], vararg=None, kwonlyargs=[], kw_defaults=[]"
+                ", kwarg=None, defaults=[]), body=[Expr(value=Subscript(value="
+                "Name(id='a', ctx=Load(), annotation=None, type_comment=None)"
+                ", slice=Index(value=Tuple(elts=[Constant(value=1, kind=None)"
+                ", Constant(value=Ellipsis, kind=None)], ctx=Load())), ctx="
+                "Load()))], decorator_list=[], returns=None, type_comment="
+                "None)], type_ignores=[])")
+        self.assertEqual(gast.dump(tree), norm)
+
 
 if __name__ == '__main__':
     unittest.main()