Repository: berkerpeksag/astor

Issue:
code_gen represents an empty set as an empty dictionary
    python -c 'import ast, astor; print(astor.code_gen.to_source(ast.Set([])))'
    {}

Since Python has no literal syntax for the empty set, and we can't use `set()` because the name `set` might be rebound, I think the right representation is `{1}.__class__()`.

Discussion:

Patch:
diff --git a/astor/code_gen.py b/astor/code_gen.py
index ecaf388..157d2cc 100644
--- a/astor/code_gen.py
+++ b/astor/code_gen.py
@@ -663,8 +663,14 @@ class SourceGenerator(ExplicitNodeVisitor):
             self.comma_list(node.elts)
 
     def visit_Set(self, node):
-        with self.delimit('{}'):
-            self.comma_list(node.elts)
+        if node.elts:
+            with self.delimit('{}'):
+                self.comma_list(node.elts)
+        else:
+            # If we tried to use "{}" to represent an empty set, it would be
+            # interpreted as an empty dictionary. We can't use "set()" either
+            # because the name "set" might be rebound.
+            self.write('{1}.__class__()')
 
     def visit_Dict(self, node):
         set_precedence(Precedence.Comma, *node.values)
diff --git a/docs/changelog.rst b/docs/changelog.rst
index aeeeca2..af9256e 100644
--- a/docs/changelog.rst
+++ b/docs/changelog.rst
@@ -42,6 +42,11 @@ Bug fixes
 .. _`Issue 85`: https://github.com/berkerpeksag/astor/issues/85
 .. _`Issue 100`: https://github.com/berkerpeksag/astor/issues/100
 
+* Improved code generation to support empty sets.
+  (Reported and fixed by Kodi Arfer in `Issue 108`_.)
+
+.. _`Issue 108`: https://github.com/berkerpeksag/astor/issues/108
+
 0.6.2 - 2017-11-11
 ------------------
 


Test patch:
diff --git a/tests/test_code_gen.py b/tests/test_code_gen.py
index b8c1c6a..3a8be7b 100644
--- a/tests/test_code_gen.py
+++ b/tests/test_code_gen.py
@@ -23,8 +23,11 @@ import astor
 def canonical(srctxt):
     return textwrap.dedent(srctxt).strip()
 
+def astorexpr(x):
+    return eval(astor.to_source(ast.Expression(body=x)))
+
 def astornum(x):
-    return eval(astor.to_source(ast.Expression(body=ast.Num(n=x))))
+    return astorexpr(ast.Num(n=x))
 
 class Comparisons(object):
 
@@ -93,6 +96,14 @@ class CodegenTestCase(unittest.TestCase, Comparisons):
         source = "from ..aaa import foo, bar as bar2"
         self.assertSrcRoundtrips(source)
 
+    def test_empty_iterable_literals(self):
+        self.assertSrcRoundtrips('()')
+        self.assertSrcRoundtrips('[]')
+        self.assertSrcRoundtrips('{}')
+        # Python has no literal for empty sets, but code_gen should produce an
+        # expression that evaluates to one.
+        self.assertEqual(astorexpr(ast.Set([])), set())
+
     def test_dictionary_literals(self):
         source = "{'a': 1, 'b': 2}"
         self.assertSrcRoundtrips(source)