summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGuido van Rossum <guido@python.org>2018-01-26 16:20:18 (GMT)
committerŁukasz Langa <lukasz@langa.pl>2018-01-26 16:20:18 (GMT)
commit95e4d589137260530e18ef98a2ed84ee3ec57e12 (patch)
tree9d0c3bc48158e9f0c83f1b9cb509c1fbebd9cfde
parentd7773d92bd11640a8c950d6c36a9cef1cee36f96 (diff)
downloadcpython-95e4d589137260530e18ef98a2ed84ee3ec57e12.zip
cpython-95e4d589137260530e18ef98a2ed84ee3ec57e12.tar.gz
cpython-95e4d589137260530e18ef98a2ed84ee3ec57e12.tar.bz2
String annotations [PEP 563] (#4390)
* Document `from __future__ import annotations` * Provide plumbing and tests for `from __future__ import annotations` * Implement unparsing the AST back to string form This is required for PEP 563 and as such only implements a part of the unparsing process that covers expressions.
-rw-r--r--Doc/glossary.rst17
-rw-r--r--Doc/library/__future__.rst5
-rw-r--r--Doc/reference/compound_stmts.rst25
-rw-r--r--Doc/reference/simple_stmts.rst15
-rw-r--r--Doc/whatsnew/3.7.rst51
-rw-r--r--Include/ast.h9
-rw-r--r--Include/code.h1
-rw-r--r--Include/compile.h3
-rw-r--r--Lib/__future__.py20
-rw-r--r--Lib/test/test_future.py163
-rw-r--r--Makefile.pre.in3
-rw-r--r--PCbuild/pythoncore.vcxproj1
-rw-r--r--PCbuild/pythoncore.vcxproj.filters3
-rw-r--r--Python/ast_unparse.c1163
-rw-r--r--Python/compile.c26
-rw-r--r--Python/future.c2
16 files changed, 1477 insertions, 30 deletions
diff --git a/Doc/glossary.rst b/Doc/glossary.rst
index f0fcb62..dcfe086 100644
--- a/Doc/glossary.rst
+++ b/Doc/glossary.rst
@@ -372,9 +372,11 @@ Glossary
may be accessed via the :attr:`__annotations__` special attribute of a
function object.
- Python itself does not assign any particular meaning to function
- annotations. They are intended to be interpreted by third-party libraries
- or tools. See :pep:`3107`, which describes some of their potential uses.
+ See also the :term:`variable annotation` glossary entry.
+
+ Annotations are meant to provide a standard way for programmers to
+ document types of functions they design. See :pep:`484`, which
+ describes this functionality.
__future__
A pseudo-module which programmers can use to enable new language features
@@ -1021,10 +1023,11 @@ Glossary
attribute of a class or module object and can be accessed using
:func:`typing.get_type_hints`.
- Python itself does not assign any particular meaning to variable
- annotations. They are intended to be interpreted by third-party libraries
- or type checking tools. See :pep:`526`, :pep:`484` which describe
- some of their potential uses.
+ See also the :term:`function annotation` glossary entry.
+
+ Annotations are meant to provide a standard way for programmers to
+ document types of functions they design. See :pep:`484` and :pep:`526`
+ which describe this functionality.
virtual environment
A cooperatively isolated runtime environment that allows Python users
diff --git a/Doc/library/__future__.rst b/Doc/library/__future__.rst
index 73d8b6b..e3d749e 100644
--- a/Doc/library/__future__.rst
+++ b/Doc/library/__future__.rst
@@ -90,6 +90,11 @@ language using this mechanism:
| generator_stop | 3.5.0b1 | 3.7 | :pep:`479`: |
| | | | *StopIteration handling inside generators* |
+------------------+-------------+--------------+---------------------------------------------+
+| annotations | 3.7.0b1 | 4.0 | :pep:`563`: |
+| | | | *Postponed evaluation of annotations* |
++------------------+-------------+--------------+---------------------------------------------+
+
+.. XXX Adding a new entry? Remember to update simple_stmts.rst, too.
.. seealso::
diff --git a/Doc/reference/compound_stmts.rst b/Doc/reference/compound_stmts.rst
index dca9362..d7792f1 100644
--- a/Doc/reference/compound_stmts.rst
+++ b/Doc/reference/compound_stmts.rst
@@ -559,12 +559,14 @@ Parameters may have annotations of the form "``: expression``" following the
parameter name. Any parameter may have an annotation even those of the form
``*identifier`` or ``**identifier``. Functions may have "return" annotation of
the form "``-> expression``" after the parameter list. These annotations can be
-any valid Python expression and are evaluated when the function definition is
-executed. Annotations may be evaluated in a different order than they appear in
-the source code. The presence of annotations does not change the semantics of a
-function. The annotation values are available as values of a dictionary keyed
-by the parameters' names in the :attr:`__annotations__` attribute of the
-function object.
+any valid Python expression. The presence of annotations does not change the
+semantics of a function. The annotation values are available as values of
+a dictionary keyed by the parameters' names in the :attr:`__annotations__`
+attribute of the function object. If the ``annotations`` import from
+:mod:`__future__` is used, annotations are preserved as strings at runtime which
+enables postponed evaluation. Otherwise, they are evaluated when the function
+definition is executed. In this case annotations may be evaluated in
+a different order than they appear in the source code.
.. index:: pair: lambda; expression
@@ -587,6 +589,17 @@ access the local variables of the function containing the def. See section
:pep:`3107` - Function Annotations
The original specification for function annotations.
+ :pep:`484` - Type Hints
+ Definition of a standard meaning for annotations: type hints.
+
+ :pep:`526` - Syntax for Variable Annotations
+ Ability to type hint variable declarations, including class
+ variables and instance variables
+
+ :pep:`563` - Postponed Evaluation of Annotations
+ Support for forward references within annotations by preserving
+ annotations in a string form at runtime instead of eager evaluation.
+
.. _class:
diff --git a/Doc/reference/simple_stmts.rst b/Doc/reference/simple_stmts.rst
index 8d17383..ef9a5f0 100644
--- a/Doc/reference/simple_stmts.rst
+++ b/Doc/reference/simple_stmts.rst
@@ -853,12 +853,15 @@ can appear before a future statement are:
* blank lines, and
* other future statements.
-.. XXX change this if future is cleaned out
-
-The features recognized by Python 3.0 are ``absolute_import``, ``division``,
-``generators``, ``unicode_literals``, ``print_function``, ``nested_scopes`` and
-``with_statement``. They are all redundant because they are always enabled, and
-only kept for backwards compatibility.
+The only feature in Python 3.7 that requires using the future statement is
+``annotations``.
+
+All historical features enabled by the future statement are still recognized
+by Python 3. The list includes ``absolute_import``, ``division``,
+``generators``, ``generator_stop``, ``unicode_literals``,
+``print_function``, ``nested_scopes`` and ``with_statement``. They are
+all redundant because they are always enabled, and only kept for
+backwards compatibility.
A future statement is recognized and treated specially at compile time: Changes
to the semantics of core constructs are often implemented by generating
diff --git a/Doc/whatsnew/3.7.rst b/Doc/whatsnew/3.7.rst
index 0e1714e..a350919 100644
--- a/Doc/whatsnew/3.7.rst
+++ b/Doc/whatsnew/3.7.rst
@@ -179,6 +179,57 @@ a normal ``__getattr__`` method, except that it will be defined on module
PEP written and implemented by Ivan Levkivskyi
+PEP 563: Postponed evaluation of annotations
+--------------------------------------------
+
+The advent of type hints in Python uncovered two glaring usability issues
+with the functionality of annotations added in :pep:`3107` and refined
+further in :pep:`526`:
+
+* annotations could only use names which were already available in the
+ current scope, in other words they didn't support forward references
+ of any kind; and
+
+* annotating source code had adverse effects on startup time of Python
+ programs.
+
+Both of these issues are fixed by postponing the evaluation of
+annotations. Instead of compiling code which executes expressions in
+annotations at their definition time, the compiler stores the annotation
+in a string form equivalent to the AST of the expression in question.
+If needed, annotations can be resolved at runtime using
+``typing.get_type_hints()``. In the common case where this is not
+required, the annotations are cheaper to store (since short strings
+are interned by the interpreter) and make startup time faster.
+
+Usability-wise, annotations now support forward references, making the
+following syntax valid::
+
+ class C:
+ @classmethod
+ def from_string(cls, source: str) -> C:
+ ...
+
+ def validate_b(self, obj: B) -> bool:
+ ...
+
+ class B:
+ ...
+
+Since this change breaks compatibility, the new behavior can be enabled
+on a per-module basis in Python 3.7 using a ``__future__`` import, like
+this::
+
+ from __future__ import annotations
+
+It will become the default in Python 4.0.
+
+.. seealso::
+
+ :pep:`563` -- Postponed evaluation of annotations
+ PEP written and implemented by Łukasz Langa.
+
+
PEP 564: Add new time functions with nanosecond resolution
----------------------------------------------------------
diff --git a/Include/ast.h b/Include/ast.h
index 6a8c816..639c4f8 100644
--- a/Include/ast.h
+++ b/Include/ast.h
@@ -16,6 +16,15 @@ PyAPI_FUNC(mod_ty) PyAST_FromNodeObject(
PyObject *filename,
PyArena *arena);
+#ifndef Py_LIMITED_API
+
+/* _PyAST_ExprAsUnicode is defined in ast_unparse.c */
+PyAPI_FUNC(PyObject *) _PyAST_ExprAsUnicode(
+ expr_ty e,
+ int omit_parens);
+
+#endif /* !Py_LIMITED_API */
+
#ifdef __cplusplus
}
#endif
diff --git a/Include/code.h b/Include/code.h
index 8b0f840..2e661e8 100644
--- a/Include/code.h
+++ b/Include/code.h
@@ -82,6 +82,7 @@ typedef struct {
#define CO_FUTURE_BARRY_AS_BDFL 0x40000
#define CO_FUTURE_GENERATOR_STOP 0x80000
+#define CO_FUTURE_ANNOTATIONS 0x100000
/* This value is found in the co_cell2arg array when the associated cell
variable does not correspond to an argument. */
diff --git a/Include/compile.h b/Include/compile.h
index edfd8bb..edb961f 100644
--- a/Include/compile.h
+++ b/Include/compile.h
@@ -16,7 +16,7 @@ PyAPI_FUNC(PyCodeObject *) PyNode_Compile(struct _node *, const char *);
#define PyCF_MASK (CO_FUTURE_DIVISION | CO_FUTURE_ABSOLUTE_IMPORT | \
CO_FUTURE_WITH_STATEMENT | CO_FUTURE_PRINT_FUNCTION | \
CO_FUTURE_UNICODE_LITERALS | CO_FUTURE_BARRY_AS_BDFL | \
- CO_FUTURE_GENERATOR_STOP)
+ CO_FUTURE_GENERATOR_STOP | CO_FUTURE_ANNOTATIONS)
#define PyCF_MASK_OBSOLETE (CO_NESTED)
#define PyCF_SOURCE_IS_UTF8 0x0100
#define PyCF_DONT_IMPLY_DEDENT 0x0200
@@ -45,6 +45,7 @@ typedef struct {
#define FUTURE_UNICODE_LITERALS "unicode_literals"
#define FUTURE_BARRY_AS_BDFL "barry_as_FLUFL"
#define FUTURE_GENERATOR_STOP "generator_stop"
+#define FUTURE_ANNOTATIONS "annotations"
struct _mod; /* Declare the existence of this type */
#define PyAST_Compile(mod, s, f, ar) PyAST_CompileEx(mod, s, f, -1, ar)
diff --git a/Lib/__future__.py b/Lib/__future__.py
index 63b2be3..ce8bed7 100644
--- a/Lib/__future__.py
+++ b/Lib/__future__.py
@@ -57,13 +57,14 @@ all_feature_names = [
"unicode_literals",
"barry_as_FLUFL",
"generator_stop",
+ "annotations",
]
__all__ = ["all_feature_names"] + all_feature_names
-# The CO_xxx symbols are defined here under the same names used by
-# compile.h, so that an editor search will find them here. However,
-# they're not exported in __all__, because they don't really belong to
+# The CO_xxx symbols are defined here under the same names defined in
+# code.h and used by compile.h, so that an editor search will find them here.
+# However, they're not exported in __all__, because they don't really belong to
# this module.
CO_NESTED = 0x0010 # nested_scopes
CO_GENERATOR_ALLOWED = 0 # generators (obsolete, was 0x1000)
@@ -74,6 +75,7 @@ CO_FUTURE_PRINT_FUNCTION = 0x10000 # print function
CO_FUTURE_UNICODE_LITERALS = 0x20000 # unicode string literals
CO_FUTURE_BARRY_AS_BDFL = 0x40000
CO_FUTURE_GENERATOR_STOP = 0x80000 # StopIteration becomes RuntimeError in generators
+CO_FUTURE_ANNOTATIONS = 0x100000 # annotations become strings at runtime
class _Feature:
def __init__(self, optionalRelease, mandatoryRelease, compiler_flag):
@@ -132,9 +134,13 @@ unicode_literals = _Feature((2, 6, 0, "alpha", 2),
CO_FUTURE_UNICODE_LITERALS)
barry_as_FLUFL = _Feature((3, 1, 0, "alpha", 2),
- (3, 9, 0, "alpha", 0),
- CO_FUTURE_BARRY_AS_BDFL)
+ (3, 9, 0, "alpha", 0),
+ CO_FUTURE_BARRY_AS_BDFL)
generator_stop = _Feature((3, 5, 0, "beta", 1),
- (3, 7, 0, "alpha", 0),
- CO_FUTURE_GENERATOR_STOP)
+ (3, 7, 0, "alpha", 0),
+ CO_FUTURE_GENERATOR_STOP)
+
+annotations = _Feature((3, 7, 0, "beta", 1),
+ (4, 0, 0, "alpha", 0),
+ CO_FUTURE_ANNOTATIONS)
diff --git a/Lib/test/test_future.py b/Lib/test/test_future.py
index 2f1c410..29c4632 100644
--- a/Lib/test/test_future.py
+++ b/Lib/test/test_future.py
@@ -1,7 +1,9 @@
# Test various flavors of legal and illegal future statements
+from functools import partial
import unittest
from test import support
+from textwrap import dedent
import os
import re
@@ -102,6 +104,167 @@ class FutureTest(unittest.TestCase):
exec("from __future__ import unicode_literals; x = ''", {}, scope)
self.assertIsInstance(scope["x"], str)
+class AnnotationsFutureTestCase(unittest.TestCase):
+ template = dedent(
+ """
+ from __future__ import annotations
+ def f() -> {ann}:
+ ...
+ def g(arg: {ann}) -> None:
+ ...
+ var: {ann}
+ var2: {ann} = None
+ """
+ )
+
+ def getActual(self, annotation):
+ scope = {}
+ exec(self.template.format(ann=annotation), {}, scope)
+ func_ret_ann = scope['f'].__annotations__['return']
+ func_arg_ann = scope['g'].__annotations__['arg']
+ var_ann1 = scope['__annotations__']['var']
+ var_ann2 = scope['__annotations__']['var2']
+ self.assertEqual(func_ret_ann, func_arg_ann)
+ self.assertEqual(func_ret_ann, var_ann1)
+ self.assertEqual(func_ret_ann, var_ann2)
+ return func_ret_ann
+
+ def assertAnnotationEqual(
+ self, annotation, expected=None, drop_parens=False, is_tuple=False,
+ ):
+ actual = self.getActual(annotation)
+ if expected is None:
+ expected = annotation if not is_tuple else annotation[1:-1]
+ if drop_parens:
+ self.assertNotEqual(actual, expected)
+ actual = actual.replace("(", "").replace(")", "")
+
+ self.assertEqual(actual, expected)
+
+ def test_annotations(self):
+ eq = self.assertAnnotationEqual
+ eq('...')
+ eq("'some_string'")
+ eq("b'\\xa3'")
+ eq('Name')
+ eq('None')
+ eq('True')
+ eq('False')
+ eq('1')
+ eq('1.0')
+ eq('1j')
+ eq('True or False')
+ eq('True or False or None')
+ eq('True and False')
+ eq('True and False and None')
+ eq('(Name1 and Name2) or Name3')
+ eq('Name1 or (Name2 and Name3)')
+ eq('(Name1 and Name2) or (Name3 and Name4)')
+ eq('Name1 or (Name2 and Name3) or Name4')
+ eq('v1 << 2')
+ eq('1 >> v2')
+ eq(r'1 % finished')
+ eq('((1 + v2) - (v3 * 4)) ^ (((5 ** v6) / 7) // 8)')
+ eq('not great')
+ eq('~great')
+ eq('+value')
+ eq('-1')
+ eq('(~int) and (not ((v1 ^ (123 + v2)) | True))')
+ eq('lambda arg: None')
+ eq('lambda a=True: a')
+ eq('lambda a, b, c=True: a')
+ eq("lambda a, b, c=True, *, d=(1 << v2), e='str': a")
+ eq("lambda a, b, c=True, *vararg, d=(v1 << 2), e='str', **kwargs: a + b")
+ eq('1 if True else 2')
+ eq('(str or None) if True else (str or bytes or None)')
+ eq('(str or None) if (1 if True else 2) else (str or bytes or None)')
+ eq("{'2.7': dead, '3.7': (long_live or die_hard)}")
+ eq("{'2.7': dead, '3.7': (long_live or die_hard), **{'3.6': verygood}}")
+ eq("{**a, **b, **c}")
+ eq("{'2.7', '3.6', '3.7', '3.8', '3.9', ('4.0' if gilectomy else '3.10')}")
+ eq("({'a': 'b'}, (True or False), (+value), 'string', b'bytes') or None")
+ eq("()")
+ eq("(1,)")
+ eq("(1, 2)")
+ eq("(1, 2, 3)")
+ eq("[]")
+ eq("[1, 2, 3, 4, 5, 6, 7, 8, 9, (10 or A), (11 or B), (12 or C)]")
+ eq("{i for i in (1, 2, 3)}")
+ eq("{(i ** 2) for i in (1, 2, 3)}")
+ eq("{(i ** 2) for i, _ in ((1, 'a'), (2, 'b'), (3, 'c'))}")
+ eq("{((i ** 2) + j) for i in (1, 2, 3) for j in (1, 2, 3)}")
+ eq("[i for i in (1, 2, 3)]")
+ eq("[(i ** 2) for i in (1, 2, 3)]")
+ eq("[(i ** 2) for i, _ in ((1, 'a'), (2, 'b'), (3, 'c'))]")
+ eq("[((i ** 2) + j) for i in (1, 2, 3) for j in (1, 2, 3)]")
+ eq(r"{i: 0 for i in (1, 2, 3)}")
+ eq("{i: j for i, j in ((1, 'a'), (2, 'b'), (3, 'c'))}")
+ eq("Python3 > Python2 > COBOL")
+ eq("Life is Life")
+ eq("call()")
+ eq("call(arg)")
+ eq("call(kwarg='hey')")
+ eq("call(arg, kwarg='hey')")
+ eq("call(arg, another, kwarg='hey', **kwargs)")
+ eq("lukasz.langa.pl")
+ eq("call.me(maybe)")
+ eq("1 .real")
+ eq("1.0 .real")
+ eq("....__class__")
+ eq("list[str]")
+ eq("dict[str, int]")
+ eq("tuple[str, ...]")
+ eq("tuple[str, int, float, dict[str, int]]")
+ eq("slice[0]")
+ eq("slice[0:1]")
+ eq("slice[0:1:2]")
+ eq("slice[:]")
+ eq("slice[:-1]")
+ eq("slice[1:]")
+ eq("slice[::-1]")
+ eq('(str or None) if (sys.version_info[0] > (3,)) else (str or bytes or None)')
+ eq("f'f-string without formatted values is just a string'")
+ eq("f'{{NOT a formatted value}}'")
+ eq("f'some f-string with {a} {few():.2f} {formatted.values!r}'")
+ eq('''f"{f'{nested} inner'} outer"''')
+ eq("f'space between opening braces: { {a for a in (1, 2, 3)}}'")
+
+ def test_annotations_inexact(self):
+ """Source formatting is not always preserved
+
+ This is due to reconstruction from AST. We *need to* put the parens
+ in nested expressions because we don't know if the source code
+ had them in the first place or not.
+ """
+ eq = partial(self.assertAnnotationEqual, drop_parens=True)
+ eq('Name1 and Name2 or Name3')
+ eq('Name1 or Name2 and Name3')
+ eq('Name1 and Name2 or Name3 and Name4')
+ eq('Name1 or Name2 and Name3 or Name4')
+ eq('1 + v2 - v3 * 4 ^ v5 ** 6 / 7 // 8')
+ eq('~int and not v1 ^ 123 + v2 | True')
+ eq('str or None if True else str or bytes or None')
+ eq("{'2.7': dead, '3.7': long_live or die_hard}")
+ eq("{'2.7', '3.6', '3.7', '3.8', '3.9', '4.0' if gilectomy else '3.10'}")
+ eq("[1, 2, 3, 4, 5, 6, 7, 8, 9, 10 or A, 11 or B, 12 or C]")
+ # Consequently, we always drop unnecessary parens if they were given in
+ # the outer scope:
+ some_name = self.getActual("(SomeName)")
+ self.assertEqual(some_name, 'SomeName')
+ # Interestingly, in the case of tuples (and generator expressions) the
+ # parens are *required* by the Python syntax in the annotation context.
+ # But there's no point storing that detail in __annotations__ so we're
+ # fine with the parens-less form.
+ eq = partial(self.assertAnnotationEqual, is_tuple=True)
+ eq("(Good, Bad, Ugly)")
+ eq("(i for i in (1, 2, 3))")
+ eq("((i ** 2) for i in (1, 2, 3))")
+ eq("((i ** 2) for i, _ in ((1, 'a'), (2, 'b'), (3, 'c')))")
+ eq("(((i ** 2) + j) for i in (1, 2, 3) for j in (1, 2, 3))")
+ eq("(*starred)")
+ eq('(yield from outside_of_generator)')
+ eq('(yield)')
+ eq('(await some.complicated[0].call(with_args=(True or (1 is not 1))))')
if __name__ == "__main__":
diff --git a/Makefile.pre.in b/Makefile.pre.in
index 162006a..f58b715 100644
--- a/Makefile.pre.in
+++ b/Makefile.pre.in
@@ -325,6 +325,7 @@ PYTHON_OBJS= \
Python/asdl.o \
Python/ast.o \
Python/ast_opt.o \
+ Python/ast_unparse.o \
Python/bltinmodule.o \
Python/ceval.o \
Python/compile.o \
@@ -840,7 +841,7 @@ regen-opcode:
$(srcdir)/Include/opcode.h.new
$(UPDATE_FILE) $(srcdir)/Include/opcode.h $(srcdir)/Include/opcode.h.new
-Python/compile.o Python/symtable.o Python/ast.o: $(srcdir)/Include/graminit.h $(srcdir)/Include/Python-ast.h
+Python/compile.o Python/symtable.o Python/ast_unparse.o Python/ast.o: $(srcdir)/Include/graminit.h $(srcdir)/Include/Python-ast.h
Python/getplatform.o: $(srcdir)/Python/getplatform.c
$(CC) -c $(PY_CORE_CFLAGS) -DPLATFORM='"$(MACHDEP)"' -o $@ $(srcdir)/Python/getplatform.c
diff --git a/PCbuild/pythoncore.vcxproj b/PCbuild/pythoncore.vcxproj
index fbcd051..f309455 100644
--- a/PCbuild/pythoncore.vcxproj
+++ b/PCbuild/pythoncore.vcxproj
@@ -358,6 +358,7 @@
<ClCompile Include="..\Python\asdl.c" />
<ClCompile Include="..\Python\ast.c" />
<ClCompile Include="..\Python\ast_opt.c" />
+ <ClCompile Include="..\Python\ast_unparse.c" />
<ClCompile Include="..\Python\bltinmodule.c" />
<ClCompile Include="..\Python\bootstrap_hash.c" />
<ClCompile Include="..\Python\ceval.c" />
diff --git a/PCbuild/pythoncore.vcxproj.filters b/PCbuild/pythoncore.vcxproj.filters
index a10686c..5f98038 100644
--- a/PCbuild/pythoncore.vcxproj.filters
+++ b/PCbuild/pythoncore.vcxproj.filters
@@ -839,6 +839,9 @@
<ClCompile Include="..\Python\ast_opt.c">
<Filter>Python</Filter>
</ClCompile>
+ <ClCompile Include="..\Python\ast_unparse.c">
+ <Filter>Python</Filter>
+ </ClCompile>
<ClCompile Include="..\Python\bltinmodule.c">
<Filter>Python</Filter>
</ClCompile>
diff --git a/Python/ast_unparse.c b/Python/ast_unparse.c
new file mode 100644
index 0000000..ef9e948
--- /dev/null
+++ b/Python/ast_unparse.c
@@ -0,0 +1,1163 @@
+#include <stdbool.h>
+#include "Python.h"
+#include "Python-ast.h"
+
+static PyObject *_str_open_br;
+static PyObject *_str_dbl_open_br;
+static PyObject *_str_close_br;
+static PyObject *_str_dbl_close_br;
+
+/* Forward declarations for recursion via helper functions. */
+static PyObject *
+expr_as_unicode(expr_ty e, bool omit_parens);
+static int
+append_ast_expr(_PyUnicodeWriter *writer, expr_ty e, bool omit_parens);
+static int
+append_joinedstr(_PyUnicodeWriter *writer, expr_ty e, bool is_format_spec);
+static int
+append_formattedvalue(_PyUnicodeWriter *writer, expr_ty e, bool is_format_spec);
+
+static int
+append_charp(_PyUnicodeWriter *writer, const char *charp)
+{
+ return _PyUnicodeWriter_WriteASCIIString(writer, charp, -1);
+}
+
+static int
+append_repr(_PyUnicodeWriter *writer, PyObject *obj)
+{
+ int ret;
+ PyObject *repr;
+ repr = PyObject_Repr(obj);
+ if (!repr) {
+ return -1;
+ }
+ ret = _PyUnicodeWriter_WriteStr(writer, repr);
+ Py_DECREF(repr);
+ return ret;
+}
+
+static int
+append_ast_boolop(_PyUnicodeWriter *writer, expr_ty e, bool omit_parens)
+{
+ Py_ssize_t i, value_count;
+ asdl_seq *values;
+
+ if (!omit_parens && -1 == append_charp(writer, "(")) {
+ return -1;
+ }
+
+ values = e->v.BoolOp.values;
+ value_count = asdl_seq_LEN(values) - 1;
+ assert(value_count >= 0);
+
+ if (-1 == append_ast_expr(writer,
+ (expr_ty)asdl_seq_GET(values, 0),
+ false)) {
+ return -1;
+ }
+
+ const char *op = (e->v.BoolOp.op == And) ? " and " : " or ";
+ for (i = 1; i <= value_count; ++i) {
+ if (-1 == append_charp(writer, op)) {
+ return -1;
+ }
+
+ if (-1 == append_ast_expr(writer,
+ (expr_ty)asdl_seq_GET(values, i),
+ false)) {
+ return -1;
+ }
+ }
+
+ return omit_parens ? 0 : append_charp(writer, ")");
+}
+
+static int
+append_ast_binop(_PyUnicodeWriter *writer, expr_ty e, bool omit_parens)
+{
+ const char *op;
+
+ if (!omit_parens && -1 == append_charp(writer, "(")) {
+ return -1;
+ }
+
+ if (-1 == append_ast_expr(writer, e->v.BinOp.left, false)) {
+ return -1;
+ }
+
+ switch(e->v.BinOp.op) {
+ case Add: op = " + "; break;
+ case Sub: op = " - "; break;
+ case Mult: op = " * "; break;
+ case MatMult: op = " @ "; break;
+ case Div: op = " / "; break;
+ case Mod: op = " % "; break;
+ case LShift: op = " << "; break;
+ case RShift: op = " >> "; break;
+ case BitOr: op = " | "; break;
+ case BitXor: op = " ^ "; break;
+ case BitAnd: op = " & "; break;
+ case FloorDiv: op = " // "; break;
+ case Pow: op = " ** "; break;
+ }
+
+ if (-1 == append_charp(writer, op)) {
+ return -1;
+ }
+
+ if (-1 == append_ast_expr(writer, e->v.BinOp.right, false)) {
+ return -1;
+ }
+
+ return omit_parens ? 0 : append_charp(writer, ")");
+}
+
+static int
+append_ast_unaryop(_PyUnicodeWriter *writer, expr_ty e, bool omit_parens)
+{
+ const char *op;
+
+ if (!omit_parens && -1 == append_charp(writer, "(")) {
+ return -1;
+ }
+
+ switch(e->v.UnaryOp.op) {
+ case Invert: op = "~"; break;
+ case Not: op = "not "; break;
+ case UAdd: op = "+"; break;
+ case USub: op = "-"; break;
+ }
+
+ if (-1 == append_charp(writer, op)) {
+ return -1;
+ }
+
+ if (-1 == append_ast_expr(writer, e->v.UnaryOp.operand, false)) {
+ return -1;
+ }
+
+ return omit_parens ? 0 : append_charp(writer, ")");
+}
+
+static int
+append_ast_arg(_PyUnicodeWriter *writer, arg_ty arg)
+{
+ if (-1 == _PyUnicodeWriter_WriteStr(writer, arg->arg)) {
+ return -1;
+ }
+ if (arg->annotation) {
+ if (-1 == append_charp(writer, ": ")) {
+ return -1;
+ }
+ if (-1 == append_ast_expr(writer, arg->annotation, true)) {
+ return -1;
+ }
+ }
+ return 0;
+}
+
+static int
+append_ast_args(_PyUnicodeWriter *writer, arguments_ty args)
+{
+ bool first;
+ Py_ssize_t i, di, arg_count, default_count;
+ arg_ty arg;
+ expr_ty default_;
+
+ first = true;
+
+ /* positional arguments with defaults */
+ arg_count = asdl_seq_LEN(args->args);
+ default_count = asdl_seq_LEN(args->defaults);
+ for (i = 0; i < arg_count; i++) {
+ if (first) {
+ first = false;
+ }
+ else if (-1 == append_charp(writer, ", ")) {
+ return -1;
+ }
+
+ arg = (arg_ty)asdl_seq_GET(args->args, i);
+ if (-1 == append_ast_arg(writer, arg)) {
+ return -1;
+ }
+
+ di = i - arg_count + default_count;
+ if (di >= 0) {
+ if (-1 == append_charp(writer, "=")) {
+ return -1;
+ }
+ default_ = (expr_ty)asdl_seq_GET(args->defaults, di);
+ if (-1 == append_ast_expr(writer, default_, false)) {
+ return -1;
+ }
+ }
+ }
+
+ /* vararg, or bare '*' if no varargs but keyword-only arguments present */
+ if (args->vararg || args->kwonlyargs) {
+ if (first) {
+ first = false;
+ }
+ else if (-1 == append_charp(writer, ", ")) {
+ return -1;
+ }
+
+ if (-1 == append_charp(writer, "*")) {
+ return -1;
+ }
+
+ if (args->vararg) {
+ if (-1 == append_ast_arg(writer, args->vararg)) {
+ return -1;
+ }
+ }
+ }
+
+ /* keyword-only arguments */
+ arg_count = asdl_seq_LEN(args->kwonlyargs);
+ default_count = asdl_seq_LEN(args->kw_defaults);
+ for (i = 0; i < arg_count; i++) {
+ if (first) {
+ first = false;
+ }
+ else if (-1 == append_charp(writer, ", ")) {
+ return -1;
+ }
+
+ arg = (arg_ty)asdl_seq_GET(args->kwonlyargs, i);
+ if (-1 == append_ast_arg(writer, arg)) {
+ return -1;
+ }
+
+ di = i - arg_count + default_count;
+ if (di >= 0) {
+ if (-1 == append_charp(writer, "=")) {
+ return -1;
+ }
+ default_ = (expr_ty)asdl_seq_GET(args->kw_defaults, di);
+ if (-1 == append_ast_expr(writer, default_, false)) {
+ return -1;
+ }
+ }
+ }
+
+ /* **kwargs */
+ if (args->kwarg) {
+ if (first) {
+ first = false;
+ }
+ else if (-1 == append_charp(writer, ", ")) {
+ return -1;
+ }
+
+ if (-1 == append_charp(writer, "**")) {
+ return -1;
+ }
+
+ if (-1 == append_ast_arg(writer, args->kwarg)) {
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+static int
+append_ast_lambda(_PyUnicodeWriter *writer, expr_ty e, bool omit_parens)
+{
+ if (!omit_parens && -1 == append_charp(writer, "(")) {
+ return -1;
+ }
+
+ if (-1 == append_charp(writer, "lambda ")) {
+ return -1;
+ }
+
+ if (-1 == append_ast_args(writer, e->v.Lambda.args)) {
+ return -1;
+ }
+
+ if (-1 == append_charp(writer, ": ")) {
+ return -1;
+ }
+
+ if (-1 == append_ast_expr(writer, e->v.Lambda.body, true)) {
+ return -1;
+ }
+
+ return omit_parens ? 0 : append_charp(writer, ")");
+}
+
+static int
+append_ast_ifexp(_PyUnicodeWriter *writer, expr_ty e, bool omit_parens)
+{
+ if (!omit_parens && -1 == append_charp(writer, "(")) {
+ return -1;
+ }
+
+ if (-1 == append_ast_expr(writer, e->v.IfExp.body, false)) {
+ return -1;
+ }
+
+ if (-1 == append_charp(writer, " if ")) {
+ return -1;
+ }
+
+ if (-1 == append_ast_expr(writer, e->v.IfExp.test, false)) {
+ return -1;
+ }
+
+ if (-1 == append_charp(writer, " else ")) {
+ return -1;
+ }
+
+ if (-1 == append_ast_expr(writer, e->v.IfExp.orelse, false)) {
+ return -1;
+ }
+
+ return omit_parens ? 0 : append_charp(writer, ")");
+}
+
+static int
+append_ast_dict(_PyUnicodeWriter *writer, expr_ty e)
+{
+ Py_ssize_t i, value_count;
+ expr_ty key_node, value_node;
+
+ if (-1 == append_charp(writer, "{")) {
+ return -1;
+ }
+
+ value_count = asdl_seq_LEN(e->v.Dict.values);
+
+ for (i = 0; i < value_count; i++) {
+ if (i > 0 && -1 == append_charp(writer, ", ")) {
+ return -1;
+ }
+ key_node = (expr_ty)asdl_seq_GET(e->v.Dict.keys, i);
+ if (key_node != NULL) {
+ if (-1 == append_ast_expr(writer, key_node, false)) {
+ return -1;
+ }
+
+ if (-1 == append_charp(writer, ": ")) {
+ return -1;
+ }
+ }
+ else if (-1 == append_charp(writer, "**")) {
+ return -1;
+ }
+
+ value_node = (expr_ty)asdl_seq_GET(e->v.Dict.values, i);
+ if (-1 == append_ast_expr(writer, value_node, false)) {
+ return -1;
+ }
+ }
+
+ return append_charp(writer, "}");
+}
+
+static int
+append_ast_set(_PyUnicodeWriter *writer, expr_ty e)
+{
+ Py_ssize_t i, elem_count;
+ expr_ty elem_node;
+
+ if (-1 == append_charp(writer, "{")) {
+ return -1;
+ }
+
+ elem_count = asdl_seq_LEN(e->v.Set.elts);
+ for (i = 0; i < elem_count; i++) {
+ if (i > 0 && -1 == append_charp(writer, ", ")) {
+ return -1;
+ }
+
+ elem_node = (expr_ty)asdl_seq_GET(e->v.Set.elts, i);
+ if (-1 == append_ast_expr(writer, elem_node, false)) {
+ return -1;
+ }
+ }
+
+ return append_charp(writer, "}");
+}
+
+static int
+append_ast_list(_PyUnicodeWriter *writer, expr_ty e)
+{
+ Py_ssize_t i, elem_count;
+ expr_ty elem_node;
+
+ if (-1 == append_charp(writer, "[")) {
+ return -1;
+ }
+
+ elem_count = asdl_seq_LEN(e->v.List.elts);
+ for (i = 0; i < elem_count; i++) {
+ if (i > 0 && -1 == append_charp(writer, ", ")) {
+ return -1;
+ }
+ elem_node = (expr_ty)asdl_seq_GET(e->v.List.elts, i);
+ if (-1 == append_ast_expr(writer, elem_node, false)) {
+ return -1;
+ }
+ }
+
+ return append_charp(writer, "]");
+}
+
+static int
+append_ast_tuple(_PyUnicodeWriter *writer, expr_ty e, bool omit_parens)
+{
+ Py_ssize_t i, elem_count;
+ expr_ty elem_node;
+
+ elem_count = asdl_seq_LEN(e->v.Tuple.elts);
+
+ if (!omit_parens || elem_count < 2) {
+ if (-1 == append_charp(writer, "(")) {
+ return -1;
+ }
+ }
+
+ for (i = 0; i < elem_count; i++) {
+ if ((i > 0 || elem_count == 1) && -1 == append_charp(writer, ", ")) {
+ return -1;
+ }
+ elem_node = (expr_ty)asdl_seq_GET(e->v.Tuple.elts, i);
+ if (-1 == append_ast_expr(writer, elem_node, false)) {
+ return -1;
+ }
+ }
+
+ if (!omit_parens || elem_count < 2) {
+ return append_charp(writer, ")");
+ }
+
+ return 0;
+}
+
+static int
+append_ast_comprehension(_PyUnicodeWriter *writer, comprehension_ty gen)
+{
+ Py_ssize_t i, if_count;
+
+ if (-1 == append_charp(writer, gen->is_async ? " async for " : " for ")) {
+ return -1;
+ }
+
+ if (-1 == append_ast_expr(writer, gen->target, true)) {
+ return -1;
+ }
+
+ if (-1 == append_charp(writer, " in ")) {
+ return -1;
+ }
+
+ if (-1 == append_ast_expr(writer, gen->iter, false)) {
+ return -1;
+ }
+
+ if_count = asdl_seq_LEN(gen->ifs);
+ for (i = 0; i < if_count; i++) {
+ if (-1 == append_charp(writer, " if ")) {
+ return -1;
+ }
+
+ if (-1 == append_ast_expr(writer,
+ (expr_ty)asdl_seq_GET(gen->ifs, i),
+ false)) {
+ return -1;
+ }
+ }
+ return 0;
+}
+
+static int
+append_ast_comprehensions(_PyUnicodeWriter *writer, asdl_seq *comprehensions)
+{
+ Py_ssize_t i, gen_count;
+ comprehension_ty comp_node;
+ gen_count = asdl_seq_LEN(comprehensions);
+
+ for (i = 0; i < gen_count; i++) {
+ comp_node = (comprehension_ty)asdl_seq_GET(comprehensions, i);
+ if (-1 == append_ast_comprehension(writer, comp_node)) {
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+static int
+append_ast_genexp(_PyUnicodeWriter *writer, expr_ty e, bool omit_parens)
+{
+ if (!omit_parens && -1 == append_charp(writer, "(")) {
+ return -1;
+ }
+
+ if (-1 == append_ast_expr(writer, e->v.GeneratorExp.elt, false)) {
+ return -1;
+ }
+
+ if (-1 == append_ast_comprehensions(writer, e->v.GeneratorExp.generators)) {
+ return -1;
+ }
+
+ return omit_parens ? 0 : append_charp(writer, ")");
+}
+
+static int
+append_ast_listcomp(_PyUnicodeWriter *writer, expr_ty e)
+{
+ if (-1 == append_charp(writer, "[")) {
+ return -1;
+ }
+
+ if (-1 == append_ast_expr(writer, e->v.ListComp.elt, false)) {
+ return -1;
+ }
+
+ if (-1 == append_ast_comprehensions(writer, e->v.ListComp.generators)) {
+ return -1;
+ }
+
+ return append_charp(writer, "]");
+}
+
+static int
+append_ast_setcomp(_PyUnicodeWriter *writer, expr_ty e)
+{
+ if (-1 == append_charp(writer, "{")) {
+ return -1;
+ }
+
+ if (-1 == append_ast_expr(writer, e->v.SetComp.elt, false)) {
+ return -1;
+ }
+
+ if (-1 == append_ast_comprehensions(writer, e->v.SetComp.generators)) {
+ return -1;
+ }
+
+ return append_charp(writer, "}");
+}
+
+static int
+append_ast_dictcomp(_PyUnicodeWriter *writer, expr_ty e)
+{
+ if (-1 == append_charp(writer, "{")) {
+ return -1;
+ }
+
+ if (-1 == append_ast_expr(writer, e->v.DictComp.key, false)) {
+ return -1;
+ }
+
+ if (-1 == append_charp(writer, ": ")) {
+ return -1;
+ }
+
+ if (-1 == append_ast_expr(writer, e->v.DictComp.value, false)) {
+ return -1;
+ }
+
+ if (-1 == append_ast_comprehensions(writer, e->v.DictComp.generators)) {
+ return -1;
+ }
+
+ return append_charp(writer, "}");
+}
+
+static int
+append_ast_compare(_PyUnicodeWriter *writer, expr_ty e, bool omit_parens)
+{
+ const char *op;
+ Py_ssize_t i, comparator_count;
+ asdl_seq *comparators;
+ asdl_int_seq *ops;
+
+ if (!omit_parens && -1 == append_charp(writer, "(")) {
+ return -1;
+ }
+
+ comparators = e->v.Compare.comparators;
+ ops = e->v.Compare.ops;
+ comparator_count = asdl_seq_LEN(comparators);
+ assert(comparator_count > 0);
+ assert(comparator_count == asdl_seq_LEN(ops));
+
+ if (-1 == append_ast_expr(writer, e->v.Compare.left, false)) {
+ return -1;
+ }
+
+ for (i = 0; i < comparator_count; i++) {
+ switch ((cmpop_ty)asdl_seq_GET(ops, i)) {
+ case Eq:
+ op = " == ";
+ break;
+ case NotEq:
+ op = " != ";
+ break;
+ case Lt:
+ op = " < ";
+ break;
+ case LtE:
+ op = " <= ";
+ break;
+ case Gt:
+ op = " > ";
+ break;
+ case GtE:
+ op = " >= ";
+ break;
+ case Is:
+ op = " is ";
+ break;
+ case IsNot:
+ op = " is not ";
+ break;
+ case In:
+ op = " in ";
+ break;
+ case NotIn:
+ op = " not in ";
+ break;
+ default:
+ PyErr_SetString(PyExc_SystemError,
+ "unexpected comparison kind");
+ return -1;
+ }
+
+ if (-1 == append_charp(writer, op)) {
+ return -1;
+ }
+
+ if (-1 == append_ast_expr(writer,
+ (expr_ty)asdl_seq_GET(comparators, i),
+ false)) {
+ return -1;
+ }
+ }
+
+ return omit_parens ? 0 : append_charp(writer, ")");
+}
+
+static int
+append_ast_keyword(_PyUnicodeWriter *writer, keyword_ty kw)
+{
+ if (kw->arg == NULL) {
+ if (-1 == append_charp(writer, "**")) {
+ return -1;
+ }
+ }
+ else {
+ if (-1 == _PyUnicodeWriter_WriteStr(writer, kw->arg)) {
+ return -1;
+ }
+
+ if (-1 == append_charp(writer, "=")) {
+ return -1;
+ }
+ }
+
+ return append_ast_expr(writer, kw->value, false);
+}
+
+static int
+append_ast_call(_PyUnicodeWriter *writer, expr_ty e)
+{
+ bool first;
+ Py_ssize_t i, arg_count, kw_count;
+ expr_ty expr;
+ keyword_ty kw;
+
+ if (-1 == append_ast_expr(writer, e->v.Call.func, false)) {
+ return -1;
+ }
+
+ if (-1 == append_charp(writer, "(")) {
+ return -1;
+ }
+
+ first = true;
+ arg_count = asdl_seq_LEN(e->v.Call.args);
+ for (i = 0; i < arg_count; i++) {
+ if (first) {
+ first = false;
+ }
+ else if (-1 == append_charp(writer, ", ")) {
+ return -1;
+ }
+
+ expr = (expr_ty)asdl_seq_GET(e->v.Call.args, i);
+ if (-1 == append_ast_expr(writer, expr, false)) {
+ return -1;
+ }
+ }
+
+ kw_count = asdl_seq_LEN(e->v.Call.keywords);
+ for (i = 0; i < kw_count; i++) {
+ if (first) {
+ first = false;
+ }
+ else if (-1 == append_charp(writer, ", ")) {
+ return -1;
+ }
+
+ kw = (keyword_ty)asdl_seq_GET(e->v.Call.keywords, i);
+ if (-1 == append_ast_keyword(writer, kw)) {
+ return -1;
+ }
+ }
+
+ return append_charp(writer, ")");
+}
+
+static PyObject *
+escape_braces(PyObject *orig)
+{
+ PyObject *temp;
+ PyObject *result;
+ temp = PyUnicode_Replace(orig, _str_open_br, _str_dbl_open_br, -1);
+ if (!temp) {
+ return NULL;
+ }
+ result = PyUnicode_Replace(temp, _str_close_br, _str_dbl_close_br, -1);
+ Py_DECREF(temp);
+ return result;
+}
+
+static int
+append_fstring_unicode(_PyUnicodeWriter *writer, PyObject *unicode)
+{
+ PyObject *escaped;
+ int result = -1;
+ escaped = escape_braces(unicode);
+ if (escaped) {
+ result = _PyUnicodeWriter_WriteStr(writer, escaped);
+ Py_DECREF(escaped);
+ }
+ return result;
+}
+
+static int
+append_fstring_element(_PyUnicodeWriter *writer, expr_ty e, bool is_format_spec)
+{
+ switch (e->kind) {
+ case Constant_kind:
+ return append_fstring_unicode(writer, e->v.Constant.value);
+ case Str_kind:
+ return append_fstring_unicode(writer, e->v.Str.s);
+ case JoinedStr_kind:
+ return append_joinedstr(writer, e, is_format_spec);
+ case FormattedValue_kind:
+ return append_formattedvalue(writer, e, is_format_spec);
+ default:
+ PyErr_SetString(PyExc_SystemError,
+ "unknown expression kind inside f-string");
+ return -1;
+ }
+}
+
+/* Build body separately to enable wrapping the entire stream of Strs,
+ Constants and FormattedValues in one opening and one closing quote. */
+static PyObject *
+build_fstring_body(asdl_seq *values, bool is_format_spec)
+{
+ Py_ssize_t i, value_count;
+ _PyUnicodeWriter body_writer;
+ _PyUnicodeWriter_Init(&body_writer);
+ body_writer.min_length = 256;
+ body_writer.overallocate = 1;
+
+ value_count = asdl_seq_LEN(values) - 1;
+ assert(value_count >= 0);
+ for (i = 0; i <= value_count; ++i) {
+ if (-1 == append_fstring_element(&body_writer,
+ (expr_ty)asdl_seq_GET(values, i),
+ is_format_spec
+ )) {
+ _PyUnicodeWriter_Dealloc(&body_writer);
+ return NULL;
+ }
+ }
+
+ return _PyUnicodeWriter_Finish(&body_writer);
+}
+
+static int
+append_joinedstr(_PyUnicodeWriter *writer, expr_ty e, bool is_format_spec)
+{
+ int result = -1;
+ PyObject *body = build_fstring_body(e->v.JoinedStr.values, is_format_spec);
+ if (!body) {
+ return -1;
+ }
+
+ if (!is_format_spec) {
+ if (-1 != append_charp(writer, "f") &&
+ -1 != append_repr(writer, body))
+ {
+ result = 0;
+ }
+ }
+ else {
+ result = _PyUnicodeWriter_WriteStr(writer, body);
+ }
+ Py_DECREF(body);
+ return result;
+}
+
+static int
+append_formattedvalue(_PyUnicodeWriter *writer, expr_ty e, bool is_format_spec)
+{
+ char *conversion;
+ char *outer_brace = "{";
+ PyObject *temp_fv_str = expr_as_unicode(e->v.FormattedValue.value, true);
+ if (!temp_fv_str) {
+ return -1;
+ }
+ if (PyUnicode_Find(temp_fv_str, _str_open_br, 0, 1, 1) == 0) {
+ /* Expression starts with a brace, split it with a space from the outer
+ one. */
+ outer_brace = "{ ";
+ }
+ if (-1 == append_charp(writer, outer_brace)) {
+ Py_DECREF(temp_fv_str);
+ return -1;
+ }
+ if (-1 == _PyUnicodeWriter_WriteStr(writer, temp_fv_str)) {
+ Py_DECREF(temp_fv_str);
+ return -1;
+ }
+ Py_DECREF(temp_fv_str);
+
+ if (e->v.FormattedValue.conversion > 0) {
+ switch (e->v.FormattedValue.conversion) {
+ case 97:
+ conversion = "!a";
+ break;
+ case 114:
+ conversion = "!r";
+ break;
+ case 115:
+ conversion = "!s";
+ break;
+ default:
+ PyErr_SetString(PyExc_SystemError,
+ "unknown f-value conversion kind");
+ return -1;
+ }
+ if (-1 == append_charp(writer, conversion)) {
+ return -1;
+ }
+ }
+ if (e->v.FormattedValue.format_spec > 0) {
+ if (-1 == _PyUnicodeWriter_WriteASCIIString(writer, ":", 1) ||
+ -1 == append_fstring_element(writer,
+ e->v.FormattedValue.format_spec,
+ true
+ ))
+ {
+ return -1;
+ }
+ }
+ return append_charp(writer, "}");
+}
+
+static int
+append_ast_attribute(_PyUnicodeWriter *writer, expr_ty e)
+{
+ const char *period;
+ if (-1 == append_ast_expr(writer, e->v.Attribute.value, false)) {
+ return -1;
+ }
+
+ /* Special case: integers require a space for attribute access to be
+ unambiguous. Floats and complex numbers don't but work with it, too. */
+ if (e->v.Attribute.value->kind == Num_kind ||
+ e->v.Attribute.value->kind == Constant_kind)
+ {
+ period = " .";
+ }
+ else {
+ period = ".";
+ }
+ if (-1 == append_charp(writer, period)) {
+ return -1;
+ }
+
+ return _PyUnicodeWriter_WriteStr(writer, e->v.Attribute.attr);
+}
+
+static int
+append_ast_simple_slice(_PyUnicodeWriter *writer, slice_ty slice)
+{
+ if (slice->v.Slice.lower) {
+ if (-1 == append_ast_expr(writer, slice->v.Slice.lower, false)) {
+ return -1;
+ }
+ }
+
+ if (-1 == append_charp(writer, ":")) {
+ return -1;
+ }
+
+ if (slice->v.Slice.upper) {
+ if (-1 == append_ast_expr(writer, slice->v.Slice.upper, false)) {
+ return -1;
+ }
+ }
+
+ if (slice->v.Slice.step) {
+ if (-1 == append_charp(writer, ":")) {
+ return -1;
+ }
+ if (-1 == append_ast_expr(writer, slice->v.Slice.step, false)) {
+ return -1;
+ }
+ }
+ return 0;
+}
+
+static int
+append_ast_ext_slice(_PyUnicodeWriter *writer, slice_ty slice)
+{
+ Py_ssize_t i, dims_count;
+ dims_count = asdl_seq_LEN(slice->v.ExtSlice.dims);
+ for (i = 0; i < dims_count; i++) {
+ if (i > 0 && -1 == append_charp(writer, ", ")) {
+ return -1;
+ }
+ if (-1 == append_ast_expr(writer,
+ (expr_ty)asdl_seq_GET(slice->v.ExtSlice.dims, i),
+ false)) {
+ return -1;
+ }
+ }
+ return 0;
+}
+
+static int
+append_ast_slice(_PyUnicodeWriter *writer, slice_ty slice, bool omit_parens)
+{
+ switch(slice->kind) {
+ case Slice_kind:
+ return append_ast_simple_slice(writer, slice);
+ case ExtSlice_kind:
+ return append_ast_ext_slice(writer, slice);
+ case Index_kind:
+ return append_ast_expr(writer, slice->v.Index.value, omit_parens);
+ default:
+ PyErr_SetString(PyExc_SystemError,
+ "unexpected slice kind");
+ return -1;
+ }
+}
+
+static int
+append_ast_subscript(_PyUnicodeWriter *writer, expr_ty e)
+{
+ if (-1 == append_ast_expr(writer, e->v.Subscript.value, false)) {
+ return -1;
+ }
+
+ if (-1 == append_charp(writer, "[")) {
+ return -1;
+ }
+
+ if (-1 == append_ast_slice(writer, e->v.Subscript.slice, true)) {
+ return -1;
+ }
+
+ return append_charp(writer, "]");
+}
+
+static int
+append_ast_starred(_PyUnicodeWriter *writer, expr_ty e)
+{
+ if (-1 == append_charp(writer, "*")) {
+ return -1;
+ }
+
+ return append_ast_expr(writer, e->v.Starred.value, false);
+}
+
+static int
+append_ast_yield(_PyUnicodeWriter *writer, expr_ty e, bool omit_parens)
+{
+ if (!omit_parens && -1 == append_charp(writer, "(")) {
+ return -1;
+ }
+
+ if (-1 == append_charp(writer, e->v.Yield.value ? "yield " : "yield")) {
+ return -1;
+ }
+
+ if (e->v.Yield.value) {
+ if (-1 == append_ast_expr(writer, e->v.Yield.value, false)) {
+ return -1;
+ }
+ }
+ return omit_parens ? 0 : append_charp(writer, ")");
+}
+
+static int
+append_ast_yield_from(_PyUnicodeWriter *writer, expr_ty e, bool omit_parens)
+{
+ if (!omit_parens && -1 == append_charp(writer, "(")) {
+ return -1;
+ }
+
+ if (-1 == append_charp(writer,
+ e->v.YieldFrom.value ? "yield from " : "yield from")) {
+ return -1;
+ }
+
+ if (e->v.YieldFrom.value) {
+ if (-1 == append_ast_expr(writer, e->v.YieldFrom.value, false)) {
+ return -1;
+ }
+ }
+ return omit_parens ? 0 : append_charp(writer, ")");
+}
+
+static int
+append_ast_await(_PyUnicodeWriter *writer, expr_ty e, bool omit_parens)
+{
+ if (!omit_parens && -1 == append_charp(writer, "(")) {
+ return -1;
+ }
+
+ if (-1 == append_charp(writer, e->v.Await.value ? "await " : "await")) {
+ return -1;
+ }
+
+ if (e->v.Await.value) {
+ if (-1 == append_ast_expr(writer, e->v.Await.value, false)) {
+ return -1;
+ }
+ }
+ return omit_parens ? 0 : append_charp(writer, ")");
+}
+
+static int
+append_ast_expr(_PyUnicodeWriter *writer, expr_ty e, bool omit_parens)
+{
+ switch (e->kind) {
+ case BoolOp_kind:
+ return append_ast_boolop(writer, e, omit_parens);
+ case BinOp_kind:
+ return append_ast_binop(writer, e, omit_parens);
+ case UnaryOp_kind:
+ return append_ast_unaryop(writer, e, omit_parens);
+ case Lambda_kind:
+ return append_ast_lambda(writer, e, omit_parens);
+ case IfExp_kind:
+ return append_ast_ifexp(writer, e, omit_parens);
+ case Dict_kind:
+ return append_ast_dict(writer, e);
+ case Set_kind:
+ return append_ast_set(writer, e);
+ case GeneratorExp_kind:
+ return append_ast_genexp(writer, e, omit_parens);
+ case ListComp_kind:
+ return append_ast_listcomp(writer, e);
+ case SetComp_kind:
+ return append_ast_setcomp(writer, e);
+ case DictComp_kind:
+ return append_ast_dictcomp(writer, e);
+ case Yield_kind:
+ return append_ast_yield(writer, e, omit_parens);
+ case YieldFrom_kind:
+ return append_ast_yield_from(writer, e, omit_parens);
+ case Await_kind:
+ return append_ast_await(writer, e, omit_parens);
+ case Compare_kind:
+ return append_ast_compare(writer, e, omit_parens);
+ case Call_kind:
+ return append_ast_call(writer, e);
+ case Constant_kind:
+ return append_repr(writer, e->v.Constant.value);
+ case Num_kind:
+ return append_repr(writer, e->v.Num.n);
+ case Str_kind:
+ return append_repr(writer, e->v.Str.s);
+ case JoinedStr_kind:
+ return append_joinedstr(writer, e, false);
+ case FormattedValue_kind:
+ return append_formattedvalue(writer, e, false);
+ case Bytes_kind:
+ return append_repr(writer, e->v.Bytes.s);
+ case Ellipsis_kind:
+ return append_charp(writer, "...");
+ case NameConstant_kind:
+ return append_repr(writer, e->v.NameConstant.value);
+ /* The following exprs can be assignment targets. */
+ case Attribute_kind:
+ return append_ast_attribute(writer, e);
+ case Subscript_kind:
+ return append_ast_subscript(writer, e);
+ case Starred_kind:
+ return append_ast_starred(writer, e);
+ case Name_kind:
+ return _PyUnicodeWriter_WriteStr(writer, e->v.Name.id);
+ /* child nodes of List and Tuple will have expr_context set */
+ case List_kind:
+ return append_ast_list(writer, e);
+ case Tuple_kind:
+ return append_ast_tuple(writer, e, omit_parens);
+ default:
+ PyErr_SetString(PyExc_SystemError,
+ "unknown expression kind");
+ return -1;
+ }
+}
+
+static int
+maybe_init_static_strings()
+{
+ if (!_str_open_br &&
+ !(_str_open_br = PyUnicode_InternFromString("{"))) {
+ return -1;
+ }
+ if (!_str_dbl_open_br &&
+ !(_str_dbl_open_br = PyUnicode_InternFromString("{{"))) {
+ return -1;
+ }
+ if (!_str_close_br &&
+ !(_str_close_br = PyUnicode_InternFromString("}"))) {
+ return -1;
+ }
+ if (!_str_dbl_close_br &&
+ !(_str_dbl_close_br = PyUnicode_InternFromString("}}"))) {
+ return -1;
+ }
+ return 0;
+}
+
+static PyObject *
+expr_as_unicode(expr_ty e, bool omit_parens)
+{
+ _PyUnicodeWriter writer;
+ _PyUnicodeWriter_Init(&writer);
+ writer.min_length = 256;
+ writer.overallocate = 1;
+ if (-1 == maybe_init_static_strings() ||
+ -1 == append_ast_expr(&writer, e, omit_parens))
+ {
+ _PyUnicodeWriter_Dealloc(&writer);
+ return NULL;
+ }
+ return _PyUnicodeWriter_Finish(&writer);
+}
+
+PyObject *
+_PyAST_ExprAsUnicode(expr_ty e, bool omit_parens)
+{
+ return expr_as_unicode(e, omit_parens);
+}
diff --git a/Python/compile.c b/Python/compile.c
index 31efc28..3e8323b 100644
--- a/Python/compile.c
+++ b/Python/compile.c
@@ -1700,12 +1700,29 @@ error:
}
static int
+compiler_visit_annexpr(struct compiler *c, expr_ty annotation)
+{
+ PyObject *ann_as_str;
+ ann_as_str = _PyAST_ExprAsUnicode(annotation, 1);
+ if (!ann_as_str) {
+ return 0;
+ }
+ ADDOP_N(c, LOAD_CONST, ann_as_str, consts);
+ return 1;
+}
+
+static int
compiler_visit_argannotation(struct compiler *c, identifier id,
expr_ty annotation, PyObject *names)
{
if (annotation) {
PyObject *mangled;
- VISIT(c, expr, annotation);
+ if (c->c_future->ff_features & CO_FUTURE_ANNOTATIONS) {
+ VISIT(c, annexpr, annotation)
+ }
+ else {
+ VISIT(c, expr, annotation);
+ }
mangled = _Py_Mangle(c->u->u_private, id);
if (!mangled)
return 0;
@@ -4688,7 +4705,12 @@ compiler_annassign(struct compiler *c, stmt_ty s)
if (!mangled) {
return 0;
}
- VISIT(c, expr, s->v.AnnAssign.annotation);
+ if (c->c_future->ff_features & CO_FUTURE_ANNOTATIONS) {
+ VISIT(c, annexpr, s->v.AnnAssign.annotation)
+ }
+ else {
+ VISIT(c, expr, s->v.AnnAssign.annotation);
+ }
/* ADDOP_N decrefs its argument */
ADDOP_N(c, STORE_ANNOTATION, mangled, names);
}
diff --git a/Python/future.c b/Python/future.c
index 5cb21ac..53faa6b 100644
--- a/Python/future.c
+++ b/Python/future.c
@@ -42,6 +42,8 @@ future_check_features(PyFutureFeatures *ff, stmt_ty s, PyObject *filename)
ff->ff_features |= CO_FUTURE_BARRY_AS_BDFL;
} else if (strcmp(feature, FUTURE_GENERATOR_STOP) == 0) {
ff->ff_features |= CO_FUTURE_GENERATOR_STOP;
+ } else if (strcmp(feature, FUTURE_ANNOTATIONS) == 0) {
+ ff->ff_features |= CO_FUTURE_ANNOTATIONS;
} else if (strcmp(feature, "braces") == 0) {
PyErr_SetString(PyExc_SyntaxError,
"not a chance");