summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
authorTodd Gamblin <tgamblin@llnl.gov>2021-12-21 15:53:38 -0800
committerGreg Becker <becker33@llnl.gov>2022-01-12 06:14:18 -0800
commit2badd6500e88b56c76f301af86f6c233b296f726 (patch)
tree5e4ed90b06460b6eed16a63fd0f67c95b86f9f2c /lib
parentb324fe5d95ebe67a2103e1603cbe0bf6e01f1cab (diff)
downloadspack-2badd6500e88b56c76f301af86f6c233b296f726.tar.gz
spack-2badd6500e88b56c76f301af86f6c233b296f726.tar.bz2
spack-2badd6500e88b56c76f301af86f6c233b296f726.tar.xz
spack-2badd6500e88b56c76f301af86f6c233b296f726.zip
unparse: Make unparsing consistent for 2.7 and 3.5-3.10
Previously, there were differences in the unparsed code for Python 2.7 and for 3.5-3.10. This makes unparsed code the same across these Python versions by: 1. Ensuring there are no spaces between unary operators and their operands. 2. Ensuring that *args and **kwargs are always the last arguments, regardless of the python version. 3. Always unparsing print as a function. 4. Not putting an extra comma after Python 2 class definitions. Without these changes, the same source can generate different code for different Python versions, depending on subtle AST differences. One place where single source will generate an inconsistent AST is with multi-argument print statements, e.g.: ``` print("foo", "bar", "baz") ``` In Python 2, this prints a tuple; in Python 3, it is the print function with multiple arguments. Use `from __future__ import print_function` to avoid this inconsistency.
Diffstat (limited to 'lib')
-rw-r--r--lib/spack/external/spack_astunparse/__init__.py4
-rw-r--r--lib/spack/external/spack_astunparse/unparser.py80
2 files changed, 71 insertions, 13 deletions
diff --git a/lib/spack/external/spack_astunparse/__init__.py b/lib/spack/external/spack_astunparse/__init__.py
index 0d48b4df7a..33c44b7812 100644
--- a/lib/spack/external/spack_astunparse/__init__.py
+++ b/lib/spack/external/spack_astunparse/__init__.py
@@ -7,7 +7,7 @@ from .unparser import Unparser
__version__ = '1.6.3'
-def unparse(tree):
+def unparse(tree, py_ver_consistent=False):
v = cStringIO()
- Unparser(tree, file=v)
+ Unparser(tree, file=v, py_ver_consistent=py_ver_consistent)
return v.getvalue()
diff --git a/lib/spack/external/spack_astunparse/unparser.py b/lib/spack/external/spack_astunparse/unparser.py
index 0ef6fd8bcb..854af9a534 100644
--- a/lib/spack/external/spack_astunparse/unparser.py
+++ b/lib/spack/external/spack_astunparse/unparser.py
@@ -29,12 +29,39 @@ class Unparser:
output source code for the abstract syntax; original formatting
is disregarded. """
- def __init__(self, tree, file = sys.stdout):
+ def __init__(self, tree, file = sys.stdout, py_ver_consistent=False):
"""Unparser(tree, file=sys.stdout) -> None.
- Print the source for tree to file."""
+ Print the source for tree to file.
+
+ Arguments:
+ py_ver_consistent (bool): if True, generate unparsed code that is
+ consistent between Python 2.7 and 3.5-3.10.
+
+ Consistency is achieved by:
+ 1. Ensuring there are no spaces between unary operators and
+ their operands.
+ 2. Ensuring that *args and **kwargs are always the last arguments,
+ regardless of the python version.
+ 3. Always unparsing print as a function.
+ 4. Not putting an extra comma after Python 2 class definitions.
+
+ Without these changes, the same source can generate different code for different
+ Python versions, depending on subtle AST differences.
+
+ One place where single source will generate an inconsistent AST is with
+ multi-argument print statements, e.g.::
+
+ print("foo", "bar", "baz")
+
+ In Python 2, this prints a tuple; in Python 3, it is the print function with
+ multiple arguments. Use ``from __future__ import print_function`` to avoid
+ this inconsistency.
+
+ """
self.f = file
self.future_imports = []
self._indent = 0
+ self._py_ver_consistent = py_ver_consistent
self.dispatch(tree)
print("", file=self.f)
self.f.flush()
@@ -175,7 +202,12 @@ class Unparser:
self.dispatch(t.locals)
def _Print(self, t):
- self.fill("print ")
+ # Use print function so that python 2 unparsing is consistent with 3
+ if self._py_ver_consistent:
+ self.fill("print(")
+ else:
+ self.fill("print ")
+
do_comma = False
if t.dest:
self.write(">>")
@@ -188,6 +220,9 @@ class Unparser:
if not t.nl:
self.write(",")
+ if self._py_ver_consistent:
+ self.write(")")
+
def _Global(self, t):
self.fill("global ")
interleave(lambda: self.write(", "), self.write, t.names)
@@ -335,9 +370,10 @@ class Unparser:
self.write(")")
elif t.bases:
self.write("(")
- for a in t.bases:
+ for a in t.bases[:-1]:
self.dispatch(a)
self.write(", ")
+ self.dispatch(t.bases[-1])
self.write(")")
self.enter()
self.dispatch(t.body)
@@ -662,7 +698,8 @@ class Unparser:
def _UnaryOp(self, t):
self.write("(")
self.write(self.unop[t.op.__class__.__name__])
- self.write(" ")
+ if not self._py_ver_consistent:
+ self.write(" ")
if six.PY2 and isinstance(t.op, ast.USub) and isinstance(t.operand, ast.Num):
# If we're applying unary minus to a number, parenthesize the number.
# This is necessary: -2147483648 is different from -(2147483648) on
@@ -717,14 +754,34 @@ class Unparser:
self.dispatch(t.func)
self.write("(")
comma = False
+
+ # move starred arguments last in Python 3.5+, for consistency w/earlier versions
+ star_and_kwargs = []
+ move_stars_last = sys.version_info[:2] >= (3, 5)
+
for e in t.args:
- if comma: self.write(", ")
- else: comma = True
- self.dispatch(e)
+ if move_stars_last and isinstance(e, ast.Starred):
+ star_and_kwargs.append(e)
+ else:
+ if comma: self.write(", ")
+ else: comma = True
+ self.dispatch(e)
+
for e in t.keywords:
- if comma: self.write(", ")
- else: comma = True
- self.dispatch(e)
+ # starting from Python 3.5 this denotes a kwargs part of the invocation
+ if e.arg is None and move_stars_last:
+ star_and_kwargs.append(e)
+ else:
+ if comma: self.write(", ")
+ else: comma = True
+ self.dispatch(e)
+
+ if move_stars_last:
+ for e in star_and_kwargs:
+ if comma: self.write(", ")
+ else: comma = True
+ self.dispatch(e)
+
if sys.version_info[:2] < (3, 5):
if t.starargs:
if comma: self.write(", ")
@@ -736,6 +793,7 @@ class Unparser:
else: comma = True
self.write("**")
self.dispatch(t.kwargs)
+
self.write(")")
def _Subscript(self, t):