diff -Nru cython-0.26.1/2to3-fixers.txt cython-0.29.14/2to3-fixers.txt --- cython-0.26.1/2to3-fixers.txt 2015-09-10 16:25:36.000000000 +0000 +++ cython-0.29.14/2to3-fixers.txt 1970-01-01 00:00:00.000000000 +0000 @@ -1 +0,0 @@ -lib2to3.fixes.fix_unicode diff -Nru cython-0.26.1/bin/cython_freeze cython-0.29.14/bin/cython_freeze --- cython-0.26.1/bin/cython_freeze 2016-12-10 15:41:15.000000000 +0000 +++ cython-0.29.14/bin/cython_freeze 2018-09-22 14:18:56.000000000 +0000 @@ -3,7 +3,7 @@ Create a C file for embedding one or more Cython source files. Requires Cython 0.11.2 (or perhaps newer). -See Demos/freeze/README.txt for more details. +See Demos/freeze/README.rst for more details. """ from __future__ import print_function diff -Nru cython-0.26.1/CHANGES.rst cython-0.29.14/CHANGES.rst --- cython-0.26.1/CHANGES.rst 2017-08-29 06:15:21.000000000 +0000 +++ cython-0.29.14/CHANGES.rst 2019-11-01 14:13:39.000000000 +0000 @@ -2,6 +2,1021 @@ Cython Changelog ================ +0.29.14 (2019-11-01) +==================== + +Bugs fixed +---------- + +* The generated code failed to initialise the ``tp_print`` slot in CPython 3.8. + Patches by Pablo Galindo and Orivej Desh (Github issues #3171, #3201). + +* ``?`` for ``bool`` was missing from the supported NumPy dtypes. + Patch by Max Klein. (Github issue #2675) + +* ``await`` was not allowed inside of f-strings. + Patch by Dmitro Getz. (Github issue #2877) + +* Coverage analysis failed for projects where the code resides in separate + source sub-directories. + Patch by Antonio Valentino. (Github issue #1985) + +* An incorrect compiler warning was fixed in automatic C++ string conversions. + Patch by Gerion Entrup. (Github issue #3108) + +* Error reports in the Jupyter notebook showed unhelpful stack traces. + Patch by Matthew Edwards (Github issue #3196). + +* ``Python.h`` is now also included explicitly from ``public`` header files. + (Github issue #3133). + +* Distutils builds with ``--parallel`` did not work when using Cython's + deprecated ``build_ext`` command. + Patch by Alphadelta14 (Github issue #3187). + +Other changes +------------- + +* The ``PyMemoryView_*()`` C-API is available in ``cpython.memoryview``. + Patch by Nathan Manville. (Github issue #2541) + + +0.29.13 (2019-07-26) +==================== + +Bugs fixed +---------- + +* A reference leak for ``None`` was fixed when converting a memoryview + to a Python object. (Github issue #3023) + +* The declaration of ``PyGILState_STATE`` in ``cpython.pystate`` was unusable. + Patch by Kirill Smelkov. (Github issue #2997) + + +Other changes +------------- + +* The declarations in ``posix.mman`` were extended. + Patches by Kirill Smelkov. (Github issues #2893, #2894, #3012) + + +0.29.12 (2019-07-07) +==================== + +Bugs fixed +---------- + +* Fix compile error in CPython 3.8b2 regarding the ``PyCode_New()`` signature. + (Github issue #3031) + +* Fix a C compiler warning about a missing ``int`` downcast. + (Github issue #3028) + +* Fix reported error positions of undefined builtins and constants. + Patch by Orivej Desh. (Github issue #3030) + +* A 32 bit issue in the Pythran support was resolved. + Patch by Serge Guelton. (Github issue #3032) + + +0.29.11 (2019-06-30) +==================== + +Bugs fixed +---------- + +* Fix compile error in CPython 3.8b2 regarding the ``PyCode_New()`` signature. + Patch by Nick Coghlan. (Github issue #3009) + +* Invalid C code generated for lambda functions in cdef methods. + Patch by Josh Tobin. (Github issue #2967) + +* Support slice handling in newer Pythran versions. + Patch by Serge Guelton. (Github issue #2989) + +* A reference leak in power-of-2 calculation was fixed. + Patch by Sebastian Berg. (Github issue #3022) + +* The search order for include files was changed. Previously it was + ``include_directories``, ``Cython/Includes``, ``sys.path``. Now it is + ``include_directories``, ``sys.path``, ``Cython/Includes``. This was done to + allow third-party ``*.pxd`` files to override the ones in Cython. + Original patch by Matti Picus. (Github issue #2905) + +* Setting ``language_level=2`` in a file did not work if ``language_level=3`` + was enabled globally before. + Patch by Jeroen Demeyer. (Github issue #2791) + + +0.29.10 (2019-06-02) +==================== + +Bugs fixed +---------- + +* Fix compile errors in CPython 3.8b1 due to the new "tp_vectorcall" slots. + (Github issue #2976) + + +0.29.9 (2019-05-29) +=================== + +Bugs fixed +---------- + +* Fix a crash regression in 0.29.8 when creating code objects fails. + +* Remove an incorrect cast when using true-division in C++ operations. + (Github issue #1950) + + +0.29.8 (2019-05-28) +=================== + +Bugs fixed +---------- + +* C compile errors with CPython 3.8 were resolved. + Patch by Marcel Plch. (Github issue #2938) + +* Python tuple constants that compare equal but have different item + types could incorrectly be merged into a single constant. + (Github issue #2919) + +* Non-ASCII characters in unprefixed strings could crash the compiler when + used with language level ``3str``. + +* Starred expressions in %-formatting tuples could fail to compile for + unicode strings. (Github issue #2939) + +* Passing Python class references through ``cython.inline()`` was broken. + (Github issue #2936) + + +0.29.7 (2019-04-14) +=================== + +Bugs fixed +---------- + +* Crash when the shared Cython config module gets unloaded and another Cython + module reports an exceptions. Cython now makes sure it keeps an owned reference + to the module. + (Github issue #2885) + +* Resolved a C89 compilation problem when enabling the fast-gil sharing feature. + +* Coverage reporting did not include the signature line of ``cdef`` functions. + (Github issue #1461) + +* Casting a GIL-requiring function into a nogil function now issues a warning. + (Github issue #2879) + +* Generators and coroutines were missing their return type annotation. + (Github issue #2884) + + +0.29.6 (2019-02-27) +=================== + +Bugs fixed +---------- + +* Fix a crash when accessing the ``__kwdefaults__`` special attribute of + fused functions. (Github issue #1470) + +* Fix the parsing of buffer format strings that contain numeric sizes, which + could lead to incorrect input rejections. (Github issue #2845) + +* Avoid a C #pragma in old gcc versions that was only added in GCC 4.6. + Patch by Michael Anselmi. (Github issue #2838) + +* Auto-encoding of Unicode strings to UTF-8 C/C++ strings failed in Python 3, + even though the default encoding there is UTF-8. + (Github issue #2819) + + +0.29.5 (2019-02-09) +=================== + +Bugs fixed +---------- + +* Crash when defining a Python subclass of an extension type and repeatedly calling + a cpdef method on it. (Github issue #2823) + +* Compiler crash when ``prange()`` loops appear inside of with-statements. + (Github issue #2780) + +* Some C compiler warnings were resolved. + Patches by Christoph Gohlke. (Github issues #2815, #2816, #2817, #2822) + +* Python conversion of C++ enums failed in 0.29. + Patch by Orivej Desh. (Github issue #2767) + + +0.29.4 (2019-02-01) +=================== + +Bugs fixed +---------- + +* Division of numeric constants by a runtime value of 0 could fail to raise a + ``ZeroDivisionError``. (Github issue #2820) + + +0.29.3 (2019-01-19) +=================== + +Bugs fixed +---------- + +* Some C code for memoryviews was generated in a non-deterministic order. + Patch by Martijn van Steenbergen. (Github issue #2779) + +* C89 compatibility was accidentally lost since 0.28. + Patches by gastineau and true-pasky. (Github issues #2778, #2801) + +* A C compiler cast warning was resolved. + Patch by Michael Buesch. (Github issue #2774) + +* An compilation failure with complex numbers under MSVC++ was resolved. + (Github issue #2797) + +* Coverage reporting could fail when modules were moved around after the build. + Patch by Wenjun Si. (Github issue #2776) + + +0.29.2 (2018-12-14) +=================== + +Bugs fixed +---------- + +* The code generated for deduplicated constants leaked some references. + (Github issue #2750) + +* The declaration of ``sigismember()`` in ``libc.signal`` was corrected. + (Github issue #2756) + +* Crashes in compiler and test runner were fixed. + (Github issue #2736, #2755) + +* A C compiler warning about an invalid safety check was resolved. + (Github issue #2731) + + +0.29.1 (2018-11-24) +=================== + +Bugs fixed +---------- + +* Extensions compiled with MinGW-64 under Windows could misinterpret integer + objects larger than 15 bit and return incorrect results. + (Github issue #2670) + +* Cython no longer requires the source to be writable when copying its data + into a memory view slice. + Patch by Andrey Paramonov. (Github issue #2644) + +* Line tracing of ``try``-statements generated invalid C code. + (Github issue #2274) + +* When using the ``warn.undeclared`` directive, Cython's own code generated + warnings that are now fixed. + Patch by Nicolas Pauss. (Github issue #2685) + +* Cython's memoryviews no longer require strides for setting the shape field + but only the ``PyBUF_ND`` flag to be set. + Patch by John Kirkham. (Github issue #2716) + +* Some C compiler warnings about unused memoryview code were fixed. + Patch by Ho Cheuk Ting. (Github issue #2588) + +* A C compiler warning about implicit signed/unsigned conversion was fixed. + (Github issue #2729) + +* Assignments to C++ references returned by ``operator[]`` could fail to compile. + (Github issue #2671) + +* The power operator and the support for NumPy math functions were fixed + in Pythran expressions. + Patch by Serge Guelton. (Github issues #2702, #2709) + +* Signatures with memory view arguments now show the expected type + when embedded in docstrings. + Patch by Matthew Chan and Benjamin Weigel. (Github issue #2634) + +* Some ``from ... cimport ...`` constructs were not correctly considered + when searching modified dependencies in ``cythonize()`` to decide + whether to recompile a module. + Patch by Kryštof Pilnáček. (Github issue #2638) + +* A struct field type in the ``cpython.array`` declarations was corrected. + Patch by John Kirkham. (Github issue #2712) + + +0.29 (2018-10-14) +================= + +Features added +-------------- + +* PEP-489 multi-phase module initialisation has been enabled again. Module + reloads in other subinterpreters raise an exception to prevent corruption + of the static module state. + +* A set of ``mypy`` compatible PEP-484 declarations were added for Cython's C data + types to integrate with static analysers in typed Python code. They are available + in the ``Cython/Shadow.pyi`` module and describe the types in the special ``cython`` + module that can be used for typing in Python code. + Original patch by Julian Gethmann. (Github issue #1965) + +* Memoryviews are supported in PEP-484/526 style type declarations. + (Github issue #2529) + +* ``@cython.nogil`` is supported as a C-function decorator in Python code. + (Github issue #2557) + +* Raising exceptions from nogil code will automatically acquire the GIL, instead + of requiring an explicit ``with gil`` block. + +* C++ functions can now be declared as potentially raising both C++ and Python + exceptions, so that Cython can handle both correctly. + (Github issue #2615) + +* ``cython.inline()`` supports a direct ``language_level`` keyword argument that + was previously only available via a directive. + +* A new language level name ``3str`` was added that mostly corresponds to language + level 3, but keeps unprefixed string literals as type 'str' in both Py2 and Py3, + and the builtin 'str' type unchanged. This will become the default in the next + Cython release and is meant to help user code a) transition more easily to this + new default and b) migrate to Python 3 source code semantics without making support + for Python 2.x difficult. + +* In CPython 3.6 and later, looking up globals in the module dict is almost + as fast as looking up C globals. + (Github issue #2313) + +* For a Python subclass of an extension type, repeated method calls to non-overridden + cpdef methods can avoid the attribute lookup in Py3.6+, which makes them 4x faster. + (Github issue #2313) + +* (In-)equality comparisons of objects to integer literals are faster. + (Github issue #2188) + +* Some internal and 1-argument method calls are faster. + +* Modules that cimport many external extension types from other Cython modules + execute less import requests during module initialisation. + +* Constant tuples and slices are deduplicated and only created once per module. + (Github issue #2292) + +* The coverage plugin considers more C file extensions such as ``.cc`` and ``.cxx``. + (Github issue #2266) + +* The ``cythonize`` command accepts compile time variable values (as set by ``DEF``) + through the new ``-E`` option. + Patch by Jerome Kieffer. (Github issue #2315) + +* ``pyximport`` can import from namespace packages. + Patch by Prakhar Goel. (Github issue #2294) + +* Some missing numpy and CPython C-API declarations were added. + Patch by John Kirkham. (Github issues #2523, #2520, #2537) + +* Declarations for the ``pylifecycle`` C-API functions were added in a new .pxd file + ``cpython.pylifecycle``. + +* The Pythran support was updated to work with the latest Pythran 0.8.7. + Original patch by Adrien Guinet. (Github issue #2600) + +* ``%a`` is included in the string formatting types that are optimised into f-strings. + In this case, it is also automatically mapped to ``%r`` in Python 2.x. + +* New C macro ``CYTHON_HEX_VERSION`` to access Cython's version in the same style as + ``PY_VERSION_HEX``. + +* Constants in ``libc.math`` are now declared as ``const`` to simplify their handling. + +* An additional ``check_size`` clause was added to the ``ctypedef class`` name + specification to allow suppressing warnings when importing modules with + backwards-compatible ``PyTypeObject`` size changes. + Patch by Matti Picus. (Github issue #2627) + +Bugs fixed +---------- + +* The exception handling in generators and coroutines under CPython 3.7 was adapted + to the newly introduced exception stack. Users of Cython 0.28 who want to support + Python 3.7 are encouraged to upgrade to 0.29 to avoid potentially incorrect error + reporting and tracebacks. (Github issue #1958) + +* Crash when importing a module under Stackless Python that was built for CPython. + Patch by Anselm Kruis. (Github issue #2534) + +* 2-value slicing of typed sequences failed if the start or stop index was None. + Patch by Christian Gibson. (Github issue #2508) + +* Multiplied string literals lost their factor when they are part of another + constant expression (e.g. 'x' * 10 + 'y' => 'xy'). + +* String formatting with the '%' operator didn't call the special ``__rmod__()`` + method if the right side is a string subclass that implements it. + (Python issue 28598) + +* The directive ``language_level=3`` did not apply to the first token in the + source file. (Github issue #2230) + +* Overriding cpdef methods did not work in Python subclasses with slots. + Note that this can have a performance impact on calls from Cython code. + (Github issue #1771) + +* Fix declarations of builtin or C types using strings in pure python mode. + (Github issue #2046) + +* Generator expressions and lambdas failed to compile in ``@cfunc`` functions. + (Github issue #459) + +* Global names with ``const`` types were not excluded from star-import assignments + which could lead to invalid C code. + (Github issue #2621) + +* Several internal function signatures were fixed that lead to warnings in gcc-8. + (Github issue #2363) + +* The numpy helper functions ``set_array_base()`` and ``get_array_base()`` + were adapted to the current numpy C-API recommendations. + Patch by Matti Picus. (Github issue #2528) + +* Some NumPy related code was updated to avoid deprecated API usage. + Original patch by jbrockmendel. (Github issue #2559) + +* Several C++ STL declarations were extended and corrected. + Patch by Valentin Valls. (Github issue #2207) + +* C lines of the module init function were unconditionally not reported in + exception stack traces. + Patch by Jeroen Demeyer. (Github issue #2492) + +* When PEP-489 support is enabled, reloading the module overwrote any static + module state. It now raises an exception instead, given that reloading is + not actually supported. + +* Object-returning, C++ exception throwing functions were not checking that + the return value was non-null. + Original patch by Matt Wozniski (Github Issue #2603) + +* The source file encoding detection could get confused if the + ``c_string_encoding`` directive appeared within the first two lines. + (Github issue #2632) + +* Cython generated modules no longer emit a warning during import when the + size of the NumPy array type is larger than what was found at compile time. + Instead, this is assumed to be a backwards compatible change on NumPy side. + +Other changes +------------- + +* Cython now emits a warning when no ``language_level`` (2, 3 or '3str') is set + explicitly, neither as a ``cythonize()`` option nor as a compiler directive. + This is meant to prepare the transition of the default language level from + currently Py2 to Py3, since that is what most new users will expect these days. + The future default will, however, not enforce unicode literals, because this + has proven a major obstacle in the support for both Python 2.x and 3.x. The + next major release is intended to make this change, so that it will parse all + code that does not request a specific language level as Python 3 code, but with + ``str`` literals. The language level 2 will continue to be supported for an + indefinite time. + +* The documentation was restructured, cleaned up and examples are now tested. + The NumPy tutorial was also rewritten to simplify the running example. + Contributed by Gabriel de Marmiesse. (Github issue #2245) + +* Cython compiles less of its own modules at build time to reduce the installed + package size to about half of its previous size. This makes the compiler + slightly slower, by about 5-7%. + + +0.28.6 (2018-11-01) +=================== + +Bugs fixed +---------- + +* Extensions compiled with MinGW-64 under Windows could misinterpret integer + objects larger than 15 bit and return incorrect results. + (Github issue #2670) + +* Multiplied string literals lost their factor when they are part of another + constant expression (e.g. 'x' * 10 + 'y' => 'xy'). + + +0.28.5 (2018-08-03) +=================== + +Bugs fixed +---------- + +* The discouraged usage of GCC's attribute ``optimize("Os")`` was replaced by the + similar attribute ``cold`` to reduce the code impact of the module init functions. + (Github issue #2494) + +* A reference leak in Py2.x was fixed when comparing str to unicode for equality. + + +0.28.4 (2018-07-08) +=================== + +Bugs fixed +---------- + +* Reallowing ``tp_clear()`` in a subtype of an ``@no_gc_clear`` extension type + generated an invalid C function call to the (non-existent) base type implementation. + (Github issue #2309) + +* Exception catching based on a non-literal (runtime) tuple could fail to match the + exception. (Github issue #2425) + +* Compile fix for CPython 3.7.0a2. (Github issue #2477) + + +0.28.3 (2018-05-27) +=================== + +Bugs fixed +---------- + +* Set iteration was broken in non-CPython since 0.28. + +* ``UnicodeEncodeError`` in Py2 when ``%s`` formatting is optimised for + unicode strings. (Github issue #2276) + +* Work around a crash bug in g++ 4.4.x by disabling the size reduction setting + of the module init function in this version. (Github issue #2235) + +* Crash when exceptions occur early during module initialisation. + (Github issue #2199) + + +0.28.2 (2018-04-13) +=================== + +Features added +-------------- + +* ``abs()`` is faster for Python long objects. + +* The C++11 methods ``front()`` and ``end()`` were added to the declaration of + ``libcpp.string``. Patch by Alex Huszagh. (Github issue #2123) + +* The C++11 methods ``reserve()`` and ``bucket_count()`` are declared for + ``libcpp.unordered_map``. Patch by Valentin Valls. (Github issue #2168) + +Bugs fixed +---------- + +* The copy of a read-only memoryview was considered read-only as well, whereas + a common reason to copy a read-only view is to make it writable. The result + of the copying is now a writable buffer by default. + (Github issue #2134) + +* The ``switch`` statement generation failed to apply recursively to the body of + converted if-statements. + +* ``NULL`` was sometimes rejected as exception return value when the returned + type is a fused pointer type. + Patch by Callie LeFave. (Github issue #2177) + +* Fixed compatibility with PyPy 5.11. + Patch by Matti Picus. (Github issue #2165) + +Other changes +------------- + +* The NumPy tutorial was rewritten to use memoryviews instead of the older + buffer declaration syntax. + Contributed by Gabriel de Marmiesse. (Github issue #2162) + + +0.28.1 (2018-03-18) +=================== + +Bugs fixed +---------- + +* ``PyFrozenSet_New()`` was accidentally used in PyPy where it is missing + from the C-API. + +* Assignment between some C++ templated types were incorrectly rejected + when the templates mix ``const`` with ``ctypedef``. + (Github issue #2148) + +* Undeclared C++ no-args constructors in subclasses could make the compilation + fail if the base class constructor was declared without ``nogil``. + (Github issue #2157) + +* Bytes %-formatting inferred ``basestring`` (bytes or unicode) as result type + in some cases where ``bytes`` would have been safe to infer. + (Github issue #2153) + +* ``None`` was accidentally disallowed as typed return value of ``dict.pop()``. + (Github issue #2152) + + +0.28 (2018-03-13) +================= + +Features added +-------------- + +* Cdef classes can now multiply inherit from ordinary Python classes. + (The primary base must still be a c class, possibly ``object``, and + the other bases must *not* be cdef classes.) + +* Type inference is now supported for Pythran compiled NumPy expressions. + Patch by Nils Braun. (Github issue #1954) + +* The ``const`` modifier can be applied to memoryview declarations to allow + read-only buffers as input. (Github issues #1605, #1869) + +* C code in the docstring of a ``cdef extern`` block is copied verbatimly + into the generated file. + Patch by Jeroen Demeyer. (Github issue #1915) + +* When compiling with gcc, the module init function is now tuned for small + code size instead of whatever compile flags were provided externally. + Cython now also disables some code intensive optimisations in that function + to further reduce the code size. (Github issue #2102) + +* Decorating an async coroutine with ``@cython.iterable_coroutine`` changes its + type at compile time to make it iterable. While this is not strictly in line + with PEP-492, it improves the interoperability with old-style coroutines that + use ``yield from`` instead of ``await``. + +* The IPython magic has preliminary support for JupyterLab. + (Github issue #1775) + +* The new TSS C-API in CPython 3.7 is supported and has been backported. + Patch by Naotoshi Seo. (Github issue #1932) + +* Cython knows the new ``Py_tss_t`` type defined in PEP-539 and automatically + initialises variables declared with that type to ``Py_tss_NEEDS_INIT``, + a value which cannot be used outside of static assignments. + +* The set methods ``.remove()`` and ``.discard()`` are optimised. + Patch by Antoine Pitrou. (Github issue #2042) + +* ``dict.pop()`` is optimised. + Original patch by Antoine Pitrou. (Github issue #2047) + +* Iteration over sets and frozensets is optimised. + (Github issue #2048) + +* Safe integer loops (< range(2^30)) are automatically optimised into C loops. + +* ``alist.extend([a,b,c])`` is optimised into sequential ``list.append()`` calls + for short literal sequences. + +* Calls to builtin methods that are not specifically optimised into C-API calls + now use a cache that avoids repeated lookups of the underlying C function. + (Github issue #2054) + +* Single argument function calls can avoid the argument tuple creation in some cases. + +* Some redundant extension type checks are avoided. + +* Formatting C enum values in f-strings is faster, as well as some other special cases. + +* String formatting with the '%' operator is optimised into f-strings in simple cases. + +* Subscripting (item access) is faster in some cases. + +* Some ``bytearray`` operations have been optimised similar to ``bytes``. + +* Some PEP-484/526 container type declarations are now considered for + loop optimisations. + +* Indexing into memoryview slices with ``view[i][j]`` is now optimised into + ``view[i, j]``. + +* Python compatible ``cython.*`` types can now be mixed with type declarations + in Cython syntax. + +* Name lookups in the module and in classes are faster. + +* Python attribute lookups on extension types without instance dict are faster. + +* Some missing signals were added to ``libc/signal.pxd``. + Patch by Jeroen Demeyer. (Github issue #1914) + +* The warning about repeated extern declarations is now visible by default. + (Github issue #1874) + +* The exception handling of the function types used by CPython's type slot + functions was corrected to match the de-facto standard behaviour, so that + code that uses them directly benefits from automatic and correct exception + propagation. Patch by Jeroen Demeyer. (Github issue #1980) + +* Defining the macro ``CYTHON_NO_PYINIT_EXPORT`` will prevent the module init + function from being exported as symbol, e.g. when linking modules statically + in an embedding setup. Patch by AraHaan. (Github issue #1944) + +Bugs fixed +---------- + +* If a module name is explicitly provided for an ``Extension()`` that is compiled + via ``cythonize()``, it was previously ignored and replaced by the source file + name. It can now be used to override the target module name, e.g. for compiling + prefixed accelerator modules from Python files. (Github issue #2038) + +* The arguments of the ``num_threads`` parameter of parallel sections + were not sufficiently validated and could lead to invalid C code. + (Github issue #1957) + +* Catching exceptions with a non-trivial exception pattern could call into + CPython with a live exception set. This triggered incorrect behaviour + and crashes, especially in CPython 3.7. + +* The signature of the special ``__richcmp__()`` method was corrected to recognise + the type of the first argument as ``self``. It was previously treated as plain + object, but CPython actually guarantees that it always has the correct type. + Note: this can change the semantics of user code that previously relied on + ``self`` being untyped. + +* Some Python 3 exceptions were not recognised as builtins when running Cython + under Python 2. + +* Some async helper functions were not defined in the generated C code when + compiling simple async code. (Github issue #2075) + +* Line tracing did not include generators and coroutines. + (Github issue #1949) + +* C++ declarations for ``unordered_map`` were corrected. + Patch by Michael Schatzow. (Github issue #1484) + +* Iterator declarations in C++ ``deque`` and ``vector`` were corrected. + Patch by Alex Huszagh. (Github issue #1870) + +* The const modifiers in the C++ ``string`` declarations were corrected, together + with the coercion behaviour of string literals into C++ strings. + (Github issue #2132) + +* Some declaration types in ``libc.limits`` were corrected. + Patch by Jeroen Demeyer. (Github issue #2016) + +* ``@cython.final`` was not accepted on Python classes with an ``@cython.cclass`` + decorator. (Github issue #2040) + +* Cython no longer creates useless and incorrect ``PyInstanceMethod`` wrappers for + methods in Python 3. Patch by Jeroen Demeyer. (Github issue #2105) + +* The builtin ``bytearray`` type could not be used as base type of cdef classes. + (Github issue #2106) + +Other changes +------------- + + +0.27.3 (2017-11-03) +=================== + +Bugs fixed +---------- + +* String forward references to extension types like ``@cython.locals(x="ExtType")`` + failed to find the named type. (Github issue #1962) + +* NumPy slicing generated incorrect results when compiled with Pythran. + Original patch by Serge Guelton (Github issue #1946). + +* Fix "undefined reference" linker error for generators on Windows in Py3.3-3.5. + (Github issue #1968) + +* Adapt to recent C-API change of ``PyThreadState`` in CPython 3.7. + +* Fix signature of ``PyWeakref_GetObject()`` API declaration. + Patch by Jeroen Demeyer (Github issue #1975). + + +0.27.2 (2017-10-22) +=================== + +Bugs fixed +---------- + +* Comprehensions could incorrectly be optimised away when they appeared in boolean + test contexts. (Github issue #1920) + +* The special methods ``__eq__``, ``__lt__`` etc. in extension types did not type + their first argument as the type of the class but ``object``. (Github issue #1935) + +* Crash on first lookup of "cline_in_traceback" option during exception handling. + (Github issue #1907) + +* Some nested module level comprehensions failed to compile. + (Github issue #1906) + +* Compiler crash on some complex type declarations in pure mode. + (Github issue #1908) + +* ``std::unordered_map.erase()`` was declared with an incorrect ``void`` return + type in ``libcpp.unordered_map``. (Github issue #1484) + +* Invalid use of C++ ``fallthrough`` attribute before C++11 and similar issue in clang. + (Github issue #1930) + +* Compiler crash on misnamed properties. (Github issue #1905) + + +0.27.1 (2017-10-01) +=================== + +Features added +-------------- + +* The Jupyter magic has a new debug option ``--verbose`` that shows details about + the distutils invocation. Patch by Boris Filippov (Github issue #1881). + +Bugs fixed +---------- + +* Py3 list comprehensions in class bodies resulted in invalid C code. + (Github issue #1889) + +* Modules built for later CPython 3.5.x versions failed to import in 3.5.0/3.5.1. + (Github issue #1880) + +* Deallocating fused types functions and methods kept their GC tracking enabled, + which could potentially lead to recursive deallocation attempts. + +* Crash when compiling in C++ mode with old setuptools versions. + (Github issue #1879) + +* C++ object arguments for the constructor of Cython implemented C++ are now + passed by reference and not by value to allow for non-copyable arguments, such + as ``unique_ptr``. + +* API-exported C++ classes with Python object members failed to compile. + (Github issue #1866) + +* Some issues with the new relaxed exception value handling were resolved. + +* Python classes as annotation types could prevent compilation. + (Github issue #1887) + +* Cython annotation types in Python files could lead to import failures + with a "cython undefined" error. Recognised types are now turned into strings. + +* Coverage analysis could fail to report on extension modules on some platforms. + +* Annotations could be parsed (and rejected) as types even with + ``annotation_typing=False``. + +Other changes +------------- + +* PEP 489 support has been disabled by default to counter incompatibilities with + import setups that try to reload or reinitialise modules. + + +0.27 (2017-09-23) +================= + +Features added +-------------- + +* Extension module initialisation follows + `PEP 489 `_ in CPython 3.5+, which + resolves several differences with regard to normal Python modules. This makes + the global names ``__file__`` and ``__path__`` correctly available to module + level code and improves the support for module-level relative imports. + (Github issues #1715, #1753, #1035) + +* Asynchronous generators (`PEP 525 `_) + and asynchronous comprehensions (`PEP 530 `_) + have been implemented. Note that async generators require finalisation support + in order to allow for asynchronous operations during cleanup, which is only + available in CPython 3.6+. All other functionality has been backported as usual. + +* Variable annotations are now parsed according to + `PEP 526 `_. Cython types (e.g. + ``cython.int``) are evaluated as C type declarations and everything else as Python + types. This can be disabled with the directive ``annotation_typing=False``. + Note that most complex PEP-484 style annotations are currently ignored. This will + change in future releases. (Github issue #1850) + +* Extension types (also in pure Python mode) can implement the normal special methods + ``__eq__``, ``__lt__`` etc. for comparisons instead of the low-level ``__richcmp__`` + method. (Github issue #690) + +* New decorator ``@cython.exceptval(x=None, check=False)`` that makes the signature + declarations ``except x``, ``except? x`` and ``except *`` available to pure Python + code. Original patch by Antonio Cuni. (Github issue #1653) + +* Signature annotations are now included in the signature docstring generated by + the ``embedsignature`` directive. Patch by Lisandro Dalcin (Github issue #1781). + +* The gdb support for Python code (``libpython.py``) was updated to the latest + version in CPython 3.7 (git rev 5fe59f8). + +* The compiler tries to find a usable exception return value for cdef functions + with ``except *`` if the returned type allows it. Note that this feature is subject + to safety limitations, so it is still better to provide an explicit declaration. + +* C functions can be assigned to function pointers with a compatible exception + declaration, not only with exact matches. A side-effect is that certain compatible + signature overrides are now allowed and some more mismatches of exception signatures + are now detected and rejected as errors that were not detected before. + +* The IPython/Jupyter magic integration has a new option ``%%cython --pgo`` for profile + guided optimisation. It compiles the cell with PGO settings for the C compiler, + executes it to generate a runtime profile, and then compiles it again using that + profile for C compiler optimisation. Currently only tested with gcc. + +* ``len(memoryview)`` can be used in nogil sections to get the size of the + first dimension of a memory view (``shape[0]``). (Github issue #1733) + +* C++ classes can now contain (properly refcounted) Python objects. + +* NumPy dtype subarrays are now accessible through the C-API. + Patch by Gerald Dalley (Github issue #245). + +* Resolves several issues with PyPy and uses faster async slots in PyPy3. + Patch by Ronan Lamy (Github issues #1871, #1878). + +Bugs fixed +---------- + +* Extension types that were cimported from other Cython modules could disagree + about the order of fused cdef methods in their call table. This could lead + to wrong methods being called and potentially also crashes. The fix required + changes to the ordering of fused methods in the call table, which may break + existing compiled modules that call fused cdef methods across module boundaries, + if these methods were implemented in a different order than they were declared + in the corresponding .pxd file. (Github issue #1873) + +* The exception state handling in generators and coroutines could lead to + exceptions in the caller being lost if an exception was raised and handled + inside of the coroutine when yielding. (Github issue #1731) + +* Loops over ``range(enum)`` were not converted into C for-loops. Note that it + is still recommended to use an explicit cast to a C integer type in this case. + +* Error positions of names (e.g. variables) were incorrectly reported after the + name and not at the beginning of the name. + +* Compile time ``DEF`` assignments were evaluated even when they occur inside of + falsy ``IF`` blocks. (Github issue #1796) + +* Disabling the line tracing from a trace function could fail. + Original patch by Dmitry Trofimov. (Github issue #1769) + +* Several issues with the Pythran integration were resolved. + +* abs(signed int) now returns a signed rather than unsigned int. + (Github issue #1837) + +* Reading ``frame.f_locals`` of a Cython function (e.g. from a debugger or profiler + could modify the module globals. (Github issue #1836) + +* Buffer type mismatches in the NumPy buffer support could leak a reference to the + buffer owner. + +* Using the "is_f_contig" and "is_c_contig" memoryview methods together could leave + one of them undeclared. (Github issue #1872) + +* Compilation failed if the for-in-range loop target was not a variable but a more + complex expression, e.g. an item assignment. (Github issue #1831) + +* Compile time evaluations of (partially) constant f-strings could show incorrect + results. + +* Escape sequences in raw f-strings (``fr'...'``) were resolved instead of passing + them through as expected. + +* Some ref-counting issues in buffer error handling have been resolved. + +Other changes +------------- + +* Type declarations in signature annotations are now parsed according to + `PEP 484 `_ + typing. Only Cython types (e.g. ``cython.int``) and Python builtin types are + currently considered as type declarations. Everything else is ignored, but this + will change in a future Cython release. + (Github issue #1672) + +* The directive ``annotation_typing`` is now ``True`` by default, which enables + parsing type declarations from annotations. + +* This release no longer supports Python 3.2. + 0.26.1 (2017-08-29) =================== @@ -46,6 +1061,8 @@ * Some include directories and dependencies were referenced with their absolute paths in the generated files despite lying within the project directory. +* Failure to compile in Py3.7 due to a modified signature of ``_PyCFunctionFast()`` + 0.26 (2017-07-19) ================= @@ -183,7 +1200,8 @@ * The new METH_FASTCALL calling convention for PyCFunctions is supported in CPython 3.6. See https://bugs.python.org/issue27810 -* Initial support for using Cython modules in Pyston. Patch by Daetalus. +* Initial support for using Cython modules in Pyston. + Patch by Boxiang Sun. * Dynamic Python attributes are allowed on cdef classes if an attribute ``cdef dict __dict__`` is declared in the class. Patch by empyrical. @@ -249,7 +1267,7 @@ * IPython cell magic was lacking a good way to enable Python 3 code semantics. It can now be used as "%%cython -3". -* Follow a recent change in `PEP 492 `_ +* Follow a recent change in `PEP 492 `_ and CPython 3.5.2 that now requires the ``__aiter__()`` method of asynchronous iterators to be a simple ``def`` method instead of an ``async def`` method. @@ -279,12 +1297,12 @@ Features added -------------- -* PEP 498: Literal String Formatting (f-strings). +* `PEP 498 `_: + Literal String Formatting (f-strings). Original patch by Jelle Zijlstra. - https://www.python.org/dev/peps/pep-0498/ -* PEP 515: Underscores as visual separators in number literals. - https://www.python.org/dev/peps/pep-0515/ +* `PEP 515 `_: + Underscores as visual separators in number literals. * Parser was adapted to some minor syntax changes in Py3.6, e.g. https://bugs.python.org/issue9232 @@ -467,11 +1485,11 @@ Features added -------------- -* PEP 492 (async/await) was implemented. - See https://www.python.org/dev/peps/pep-0492/ +* `PEP 492 `_ + (async/await) was implemented. -* PEP 448 (Additional Unpacking Generalizations) was implemented. - See https://www.python.org/dev/peps/pep-0448/ +* `PEP 448 `_ + (Additional Unpacking Generalizations) was implemented. * Support for coverage.py 4.0+ can be enabled by adding the plugin "Cython.Coverage" to the ".coveragerc" config file. @@ -662,9 +1680,9 @@ * Anonymous C tuple types can be declared as (ctype1, ctype2, ...). -* PEP 479: turn accidental StopIteration exceptions that exit generators +* `PEP 479 `_: + turn accidental StopIteration exceptions that exit generators into a RuntimeError, activated with future import "generator_stop". - See https://www.python.org/dev/peps/pep-0479/ * Looping over ``reversed(range())`` is optimised in the same way as ``range()``. Patch by Favian Contreras. @@ -1639,9 +2657,9 @@ * GDB support. http://docs.cython.org/src/userguide/debugging.html -* A new build system with support for inline distutils directives, correct dependency tracking, and parallel compilation. http://wiki.cython.org/enhancements/distutils_preprocessing +* A new build system with support for inline distutils directives, correct dependency tracking, and parallel compilation. https://github.com/cython/cython/wiki/enhancements-distutils_preprocessing -* Support for dynamic compilation at runtime via the new cython.inline function and cython.compile decorator. http://wiki.cython.org/enhancements/inline +* Support for dynamic compilation at runtime via the new cython.inline function and cython.compile decorator. https://github.com/cython/cython/wiki/enhancements-inline * "nogil" blocks are supported when compiling pure Python code by writing "with cython.nogil". diff -Nru cython-0.26.1/Cython/Build/Cythonize.py cython-0.29.14/Cython/Build/Cythonize.py --- cython-0.26.1/Cython/Build/Cythonize.py 2016-12-10 15:41:15.000000000 +0000 +++ cython-0.29.14/Cython/Build/Cythonize.py 2018-12-14 14:27:50.000000000 +0000 @@ -21,13 +21,21 @@ class _FakePool(object): def map_async(self, func, args): - from itertools import imap + try: + from itertools import imap + except ImportError: + imap=map for _ in imap(func, args): pass - def close(self): pass - def terminate(self): pass - def join(self): pass + def close(self): + pass + + def terminate(self): + pass + + def join(self): + pass def parse_directives(option, name, value, parser): @@ -52,6 +60,13 @@ setattr(parser.values, dest, options) +def parse_compile_time_env(option, name, value, parser): + dest = option.dest + old_env = dict(getattr(parser.values, dest, {})) + new_env = Options.parse_compile_time_env(value, current_settings=old_env) + setattr(parser.values, dest, new_env) + + def find_package_base(path): base_dir, package_path = os.path.split(path) while os.path.isfile(os.path.join(base_dir, '__init__.py')): @@ -85,6 +100,7 @@ exclude_failures=options.keep_going, exclude=options.excludes, compiler_directives=options.directives, + compile_time_env=options.compile_time_env, force=options.force, quiet=options.quiet, **options.options) @@ -136,13 +152,23 @@ from optparse import OptionParser parser = OptionParser(usage='%prog [options] [sources and packages]+') - parser.add_option('-X', '--directive', metavar='NAME=VALUE,...', dest='directives', - type=str, action='callback', callback=parse_directives, default={}, + parser.add_option('-X', '--directive', metavar='NAME=VALUE,...', + dest='directives', default={}, type="str", + action='callback', callback=parse_directives, help='set a compiler directive') - parser.add_option('-s', '--option', metavar='NAME=VALUE', dest='options', - type=str, action='callback', callback=parse_options, default={}, + parser.add_option('-E', '--compile-time-env', metavar='NAME=VALUE,...', + dest='compile_time_env', default={}, type="str", + action='callback', callback=parse_compile_time_env, + help='set a compile time environment variable') + parser.add_option('-s', '--option', metavar='NAME=VALUE', + dest='options', default={}, type="str", + action='callback', callback=parse_options, help='set a cythonize option') - parser.add_option('-3', dest='python3_mode', action='store_true', + parser.add_option('-2', dest='language_level', action='store_const', const=2, default=None, + help='use Python 2 syntax mode by default') + parser.add_option('-3', dest='language_level', action='store_const', const=3, + help='use Python 3 syntax mode by default') + parser.add_option('--3str', dest='language_level', action='store_const', const='3str', help='use Python 3 syntax mode by default') parser.add_option('-a', '--annotate', dest='annotate', action='store_true', help='generate annotated HTML page for source files') @@ -176,8 +202,9 @@ options.build = True if multiprocessing is None: options.parallel = 0 - if options.python3_mode: - options.options['language_level'] = 3 + if options.language_level: + assert options.language_level in (2, 3, '3str') + options.options['language_level'] = options.language_level return options, args diff -Nru cython-0.26.1/Cython/Build/Dependencies.py cython-0.29.14/Cython/Build/Dependencies.py --- cython-0.26.1/Cython/Build/Dependencies.py 2017-08-25 16:06:31.000000000 +0000 +++ cython-0.29.14/Cython/Build/Dependencies.py 2019-06-30 06:50:51.000000000 +0000 @@ -4,8 +4,19 @@ from .. import __version__ import collections -import re, os, sys, time +import contextlib +import hashlib +import os +import shutil +import subprocess +import re, sys, time +import warnings from glob import iglob +from io import open as io_open +from os.path import relpath as _relpath +from distutils.extension import Extension +from distutils.util import strtobool +import zipfile try: import gzip @@ -14,47 +25,21 @@ except ImportError: gzip_open = open gzip_ext = '' -import shutil -import subprocess -import os - -try: - import hashlib -except ImportError: - import md5 as hashlib - -try: - from io import open as io_open -except ImportError: - from codecs import open as io_open try: - from os.path import relpath as _relpath + import zlib + zipfile_compression_mode = zipfile.ZIP_DEFLATED except ImportError: - # Py<2.6 - def _relpath(path, start=os.path.curdir): - if not path: - raise ValueError("no path specified") - start_list = os.path.abspath(start).split(os.path.sep) - path_list = os.path.abspath(path).split(os.path.sep) - i = len(os.path.commonprefix([start_list, path_list])) - rel_list = [os.path.pardir] * (len(start_list)-i) + path_list[i:] - if not rel_list: - return os.path.curdir - return os.path.join(*rel_list) + zipfile_compression_mode = zipfile.ZIP_STORED try: import pythran - PythranAvailable = True except: - PythranAvailable = False - -from distutils.extension import Extension -from distutils.util import strtobool + pythran = None from .. import Utils from ..Utils import (cached_function, cached_method, path_exists, - safe_makedirs, copy_file_to_dir_if_newer, is_package_dir) + safe_makedirs, copy_file_to_dir_if_newer, is_package_dir, replace_suffix) from ..Compiler.Main import Context, CompilationOptions, default_options join_path = cached_function(os.path.join) @@ -126,21 +111,42 @@ @cached_function def file_hash(filename): - path = os.path.normpath(filename.encode("UTF-8")) - prefix = (str(len(path)) + ":").encode("UTF-8") + path = os.path.normpath(filename) + prefix = ('%d:%s' % (len(path), path)).encode("UTF-8") m = hashlib.md5(prefix) - m.update(path) - f = open(filename, 'rb') - try: + with open(path, 'rb') as f: data = f.read(65000) while data: m.update(data) data = f.read(65000) - finally: - f.close() return m.hexdigest() +def update_pythran_extension(ext): + if pythran is None: + raise RuntimeError("You first need to install Pythran to use the np_pythran directive.") + try: + pythran_ext = pythran.config.make_extension(python=True) + except TypeError: # older pythran version only + pythran_ext = pythran.config.make_extension() + + ext.include_dirs.extend(pythran_ext['include_dirs']) + ext.extra_compile_args.extend(pythran_ext['extra_compile_args']) + ext.extra_link_args.extend(pythran_ext['extra_link_args']) + ext.define_macros.extend(pythran_ext['define_macros']) + ext.undef_macros.extend(pythran_ext['undef_macros']) + ext.library_dirs.extend(pythran_ext['library_dirs']) + ext.libraries.extend(pythran_ext['libraries']) + ext.language = 'c++' + + # These options are not compatible with the way normal Cython extensions work + for bad_option in ["-fwhole-program", "-fvisibility=hidden"]: + try: + ext.extra_compile_args.remove(bad_option) + except ValueError: + pass + + def parse_list(s): """ >>> parse_list("") @@ -223,7 +229,7 @@ break line = line[1:].lstrip() kind = next((k for k in ("distutils:","cython:") if line.startswith(k)), None) - if not kind is None: + if kind is not None: key, _, value = [s.strip() for s in line[len(kind):].partition('=')] type = distutils_settings.get(key, None) if line.startswith("cython:") and type is None: continue @@ -391,6 +397,10 @@ r"(?:^\s*cimport +([0-9a-zA-Z_.]+(?: *, *[0-9a-zA-Z_.]+)*))|" r"(?:^\s*cdef +extern +from +['\"]([^'\"]+)['\"])|" r"(?:^\s*include +['\"]([^'\"]+)['\"])", re.M) +dependency_after_from_regex = re.compile( + r"(?:^\s+\(([0-9a-zA-Z_., ]*)\)[#\n])|" + r"(?:^\s+([0-9a-zA-Z_., ]*)[#\n])", + re.M) def normalize_existing(base_path, rel_paths): @@ -466,11 +476,8 @@ # Actual parsing is way too slow, so we use regular expressions. # The only catch is that we must strip comments and string # literals ahead of time. - fh = Utils.open_source_file(source_filename, error_handling='ignore') - try: + with Utils.open_source_file(source_filename, error_handling='ignore') as fh: source = fh.read() - finally: - fh.close() distutils_info = DistutilsInfo(source) source, literals = strip_string_literals(source) source = source.replace('\\\n', ' ').replace('\t', ' ') @@ -483,6 +490,13 @@ cimport_from, cimport_list, extern, include = m.groups() if cimport_from: cimports.append(cimport_from) + m_after_from = dependency_after_from_regex.search(source, pos=m.end()) + if m_after_from: + multiline, one_line = m_after_from.groups() + subimports = multiline or one_line + cimports.extend("{0}.{1}".format(cimport_from, s.strip()) + for s in subimports.split(',')) + elif cimport_list: cimports.extend(x.strip() for x in cimport_list.split(",")) elif extern: @@ -579,14 +593,14 @@ pxd_list = [filename[:-4] + '.pxd'] else: pxd_list = [] + # Cimports generates all possible combinations package.module + # when imported as from package cimport module. for module in self.cimports(filename): if module[:7] == 'cython.' or module == 'cython': continue pxd_file = self.find_pxd(module, filename) if pxd_file is not None: pxd_list.append(pxd_file) - elif not self.quiet: - print("%s: cannot find cimported module '%s'" % (filename, module)) return tuple(pxd_list) @cached_method @@ -609,15 +623,32 @@ def newest_dependency(self, filename): return max([self.extract_timestamp(f) for f in self.all_dependencies(filename)]) - def transitive_fingerprint(self, filename, extra=None): + def transitive_fingerprint(self, filename, module, compilation_options): + r""" + Return a fingerprint of a cython file that is about to be cythonized. + + Fingerprints are looked up in future compilations. If the fingerprint + is found, the cythonization can be skipped. The fingerprint must + incorporate everything that has an influence on the generated code. + """ try: m = hashlib.md5(__version__.encode('UTF-8')) m.update(file_hash(filename).encode('UTF-8')) for x in sorted(self.all_dependencies(filename)): if os.path.splitext(x)[1] not in ('.c', '.cpp', '.h'): m.update(file_hash(x).encode('UTF-8')) - if extra is not None: - m.update(str(extra).encode('UTF-8')) + # Include the module attributes that change the compilation result + # in the fingerprint. We do not iterate over module.__dict__ and + # include almost everything here as users might extend Extension + # with arbitrary (random) attributes that would lead to cache + # misses. + m.update(str(( + module.language, + getattr(module, 'py_limited_api', False), + getattr(module, 'np_pythran', False) + )).encode('UTF-8')) + + m.update(compilation_options.get_fingerprint().encode('UTF-8')) return m.hexdigest() except IOError: return None @@ -712,7 +743,8 @@ def create_extension_list(patterns, exclude=None, ctx=None, aliases=None, quiet=False, language=None, exclude_failures=False): if language is not None: - print('Please put "# distutils: language=%s" in your .pyx or .pxd file(s)' % language) + print('Warning: passing language={0!r} to cythonize() is deprecated. ' + 'Instead, put "# distutils: language={0}" in your .pyx or .pxd file(s)'.format(language)) if exclude is None: exclude = [] if patterns is None: @@ -755,11 +787,11 @@ cython_sources = [s for s in pattern.sources if os.path.splitext(s)[1] in ('.py', '.pyx')] if cython_sources: - filepattern = cython_sources[0] - if len(cython_sources) > 1: - print("Warning: Multiple cython sources found for extension '%s': %s\n" - "See http://cython.readthedocs.io/en/latest/src/userguide/sharing_declarations.html " - "for sharing declarations among Cython files." % (pattern.name, cython_sources)) + filepattern = cython_sources[0] + if len(cython_sources) > 1: + print("Warning: Multiple cython sources found for extension '%s': %s\n" + "See http://cython.readthedocs.io/en/latest/src/userguide/sharing_declarations.html " + "for sharing declarations among Cython files." % (pattern.name, cython_sources)) else: # ignore non-cython modules module_list.append(pattern) @@ -778,16 +810,15 @@ for file in nonempty(sorted(extended_iglob(filepattern)), "'%s' doesn't match any files" % filepattern): if os.path.abspath(file) in to_exclude: continue - pkg = deps.package(file) module_name = deps.fully_qualified_name(file) if '*' in name: if module_name in explicit_modules: continue - elif name != module_name: - print("Warning: Extension name '%s' does not match fully qualified name '%s' of '%s'" % ( - name, module_name, file)) + elif name: module_name = name + Utils.raise_error_if_module_name_forbidden(module_name) + if module_name not in seen: try: kwds = deps.distutils_info(file, aliases, base).values @@ -818,26 +849,9 @@ # Create the new extension m, metadata = create_extension(template, kwds) - if np_pythran: - if not PythranAvailable: - raise RuntimeError("You first need to install Pythran to use the np_pythran directive.") - pythran_ext = pythran.config.make_extension() - m.include_dirs.extend(pythran_ext['include_dirs']) - m.extra_compile_args.extend(pythran_ext['extra_compile_args']) - m.extra_link_args.extend(pythran_ext['extra_link_args']) - m.define_macros.extend(pythran_ext['define_macros']) - m.undef_macros.extend(pythran_ext['undef_macros']) - m.library_dirs.extend(pythran_ext['library_dirs']) - m.libraries.extend(pythran_ext['libraries']) - # These options are not compatible with the way normal Cython extensions work - try: - m.extra_compile_args.remove("-fwhole-program") - except ValueError: pass - try: - m.extra_compile_args.remove("-fvisibility=hidden") - except ValueError: pass - m.language = 'c++' - m.np_pythran = np_pythran + m.np_pythran = np_pythran or getattr(m, 'np_pythran', False) + if m.np_pythran: + update_pythran_extension(m) module_list.append(m) # Store metadata (this will be written as JSON in the @@ -845,8 +859,13 @@ module_metadata[module_name] = metadata if file not in m.sources: - # Old setuptools unconditionally replaces .pyx with .c - m.sources.remove(file.rsplit('.')[0] + '.c') + # Old setuptools unconditionally replaces .pyx with .c/.cpp + target_file = os.path.splitext(file)[0] + ('.cpp' if m.language == 'c++' else '.c') + try: + m.sources.remove(target_file) + except ValueError: + # never seen this in the wild, but probably better to warn about this unexpected case + print("Warning: Cython source file not found in sources list, adding %s" % file) m.sources.insert(0, file) seen.add(name) return module_list, module_metadata @@ -859,41 +878,80 @@ Compile a set of source modules into C/C++ files and return a list of distutils Extension objects for them. - As module list, pass either a glob pattern, a list of glob patterns or a list of - Extension objects. The latter allows you to configure the extensions separately - through the normal distutils options. - - When using glob patterns, you can exclude certain module names explicitly - by passing them into the 'exclude' option. - - To globally enable C++ mode, you can pass language='c++'. Otherwise, this - will be determined at a per-file level based on compiler directives. This - affects only modules found based on file names. Extension instances passed - into cythonize() will not be changed. - - For parallel compilation, set the 'nthreads' option to the number of - concurrent builds. - - For a broad 'try to compile' mode that ignores compilation failures and - simply excludes the failed extensions, pass 'exclude_failures=True'. Note - that this only really makes sense for compiling .py files which can also - be used without compilation. - - Additional compilation options can be passed as keyword arguments. + :param module_list: As module list, pass either a glob pattern, a list of glob + patterns or a list of Extension objects. The latter + allows you to configure the extensions separately + through the normal distutils options. + You can also pass Extension objects that have + glob patterns as their sources. Then, cythonize + will resolve the pattern and create a + copy of the Extension for every matching file. + + :param exclude: When passing glob patterns as ``module_list``, you can exclude certain + module names explicitly by passing them into the ``exclude`` option. + + :param nthreads: The number of concurrent builds for parallel compilation + (requires the ``multiprocessing`` module). + + :param aliases: If you want to use compiler directives like ``# distutils: ...`` but + can only know at compile time (when running the ``setup.py``) which values + to use, you can use aliases and pass a dictionary mapping those aliases + to Python strings when calling :func:`cythonize`. As an example, say you + want to use the compiler + directive ``# distutils: include_dirs = ../static_libs/include/`` + but this path isn't always fixed and you want to find it when running + the ``setup.py``. You can then do ``# distutils: include_dirs = MY_HEADERS``, + find the value of ``MY_HEADERS`` in the ``setup.py``, put it in a python + variable called ``foo`` as a string, and then call + ``cythonize(..., aliases={'MY_HEADERS': foo})``. + + :param quiet: If True, Cython won't print error and warning messages during the compilation. + + :param force: Forces the recompilation of the Cython modules, even if the timestamps + don't indicate that a recompilation is necessary. + + :param language: To globally enable C++ mode, you can pass ``language='c++'``. Otherwise, this + will be determined at a per-file level based on compiler directives. This + affects only modules found based on file names. Extension instances passed + into :func:`cythonize` will not be changed. It is recommended to rather + use the compiler directive ``# distutils: language = c++`` than this option. + + :param exclude_failures: For a broad 'try to compile' mode that ignores compilation + failures and simply excludes the failed extensions, + pass ``exclude_failures=True``. Note that this only + really makes sense for compiling ``.py`` files which can also + be used without compilation. + + :param annotate: If ``True``, will produce a HTML file for each of the ``.pyx`` or ``.py`` + files compiled. The HTML file gives an indication + of how much Python interaction there is in + each of the source code lines, compared to plain C code. + It also allows you to see the C/C++ code + generated for each line of Cython code. This report is invaluable when + optimizing a function for speed, + and for determining when to :ref:`release the GIL `: + in general, a ``nogil`` block may contain only "white" code. + See examples in :ref:`determining_where_to_add_types` or + :ref:`primes`. + + :param compiler_directives: Allow to set compiler directives in the ``setup.py`` like this: + ``compiler_directives={'embedsignature': True}``. + See :ref:`compiler-directives`. """ if exclude is None: exclude = [] if 'include_path' not in options: options['include_path'] = ['.'] if 'common_utility_include_dir' in options: - if options.get('cache'): - raise NotImplementedError("common_utility_include_dir does not yet work with caching") safe_makedirs(options['common_utility_include_dir']) - if PythranAvailable: - pythran_options = CompilationOptions(**options); + + if pythran is None: + pythran_options = None + else: + pythran_options = CompilationOptions(**options) pythran_options.cplus = True pythran_options.np_pythran = True - pythran_include_dir = os.path.dirname(pythran.__file__) + c_options = CompilationOptions(**options) cpp_options = CompilationOptions(**options); cpp_options.cplus = True ctx = c_options.create_context() @@ -909,22 +967,33 @@ deps = create_dependency_tree(ctx, quiet=quiet) build_dir = getattr(options, 'build_dir', None) - modules_by_cfile = {} + def copy_to_build_dir(filepath, root=os.getcwd()): + filepath_abs = os.path.abspath(filepath) + if os.path.isabs(filepath): + filepath = filepath_abs + if filepath_abs.startswith(root): + # distutil extension depends are relative to cwd + mod_dir = join_path(build_dir, + os.path.dirname(_relpath(filepath, root))) + copy_once_if_newer(filepath_abs, mod_dir) + + modules_by_cfile = collections.defaultdict(list) to_compile = [] for m in module_list: if build_dir: - root = os.getcwd() # distutil extension depends are relative to cwd - def copy_to_build_dir(filepath, root=root): - filepath_abs = os.path.abspath(filepath) - if os.path.isabs(filepath): - filepath = filepath_abs - if filepath_abs.startswith(root): - mod_dir = join_path(build_dir, - os.path.dirname(_relpath(filepath, root))) - copy_once_if_newer(filepath_abs, mod_dir) for dep in m.depends: copy_to_build_dir(dep) + cy_sources = [ + source for source in m.sources + if os.path.splitext(source)[1] in ('.pyx', '.py')] + if len(cy_sources) == 1: + # normal "special" case: believe the Extension module name to allow user overrides + full_module_name = m.name + else: + # infer FQMN from source files + full_module_name = None + new_sources = [] for source in m.sources: base, ext = os.path.splitext(source) @@ -941,6 +1010,8 @@ # setup for out of place build directory if enabled if build_dir: + if os.path.isabs(c_file): + warnings.warn("build_dir has no effect for absolute source paths") c_file = os.path.join(build_dir, c_file) dir = os.path.dirname(c_file) safe_makedirs_once(dir) @@ -965,17 +1036,15 @@ else: print("Compiling %s because it depends on %s." % (source, dep)) if not force and options.cache: - extra = m.language - fingerprint = deps.transitive_fingerprint(source, extra) + fingerprint = deps.transitive_fingerprint(source, m, options) else: fingerprint = None - to_compile.append((priority, source, c_file, fingerprint, quiet, - options, not exclude_failures, module_metadata.get(m.name))) + to_compile.append(( + priority, source, c_file, fingerprint, quiet, + options, not exclude_failures, module_metadata.get(m.name), + full_module_name)) new_sources.append(c_file) - if c_file not in modules_by_cfile: - modules_by_cfile[c_file] = [m] - else: - modules_by_cfile[c_file].append(m) + modules_by_cfile[c_file].append(m) else: new_sources.append(source) if build_dir: @@ -1091,34 +1160,35 @@ # TODO: Share context? Issue: pyx processing leaks into pxd module @record_results -def cythonize_one(pyx_file, c_file, fingerprint, quiet, options=None, raise_on_failure=True, embedded_metadata=None, progress=""): - from ..Compiler.Main import compile, default_options +def cythonize_one(pyx_file, c_file, fingerprint, quiet, options=None, + raise_on_failure=True, embedded_metadata=None, full_module_name=None, + progress=""): + from ..Compiler.Main import compile_single, default_options from ..Compiler.Errors import CompileError, PyrexError if fingerprint: if not os.path.exists(options.cache): - try: - os.mkdir(options.cache) - except: - if not os.path.exists(options.cache): - raise + safe_makedirs(options.cache) # Cython-generated c files are highly compressible. # (E.g. a compression ratio of about 10 for Sage). - fingerprint_file = join_path( - options.cache, "%s-%s%s" % (os.path.basename(c_file), fingerprint, gzip_ext)) - if os.path.exists(fingerprint_file): + fingerprint_file_base = join_path( + options.cache, "%s-%s" % (os.path.basename(c_file), fingerprint)) + gz_fingerprint_file = fingerprint_file_base + gzip_ext + zip_fingerprint_file = fingerprint_file_base + '.zip' + if os.path.exists(gz_fingerprint_file) or os.path.exists(zip_fingerprint_file): if not quiet: print("%sFound compiled %s in cache" % (progress, pyx_file)) - os.utime(fingerprint_file, None) - g = gzip_open(fingerprint_file, 'rb') - try: - f = open(c_file, 'wb') - try: - shutil.copyfileobj(g, f) - finally: - f.close() - finally: - g.close() + if os.path.exists(gz_fingerprint_file): + os.utime(gz_fingerprint_file, None) + with contextlib.closing(gzip_open(gz_fingerprint_file, 'rb')) as g: + with contextlib.closing(open(c_file, 'wb')) as f: + shutil.copyfileobj(g, f) + else: + os.utime(zip_fingerprint_file, None) + dirname = os.path.dirname(c_file) + with contextlib.closing(zipfile.ZipFile(zip_fingerprint_file)) as z: + for artifact in z.namelist(): + z.extract(artifact, os.path.join(dirname, artifact)) return if not quiet: print("%sCythonizing %s" % (progress, pyx_file)) @@ -1129,7 +1199,7 @@ any_failures = 0 try: - result = compile([pyx_file], options) + result = compile_single(pyx_file, options, full_module_name=full_module_name) if result.num_errors > 0: any_failures = 1 except (EnvironmentError, PyrexError) as e: @@ -1150,15 +1220,21 @@ elif os.path.exists(c_file): os.remove(c_file) elif fingerprint: - f = open(c_file, 'rb') - try: - g = gzip_open(fingerprint_file, 'wb') - try: - shutil.copyfileobj(f, g) - finally: - g.close() - finally: - f.close() + artifacts = list(filter(None, [ + getattr(result, attr, None) + for attr in ('c_file', 'h_file', 'api_file', 'i_file')])) + if len(artifacts) == 1: + fingerprint_file = gz_fingerprint_file + with contextlib.closing(open(c_file, 'rb')) as f: + with contextlib.closing(gzip_open(fingerprint_file + '.tmp', 'wb')) as g: + shutil.copyfileobj(f, g) + else: + fingerprint_file = zip_fingerprint_file + with contextlib.closing(zipfile.ZipFile( + fingerprint_file + '.tmp', 'w', zipfile_compression_mode)) as zip: + for artifact in artifacts: + zip.write(artifact, os.path.basename(artifact)) + os.rename(fingerprint_file + '.tmp', fingerprint_file) def cythonize_one_helper(m): diff -Nru cython-0.26.1/Cython/Build/Inline.py cython-0.29.14/Cython/Build/Inline.py --- cython-0.26.1/Cython/Build/Inline.py 2016-12-10 15:41:15.000000000 +0000 +++ cython-0.29.14/Cython/Build/Inline.py 2019-05-27 19:37:21.000000000 +0000 @@ -90,7 +90,7 @@ elif 'numpy' in sys.modules and isinstance(arg, sys.modules['numpy'].ndarray): return 'numpy.ndarray[numpy.%s_t, ndim=%s]' % (arg.dtype.name, arg.ndim) else: - for base_type in py_type.mro(): + for base_type in py_type.__mro__: if base_type.__module__ in ('__builtin__', 'builtins'): return 'object' module = context.find_module(base_type.__module__, need_pxd=False) @@ -136,8 +136,10 @@ else: print("Couldn't find %r" % symbol) -def cython_inline(code, get_type=unsafe_type, lib_dir=os.path.join(get_cython_cache_dir(), 'inline'), - cython_include_dirs=None, force=False, quiet=False, locals=None, globals=None, **kwds): +def cython_inline(code, get_type=unsafe_type, + lib_dir=os.path.join(get_cython_cache_dir(), 'inline'), + cython_include_dirs=None, cython_compiler_directives=None, + force=False, quiet=False, locals=None, globals=None, language_level=None, **kwds): if get_type is None: get_type = lambda x: 'object' @@ -169,6 +171,11 @@ if not quiet: # Parsing from strings not fully supported (e.g. cimports). print("Could not parse code as a string (to extract unbound symbols).") + + cython_compiler_directives = dict(cython_compiler_directives or {}) + if language_level is not None: + cython_compiler_directives['language_level'] = language_level + cimports = [] for name, arg in list(kwds.items()): if arg is cython_module: @@ -176,7 +183,7 @@ del kwds[name] arg_names = sorted(kwds) arg_sigs = tuple([(get_type(kwds[arg], ctx), arg) for arg in arg_names]) - key = orig_code, arg_sigs, sys.version_info, sys.executable, Cython.__version__ + key = orig_code, arg_sigs, sys.version_info, sys.executable, language_level, Cython.__version__ module_name = "_cython_inline_" + hashlib.md5(_unicode(key).encode('utf-8')).hexdigest() if module_name in sys.modules: @@ -233,7 +240,11 @@ extra_compile_args = cflags) if build_extension is None: build_extension = _get_build_extension() - build_extension.extensions = cythonize([extension], include_path=cython_include_dirs or ['.'], quiet=quiet) + build_extension.extensions = cythonize( + [extension], + include_path=cython_include_dirs or ['.'], + compiler_directives=cython_compiler_directives, + quiet=quiet) build_extension.build_temp = os.path.dirname(pyx_file) build_extension.build_lib = lib_dir build_extension.run() diff -Nru cython-0.26.1/Cython/Build/IpythonMagic.py cython-0.29.14/Cython/Build/IpythonMagic.py --- cython-0.26.1/Cython/Build/IpythonMagic.py 2017-08-12 14:06:59.000000000 +0000 +++ cython-0.29.14/Cython/Build/IpythonMagic.py 2019-11-01 14:13:39.000000000 +0000 @@ -14,7 +14,7 @@ Usage ===== -To enable the magics below, execute ``%load_ext cythonmagic``. +To enable the magics below, execute ``%load_ext cython``. ``%%cython`` @@ -52,6 +52,10 @@ import re import sys import time +import copy +import distutils.log +import textwrap + try: reload @@ -83,6 +87,20 @@ from .Dependencies import cythonize +PGO_CONFIG = { + 'gcc': { + 'gen': ['-fprofile-generate', '-fprofile-dir={TEMPDIR}'], + 'use': ['-fprofile-use', '-fprofile-correction', '-fprofile-dir={TEMPDIR}'], + }, + # blind copy from 'configure' script in CPython 3.7 + 'icc': { + 'gen': ['-prof-gen'], + 'use': ['-prof-use'], + } +} +PGO_CONFIG['mingw32'] = PGO_CONFIG['gcc'] + + @magics_class class CythonMagics(Magics): @@ -148,11 +166,15 @@ f.write(cell) if 'pyximport' not in sys.modules or not self._pyximport_installed: import pyximport - pyximport.install(reload_support=True) + pyximport.install() self._pyximport_installed = True if module_name in self._reloads: module = self._reloads[module_name] - reload(module) + # Note: reloading extension modules is not actually supported + # (requires PEP-489 reinitialisation support). + # Don't know why this should ever have worked as it reads here. + # All we really need to do is to update the globals below. + #reload(module) else: __import__(module_name) module = sys.modules[module_name] @@ -161,6 +183,14 @@ @magic_arguments.magic_arguments() @magic_arguments.argument( + '-a', '--annotate', action='store_true', default=False, + help="Produce a colorized HTML version of the source." + ) + @magic_arguments.argument( + '-+', '--cplus', action='store_true', default=False, + help="Output a C++ rather than C file." + ) + @magic_arguments.argument( '-3', dest='language_level', action='store_const', const=3, default=None, help="Select Python 3 syntax." ) @@ -169,6 +199,11 @@ help="Select Python 2 syntax." ) @magic_arguments.argument( + '-f', '--force', action='store_true', default=False, + help="Force the compilation of a new module, even if the source has been " + "previously compiled." + ) + @magic_arguments.argument( '-c', '--compile-args', action='append', default=[], help="Extra flags to pass to compiler via the `extra_compile_args` " "Extension flag (can be specified multiple times)." @@ -203,17 +238,14 @@ "multiple times)." ) @magic_arguments.argument( - '-+', '--cplus', action='store_true', default=False, - help="Output a C++ rather than C file." + '--pgo', dest='pgo', action='store_true', default=False, + help=("Enable profile guided optimisation in the C compiler. " + "Compiles the cell twice and executes it in between to generate a runtime profile.") ) @magic_arguments.argument( - '-f', '--force', action='store_true', default=False, - help="Force the compilation of a new module, even if the source has been " - "previously compiled." - ) - @magic_arguments.argument( - '-a', '--annotate', action='store_true', default=False, - help="Produce a colorized HTML version of the source." + '--verbose', dest='quiet', action='store_false', default=True, + help=("Print debug information like generated .c/.cpp file location " + "and exact gcc/g++ command invoked.") ) @cell_magic def cython(self, line, cell): @@ -235,77 +267,78 @@ %%cython --compile-args=-fopenmp --link-args=-fopenmp ... + + To enable profile guided optimisation, pass the ``--pgo`` option. + Note that the cell itself needs to take care of establishing a suitable + profile when executed. This can be done by implementing the functions to + optimise, and then calling them directly in the same cell on some realistic + training data like this:: + + %%cython --pgo + def critical_function(data): + for item in data: + ... + + # execute function several times to build profile + from somewhere import some_typical_data + for _ in range(100): + critical_function(some_typical_data) + + In Python 3.5 and later, you can distinguish between the profile and + non-profile runs as follows:: + + if "_pgo_" in __name__: + ... # execute critical code here """ args = magic_arguments.parse_argstring(self.cython, line) code = cell if cell.endswith('\n') else cell + '\n' lib_dir = os.path.join(get_ipython_cache_dir(), 'cython') - quiet = True - key = code, line, sys.version_info, sys.executable, cython_version + key = (code, line, sys.version_info, sys.executable, cython_version) if not os.path.exists(lib_dir): os.makedirs(lib_dir) + if args.pgo: + key += ('pgo',) if args.force: # Force a new module name by adding the current time to the # key which is hashed to determine the module name. - key += time.time(), + key += (time.time(),) if args.name: module_name = py3compat.unicode_to_str(args.name) else: module_name = "_cython_magic_" + hashlib.md5(str(key).encode('utf-8')).hexdigest() + html_file = os.path.join(lib_dir, module_name + '.html') module_path = os.path.join(lib_dir, module_name + self.so_ext) have_module = os.path.isfile(module_path) - need_cythonize = not have_module + need_cythonize = args.pgo or not have_module if args.annotate: - html_file = os.path.join(lib_dir, module_name + '.html') if not os.path.isfile(html_file): need_cythonize = True + extension = None if need_cythonize: - c_include_dirs = args.include - c_src_files = list(map(str, args.src)) - if 'numpy' in code: - import numpy - c_include_dirs.append(numpy.get_include()) - pyx_file = os.path.join(lib_dir, module_name + '.pyx') - pyx_file = py3compat.cast_bytes_py2(pyx_file, encoding=sys.getfilesystemencoding()) - with io.open(pyx_file, 'w', encoding='utf-8') as f: - f.write(code) - extension = Extension( - name=module_name, - sources=[pyx_file] + c_src_files, - include_dirs=c_include_dirs, - library_dirs=args.library_dirs, - extra_compile_args=args.compile_args, - extra_link_args=args.link_args, - libraries=args.lib, - language='c++' if args.cplus else 'c', - ) - build_extension = self._get_build_extension() - try: - opts = dict( - quiet=quiet, - annotate=args.annotate, - force=True, - ) - if args.language_level is not None: - assert args.language_level in (2, 3) - opts['language_level'] = args.language_level - elif sys.version_info[0] > 2: - opts['language_level'] = 3 - build_extension.extensions = cythonize([extension], **opts) - except CompileError: - return - - if not have_module: - build_extension.build_temp = os.path.dirname(pyx_file) - build_extension.build_lib = lib_dir - build_extension.run() + extensions = self._cythonize(module_name, code, lib_dir, args, quiet=args.quiet) + if extensions is None: + # Compilation failed and printed error message + return None + assert len(extensions) == 1 + extension = extensions[0] self._code_cache[key] = module_name + if args.pgo: + self._profile_pgo_wrapper(extension, lib_dir) + + try: + self._build_extension(extension, lib_dir, pgo_step_name='use' if args.pgo else None, + quiet=args.quiet) + except distutils.errors.CompileError: + # Build failed and printed error message + return None + module = imp.load_dynamic(module_name, module_path) self._import_all(module) @@ -324,6 +357,129 @@ else: return display.HTML(self.clean_annotated_html(annotated_html)) + def _profile_pgo_wrapper(self, extension, lib_dir): + """ + Generate a .c file for a separate extension module that calls the + module init function of the original module. This makes sure that the + PGO profiler sees the correct .o file of the final module, but it still + allows us to import the module under a different name for profiling, + before recompiling it into the PGO optimised module. Overwriting and + reimporting the same shared library is not portable. + """ + extension = copy.copy(extension) # shallow copy, do not modify sources in place! + module_name = extension.name + pgo_module_name = '_pgo_' + module_name + pgo_wrapper_c_file = os.path.join(lib_dir, pgo_module_name + '.c') + with io.open(pgo_wrapper_c_file, 'w', encoding='utf-8') as f: + f.write(textwrap.dedent(u""" + #include "Python.h" + #if PY_MAJOR_VERSION < 3 + extern PyMODINIT_FUNC init%(module_name)s(void); + PyMODINIT_FUNC init%(pgo_module_name)s(void); /*proto*/ + PyMODINIT_FUNC init%(pgo_module_name)s(void) { + PyObject *sys_modules; + init%(module_name)s(); if (PyErr_Occurred()) return; + sys_modules = PyImport_GetModuleDict(); /* borrowed, no exception, "never" fails */ + if (sys_modules) { + PyObject *module = PyDict_GetItemString(sys_modules, "%(module_name)s"); if (!module) return; + PyDict_SetItemString(sys_modules, "%(pgo_module_name)s", module); + Py_DECREF(module); + } + } + #else + extern PyMODINIT_FUNC PyInit_%(module_name)s(void); + PyMODINIT_FUNC PyInit_%(pgo_module_name)s(void); /*proto*/ + PyMODINIT_FUNC PyInit_%(pgo_module_name)s(void) { + return PyInit_%(module_name)s(); + } + #endif + """ % {'module_name': module_name, 'pgo_module_name': pgo_module_name})) + + extension.sources = extension.sources + [pgo_wrapper_c_file] # do not modify in place! + extension.name = pgo_module_name + + self._build_extension(extension, lib_dir, pgo_step_name='gen') + + # import and execute module code to generate profile + so_module_path = os.path.join(lib_dir, pgo_module_name + self.so_ext) + imp.load_dynamic(pgo_module_name, so_module_path) + + def _cythonize(self, module_name, code, lib_dir, args, quiet=True): + pyx_file = os.path.join(lib_dir, module_name + '.pyx') + pyx_file = py3compat.cast_bytes_py2(pyx_file, encoding=sys.getfilesystemencoding()) + + c_include_dirs = args.include + c_src_files = list(map(str, args.src)) + if 'numpy' in code: + import numpy + c_include_dirs.append(numpy.get_include()) + with io.open(pyx_file, 'w', encoding='utf-8') as f: + f.write(code) + extension = Extension( + name=module_name, + sources=[pyx_file] + c_src_files, + include_dirs=c_include_dirs, + library_dirs=args.library_dirs, + extra_compile_args=args.compile_args, + extra_link_args=args.link_args, + libraries=args.lib, + language='c++' if args.cplus else 'c', + ) + try: + opts = dict( + quiet=quiet, + annotate=args.annotate, + force=True, + ) + if args.language_level is not None: + assert args.language_level in (2, 3) + opts['language_level'] = args.language_level + elif sys.version_info[0] >= 3: + opts['language_level'] = 3 + return cythonize([extension], **opts) + except CompileError: + return None + + def _build_extension(self, extension, lib_dir, temp_dir=None, pgo_step_name=None, quiet=True): + build_extension = self._get_build_extension( + extension, lib_dir=lib_dir, temp_dir=temp_dir, pgo_step_name=pgo_step_name) + old_threshold = None + try: + if not quiet: + old_threshold = distutils.log.set_threshold(distutils.log.DEBUG) + build_extension.run() + finally: + if not quiet and old_threshold is not None: + distutils.log.set_threshold(old_threshold) + + def _add_pgo_flags(self, build_extension, step_name, temp_dir): + compiler_type = build_extension.compiler.compiler_type + if compiler_type == 'unix': + compiler_cmd = build_extension.compiler.compiler_so + # TODO: we could try to call "[cmd] --version" for better insights + if not compiler_cmd: + pass + elif 'clang' in compiler_cmd or 'clang' in compiler_cmd[0]: + compiler_type = 'clang' + elif 'icc' in compiler_cmd or 'icc' in compiler_cmd[0]: + compiler_type = 'icc' + elif 'gcc' in compiler_cmd or 'gcc' in compiler_cmd[0]: + compiler_type = 'gcc' + elif 'g++' in compiler_cmd or 'g++' in compiler_cmd[0]: + compiler_type = 'gcc' + config = PGO_CONFIG.get(compiler_type) + orig_flags = [] + if config and step_name in config: + flags = [f.format(TEMPDIR=temp_dir) for f in config[step_name]] + for extension in build_extension.extensions: + orig_flags.append((extension.extra_compile_args, extension.extra_link_args)) + extension.extra_compile_args = extension.extra_compile_args + flags + extension.extra_link_args = extension.extra_link_args + flags + else: + print("No PGO %s configuration known for C compiler type '%s'" % (step_name, compiler_type), + file=sys.stderr) + return orig_flags + @property def so_ext(self): """The extension suffix for compiled modules.""" @@ -345,7 +501,8 @@ else: _path_created.clear() - def _get_build_extension(self): + def _get_build_extension(self, extension=None, lib_dir=None, temp_dir=None, + pgo_step_name=None, _build_ext=build_ext): self._clear_distutils_mkpath_cache() dist = Distribution() config_files = dist.find_config_files() @@ -354,8 +511,28 @@ except ValueError: pass dist.parse_config_files(config_files) - build_extension = build_ext(dist) + + if not temp_dir: + temp_dir = lib_dir + add_pgo_flags = self._add_pgo_flags + + if pgo_step_name: + base_build_ext = _build_ext + class _build_ext(_build_ext): + def build_extensions(self): + add_pgo_flags(self, pgo_step_name, temp_dir) + base_build_ext.build_extensions(self) + + build_extension = _build_ext(dist) build_extension.finalize_options() + if temp_dir: + temp_dir = py3compat.cast_bytes_py2(temp_dir, encoding=sys.getfilesystemencoding()) + build_extension.build_temp = temp_dir + if lib_dir: + lib_dir = py3compat.cast_bytes_py2(lib_dir, encoding=sys.getfilesystemencoding()) + build_extension.build_lib = lib_dir + if extension is not None: + build_extension.extensions = [extension] return build_extension @staticmethod diff -Nru cython-0.26.1/Cython/Build/Tests/TestCyCache.py cython-0.29.14/Cython/Build/Tests/TestCyCache.py --- cython-0.26.1/Cython/Build/Tests/TestCyCache.py 1970-01-01 00:00:00.000000000 +0000 +++ cython-0.29.14/Cython/Build/Tests/TestCyCache.py 2018-11-24 09:20:06.000000000 +0000 @@ -0,0 +1,106 @@ +import difflib +import glob +import gzip +import os +import tempfile + +import Cython.Build.Dependencies +import Cython.Utils +from Cython.TestUtils import CythonTest + + +class TestCyCache(CythonTest): + + def setUp(self): + CythonTest.setUp(self) + self.temp_dir = tempfile.mkdtemp( + prefix='cycache-test', + dir='TEST_TMP' if os.path.isdir('TEST_TMP') else None) + self.src_dir = tempfile.mkdtemp(prefix='src', dir=self.temp_dir) + self.cache_dir = tempfile.mkdtemp(prefix='cache', dir=self.temp_dir) + + def cache_files(self, file_glob): + return glob.glob(os.path.join(self.cache_dir, file_glob)) + + def fresh_cythonize(self, *args, **kwargs): + Cython.Utils.clear_function_caches() + Cython.Build.Dependencies._dep_tree = None # discard method caches + Cython.Build.Dependencies.cythonize(*args, **kwargs) + + def test_cycache_switch(self): + content1 = 'value = 1\n' + content2 = 'value = 2\n' + a_pyx = os.path.join(self.src_dir, 'a.pyx') + a_c = a_pyx[:-4] + '.c' + + open(a_pyx, 'w').write(content1) + self.fresh_cythonize(a_pyx, cache=self.cache_dir) + self.fresh_cythonize(a_pyx, cache=self.cache_dir) + self.assertEqual(1, len(self.cache_files('a.c*'))) + a_contents1 = open(a_c).read() + os.unlink(a_c) + + open(a_pyx, 'w').write(content2) + self.fresh_cythonize(a_pyx, cache=self.cache_dir) + a_contents2 = open(a_c).read() + os.unlink(a_c) + + self.assertNotEqual(a_contents1, a_contents2, 'C file not changed!') + self.assertEqual(2, len(self.cache_files('a.c*'))) + + open(a_pyx, 'w').write(content1) + self.fresh_cythonize(a_pyx, cache=self.cache_dir) + self.assertEqual(2, len(self.cache_files('a.c*'))) + a_contents = open(a_c).read() + self.assertEqual( + a_contents, a_contents1, + msg='\n'.join(list(difflib.unified_diff( + a_contents.split('\n'), a_contents1.split('\n')))[:10])) + + def test_cycache_uses_cache(self): + a_pyx = os.path.join(self.src_dir, 'a.pyx') + a_c = a_pyx[:-4] + '.c' + open(a_pyx, 'w').write('pass') + self.fresh_cythonize(a_pyx, cache=self.cache_dir) + a_cache = os.path.join(self.cache_dir, os.listdir(self.cache_dir)[0]) + gzip.GzipFile(a_cache, 'wb').write('fake stuff'.encode('ascii')) + os.unlink(a_c) + self.fresh_cythonize(a_pyx, cache=self.cache_dir) + a_contents = open(a_c).read() + self.assertEqual(a_contents, 'fake stuff', + 'Unexpected contents: %s...' % a_contents[:100]) + + def test_multi_file_output(self): + a_pyx = os.path.join(self.src_dir, 'a.pyx') + a_c = a_pyx[:-4] + '.c' + a_h = a_pyx[:-4] + '.h' + a_api_h = a_pyx[:-4] + '_api.h' + open(a_pyx, 'w').write('cdef public api int foo(int x): return x\n') + self.fresh_cythonize(a_pyx, cache=self.cache_dir) + expected = [a_c, a_h, a_api_h] + for output in expected: + self.assertTrue(os.path.exists(output), output) + os.unlink(output) + self.fresh_cythonize(a_pyx, cache=self.cache_dir) + for output in expected: + self.assertTrue(os.path.exists(output), output) + + def test_options_invalidation(self): + hash_pyx = os.path.join(self.src_dir, 'options.pyx') + hash_c = hash_pyx[:-len('.pyx')] + '.c' + + open(hash_pyx, 'w').write('pass') + self.fresh_cythonize(hash_pyx, cache=self.cache_dir, cplus=False) + self.assertEqual(1, len(self.cache_files('options.c*'))) + + os.unlink(hash_c) + self.fresh_cythonize(hash_pyx, cache=self.cache_dir, cplus=True) + self.assertEqual(2, len(self.cache_files('options.c*'))) + + os.unlink(hash_c) + self.fresh_cythonize(hash_pyx, cache=self.cache_dir, cplus=False, show_version=False) + self.assertEqual(2, len(self.cache_files('options.c*'))) + + os.unlink(hash_c) + self.fresh_cythonize(hash_pyx, cache=self.cache_dir, cplus=False, show_version=True) + self.assertEqual(2, len(self.cache_files('options.c*'))) diff -Nru cython-0.26.1/Cython/Build/Tests/TestInline.py cython-0.29.14/Cython/Build/Tests/TestInline.py --- cython-0.26.1/Cython/Build/Tests/TestInline.py 2016-12-10 15:41:15.000000000 +0000 +++ cython-0.29.14/Cython/Build/Tests/TestInline.py 2019-05-27 19:37:21.000000000 +0000 @@ -51,6 +51,12 @@ foo = inline("def foo(x): return x * x", **self.test_kwds)['foo'] self.assertEquals(foo(7), 49) + def test_class_ref(self): + class Type(object): + pass + tp = inline("Type")['Type'] + self.assertEqual(tp, Type) + def test_pure(self): import cython as cy b = inline(""" @@ -60,6 +66,14 @@ """, a=3, **self.test_kwds) self.assertEquals(type(b), float) + def test_compiler_directives(self): + self.assertEqual( + inline('return sum(x)', + x=[1, 2, 3], + cython_compiler_directives={'boundscheck': False}), + 6 + ) + if has_numpy: def test_numpy(self): diff -Nru cython-0.26.1/Cython/Build/Tests/TestIpythonMagic.py cython-0.29.14/Cython/Build/Tests/TestIpythonMagic.py --- cython-0.26.1/Cython/Build/Tests/TestIpythonMagic.py 2017-08-25 16:06:31.000000000 +0000 +++ cython-0.29.14/Cython/Build/Tests/TestIpythonMagic.py 2018-09-22 14:18:56.000000000 +0000 @@ -3,25 +3,38 @@ """Tests for the Cython magics extension.""" +from __future__ import absolute_import + import os import sys +from contextlib import contextmanager +from Cython.Build import IpythonMagic +from Cython.TestUtils import CythonTest try: - from IPython.testing.globalipapp import get_ipython + import IPython.testing.globalipapp from IPython.utils import py3compat -except: - __test__ = False +except ImportError: + # Disable tests and fake helpers for initialisation below. + class _py3compat(object): + def str_to_unicode(self, s): + return s + + py3compat = _py3compat() + + def skip_if_not_installed(_): + return None +else: + def skip_if_not_installed(c): + return c try: - # disable IPython history thread to avoid having to clean it up + # disable IPython history thread before it gets started to avoid having to clean it up from IPython.core.history import HistoryManager HistoryManager.enabled = False except ImportError: pass -from Cython.TestUtils import CythonTest - -ip = get_ipython() code = py3compat.str_to_unicode("""\ def f(x): return 2*x @@ -35,6 +48,12 @@ return f(*(x,)) """) +pgo_cython3_code = cython3_code + py3compat.str_to_unicode("""\ +def main(): + for _ in range(100): call(5) +main() +""") + if sys.platform == 'win32': # not using IPython's decorators here because they depend on "nose" @@ -55,19 +74,27 @@ return _skip_win32 +@skip_if_not_installed class TestIPythonMagic(CythonTest): + @classmethod + def setUpClass(cls): + CythonTest.setUpClass() + cls._ip = IPython.testing.globalipapp.get_ipython() + def setUp(self): CythonTest.setUp(self) - ip.extension_manager.load_extension('cython') + self._ip.extension_manager.load_extension('cython') def test_cython_inline(self): + ip = self._ip ip.ex('a=10; b=20') result = ip.run_cell_magic('cython_inline', '', 'return a+b') self.assertEqual(result, 30) @skip_win32('Skip on Windows') def test_cython_pyximport(self): + ip = self._ip module_name = '_test_cython_pyximport' ip.run_cell_magic('cython_pyximport', module_name, code) ip.ex('g = f(10)') @@ -81,12 +108,14 @@ pass def test_cython(self): + ip = self._ip ip.run_cell_magic('cython', '', code) ip.ex('g = f(10)') self.assertEqual(ip.user_ns['g'], 20.0) def test_cython_name(self): # The Cython module named 'mymodule' defines the function f. + ip = self._ip ip.run_cell_magic('cython', '--name=mymodule', code) # This module can now be imported in the interactive namespace. ip.ex('import mymodule; g = mymodule.f(10)') @@ -94,6 +123,7 @@ def test_cython_language_level(self): # The Cython cell defines the functions f() and call(). + ip = self._ip ip.run_cell_magic('cython', '', cython3_code) ip.ex('g = f(10); h = call(10)') if sys.version_info[0] < 3: @@ -105,6 +135,7 @@ def test_cython3(self): # The Cython cell defines the functions f() and call(). + ip = self._ip ip.run_cell_magic('cython', '-3', cython3_code) ip.ex('g = f(10); h = call(10)') self.assertEqual(ip.user_ns['g'], 2.0 / 10.0) @@ -112,13 +143,24 @@ def test_cython2(self): # The Cython cell defines the functions f() and call(). + ip = self._ip ip.run_cell_magic('cython', '-2', cython3_code) ip.ex('g = f(10); h = call(10)') self.assertEqual(ip.user_ns['g'], 2 // 10) self.assertEqual(ip.user_ns['h'], 2 // 10) @skip_win32('Skip on Windows') + def test_cython3_pgo(self): + # The Cython cell defines the functions f() and call(). + ip = self._ip + ip.run_cell_magic('cython', '-3 --pgo', pgo_cython3_code) + ip.ex('g = f(10); h = call(10); main()') + self.assertEqual(ip.user_ns['g'], 2.0 / 10.0) + self.assertEqual(ip.user_ns['h'], 2.0 / 10.0) + + @skip_win32('Skip on Windows') def test_extlibs(self): + ip = self._ip code = py3compat.str_to_unicode(""" from libc.math cimport sin x = sin(0.0) @@ -126,3 +168,45 @@ ip.user_ns['x'] = 1 ip.run_cell_magic('cython', '-l m', code) self.assertEqual(ip.user_ns['x'], 0) + + + def test_cython_verbose(self): + ip = self._ip + ip.run_cell_magic('cython', '--verbose', code) + ip.ex('g = f(10)') + self.assertEqual(ip.user_ns['g'], 20.0) + + def test_cython_verbose_thresholds(self): + @contextmanager + def mock_distutils(): + class MockLog: + DEBUG = 1 + INFO = 2 + thresholds = [INFO] + + def set_threshold(self, val): + self.thresholds.append(val) + return self.thresholds[-2] + + + new_log = MockLog() + old_log = IpythonMagic.distutils.log + try: + IpythonMagic.distutils.log = new_log + yield new_log + finally: + IpythonMagic.distutils.log = old_log + + ip = self._ip + with mock_distutils() as verbose_log: + ip.run_cell_magic('cython', '--verbose', code) + ip.ex('g = f(10)') + self.assertEqual(ip.user_ns['g'], 20.0) + self.assertEquals([verbose_log.INFO, verbose_log.DEBUG, verbose_log.INFO], + verbose_log.thresholds) + + with mock_distutils() as normal_log: + ip.run_cell_magic('cython', '', code) + ip.ex('g = f(10)') + self.assertEqual(ip.user_ns['g'], 20.0) + self.assertEquals([normal_log.INFO], normal_log.thresholds) diff -Nru cython-0.26.1/Cython/Build/Tests/TestStripLiterals.py cython-0.29.14/Cython/Build/Tests/TestStripLiterals.py --- cython-0.26.1/Cython/Build/Tests/TestStripLiterals.py 2015-06-22 12:53:11.000000000 +0000 +++ cython-0.29.14/Cython/Build/Tests/TestStripLiterals.py 2018-11-24 09:20:06.000000000 +0000 @@ -6,10 +6,10 @@ def t(self, before, expected): actual, literals = strip_string_literals(before, prefix="_L") - self.assertEquals(expected, actual) + self.assertEqual(expected, actual) for key, value in literals.items(): actual = actual.replace(key, value) - self.assertEquals(before, actual) + self.assertEqual(before, actual) def test_empty(self): self.t("", "") diff -Nru cython-0.26.1/Cython/CodeWriter.py cython-0.29.14/Cython/CodeWriter.py --- cython-0.26.1/Cython/CodeWriter.py 2017-08-12 14:06:59.000000000 +0000 +++ cython-0.29.14/Cython/CodeWriter.py 2018-09-22 14:18:56.000000000 +0000 @@ -363,7 +363,7 @@ self.dedent() def visit_IfStatNode(self, node): - # The IfClauseNode is handled directly without a seperate match + # The IfClauseNode is handled directly without a separate match # for clariy. self.startline(u"if ") self.visit(node.if_clauses[0].condition) @@ -519,3 +519,298 @@ def visit_StatNode(self, node): pass + + +class ExpressionWriter(TreeVisitor): + + def __init__(self, result=None): + super(ExpressionWriter, self).__init__() + if result is None: + result = u"" + self.result = result + self.precedence = [0] + + def write(self, tree): + self.visit(tree) + return self.result + + def put(self, s): + self.result += s + + def remove(self, s): + if self.result.endswith(s): + self.result = self.result[:-len(s)] + + def comma_separated_list(self, items): + if len(items) > 0: + for item in items[:-1]: + self.visit(item) + self.put(u", ") + self.visit(items[-1]) + + def visit_Node(self, node): + raise AssertionError("Node not handled by serializer: %r" % node) + + def visit_NameNode(self, node): + self.put(node.name) + + def visit_NoneNode(self, node): + self.put(u"None") + + def visit_EllipsisNode(self, node): + self.put(u"...") + + def visit_BoolNode(self, node): + self.put(str(node.value)) + + def visit_ConstNode(self, node): + self.put(str(node.value)) + + def visit_ImagNode(self, node): + self.put(node.value) + self.put(u"j") + + def emit_string(self, node, prefix=u""): + repr_val = repr(node.value) + if repr_val[0] in 'ub': + repr_val = repr_val[1:] + self.put(u"%s%s" % (prefix, repr_val)) + + def visit_BytesNode(self, node): + self.emit_string(node, u"b") + + def visit_StringNode(self, node): + self.emit_string(node) + + def visit_UnicodeNode(self, node): + self.emit_string(node, u"u") + + def emit_sequence(self, node, parens=(u"", u"")): + open_paren, close_paren = parens + items = node.subexpr_nodes() + self.put(open_paren) + self.comma_separated_list(items) + self.put(close_paren) + + def visit_ListNode(self, node): + self.emit_sequence(node, u"[]") + + def visit_TupleNode(self, node): + self.emit_sequence(node, u"()") + + def visit_SetNode(self, node): + if len(node.subexpr_nodes()) > 0: + self.emit_sequence(node, u"{}") + else: + self.put(u"set()") + + def visit_DictNode(self, node): + self.emit_sequence(node, u"{}") + + def visit_DictItemNode(self, node): + self.visit(node.key) + self.put(u": ") + self.visit(node.value) + + unop_precedence = { + 'not': 3, '!': 3, + '+': 11, '-': 11, '~': 11, + } + binop_precedence = { + 'or': 1, + 'and': 2, + # unary: 'not': 3, '!': 3, + 'in': 4, 'not_in': 4, 'is': 4, 'is_not': 4, '<': 4, '<=': 4, '>': 4, '>=': 4, '!=': 4, '==': 4, + '|': 5, + '^': 6, + '&': 7, + '<<': 8, '>>': 8, + '+': 9, '-': 9, + '*': 10, '@': 10, '/': 10, '//': 10, '%': 10, + # unary: '+': 11, '-': 11, '~': 11 + '**': 12, + } + + def operator_enter(self, new_prec): + old_prec = self.precedence[-1] + if old_prec > new_prec: + self.put(u"(") + self.precedence.append(new_prec) + + def operator_exit(self): + old_prec, new_prec = self.precedence[-2:] + if old_prec > new_prec: + self.put(u")") + self.precedence.pop() + + def visit_NotNode(self, node): + op = 'not' + prec = self.unop_precedence[op] + self.operator_enter(prec) + self.put(u"not ") + self.visit(node.operand) + self.operator_exit() + + def visit_UnopNode(self, node): + op = node.operator + prec = self.unop_precedence[op] + self.operator_enter(prec) + self.put(u"%s" % node.operator) + self.visit(node.operand) + self.operator_exit() + + def visit_BinopNode(self, node): + op = node.operator + prec = self.binop_precedence.get(op, 0) + self.operator_enter(prec) + self.visit(node.operand1) + self.put(u" %s " % op.replace('_', ' ')) + self.visit(node.operand2) + self.operator_exit() + + def visit_BoolBinopNode(self, node): + self.visit_BinopNode(node) + + def visit_PrimaryCmpNode(self, node): + self.visit_BinopNode(node) + + def visit_IndexNode(self, node): + self.visit(node.base) + self.put(u"[") + if isinstance(node.index, TupleNode): + self.emit_sequence(node.index) + else: + self.visit(node.index) + self.put(u"]") + + def visit_SliceIndexNode(self, node): + self.visit(node.base) + self.put(u"[") + if node.start: + self.visit(node.start) + self.put(u":") + if node.stop: + self.visit(node.stop) + if node.slice: + self.put(u":") + self.visit(node.slice) + self.put(u"]") + + def visit_SliceNode(self, node): + if not node.start.is_none: + self.visit(node.start) + self.put(u":") + if not node.stop.is_none: + self.visit(node.stop) + if not node.step.is_none: + self.put(u":") + self.visit(node.step) + + def visit_CondExprNode(self, node): + self.visit(node.true_val) + self.put(u" if ") + self.visit(node.test) + self.put(u" else ") + self.visit(node.false_val) + + def visit_AttributeNode(self, node): + self.visit(node.obj) + self.put(u".%s" % node.attribute) + + def visit_SimpleCallNode(self, node): + self.visit(node.function) + self.put(u"(") + self.comma_separated_list(node.args) + self.put(")") + + def emit_pos_args(self, node): + if node is None: + return + if isinstance(node, AddNode): + self.emit_pos_args(node.operand1) + self.emit_pos_args(node.operand2) + elif isinstance(node, TupleNode): + for expr in node.subexpr_nodes(): + self.visit(expr) + self.put(u", ") + elif isinstance(node, AsTupleNode): + self.put("*") + self.visit(node.arg) + self.put(u", ") + else: + self.visit(node) + self.put(u", ") + + def emit_kwd_args(self, node): + if node is None: + return + if isinstance(node, MergedDictNode): + for expr in node.subexpr_nodes(): + self.emit_kwd_args(expr) + elif isinstance(node, DictNode): + for expr in node.subexpr_nodes(): + self.put(u"%s=" % expr.key.value) + self.visit(expr.value) + self.put(u", ") + else: + self.put(u"**") + self.visit(node) + self.put(u", ") + + def visit_GeneralCallNode(self, node): + self.visit(node.function) + self.put(u"(") + self.emit_pos_args(node.positional_args) + self.emit_kwd_args(node.keyword_args) + self.remove(u", ") + self.put(")") + + def emit_comprehension(self, body, target, + sequence, condition, + parens=(u"", u"")): + open_paren, close_paren = parens + self.put(open_paren) + self.visit(body) + self.put(u" for ") + self.visit(target) + self.put(u" in ") + self.visit(sequence) + if condition: + self.put(u" if ") + self.visit(condition) + self.put(close_paren) + + def visit_ComprehensionAppendNode(self, node): + self.visit(node.expr) + + def visit_DictComprehensionAppendNode(self, node): + self.visit(node.key_expr) + self.put(u": ") + self.visit(node.value_expr) + + def visit_ComprehensionNode(self, node): + tpmap = {'list': u"[]", 'dict': u"{}", 'set': u"{}"} + parens = tpmap[node.type.py_type_name()] + body = node.loop.body + target = node.loop.target + sequence = node.loop.iterator.sequence + condition = None + if hasattr(body, 'if_clauses'): + # type(body) is Nodes.IfStatNode + condition = body.if_clauses[0].condition + body = body.if_clauses[0].body + self.emit_comprehension(body, target, sequence, condition, parens) + + def visit_GeneratorExpressionNode(self, node): + body = node.loop.body + target = node.loop.target + sequence = node.loop.iterator.sequence + condition = None + if hasattr(body, 'if_clauses'): + # type(body) is Nodes.IfStatNode + condition = body.if_clauses[0].condition + body = body.if_clauses[0].body.expr.arg + elif hasattr(body, 'expr'): + # type(body) is Nodes.ExprStatNode + body = body.expr.arg + self.emit_comprehension(body, target, sequence, condition, u"()") diff -Nru cython-0.26.1/Cython/Compiler/Annotate.py cython-0.29.14/Cython/Compiler/Annotate.py --- cython-0.26.1/Cython/Compiler/Annotate.py 2017-08-25 16:06:31.000000000 +0000 +++ cython-0.29.14/Cython/Compiler/Annotate.py 2018-09-22 14:18:56.000000000 +0000 @@ -79,14 +79,6 @@ css.append(HtmlFormatter().get_style_defs('.cython')) return '\n'.join(css) - _js = """ - function toggleDiv(id) { - theDiv = id.nextElementSibling - if (theDiv.style.display != 'block') theDiv.style.display = 'block'; - else theDiv.style.display = 'none'; - } - """.strip() - _css_template = textwrap.dedent(""" body.cython { font-family: courier; font-size: 12; } @@ -114,6 +106,14 @@ .cython.code .c_call { color: #0000FF; } """) + # on-click toggle function to show/hide C source code + _onclick_attr = ' onclick="{0}"'.format(( + "(function(s){" + " s.display = s.display === 'block' ? 'none' : 'block'" + "})(this.nextElementSibling.style)" + ).replace(' ', '') # poor dev's JS minification + ) + def save_annotation(self, source_filename, target_filename, coverage_xml=None): with Utils.open_source_file(source_filename) as f: code = f.read() @@ -141,9 +141,6 @@ -

Generated by Cython {watermark}{more_info}

@@ -151,7 +148,7 @@ Yellow lines hint at Python interaction.
Click on a line that starts with a "+" to see the C code that Cython generated for it.

- ''').format(css=self._css(), js=self._js, watermark=Version.watermark, + ''').format(css=self._css(), watermark=Version.watermark, filename=os.path.basename(source_filename) if source_filename else '', more_info=coverage_info) ] @@ -253,7 +250,7 @@ calls['py_macro_api'] + calls['pyx_macro_api']) if c_code: - onclick = " onclick='toggleDiv(this)'" + onclick = self._onclick_attr expandsymbol = '+' else: onclick = '' diff -Nru cython-0.26.1/Cython/Compiler/AutoDocTransforms.py cython-0.29.14/Cython/Compiler/AutoDocTransforms.py --- cython-0.26.1/Cython/Compiler/AutoDocTransforms.py 2016-12-10 15:41:15.000000000 +0000 +++ cython-0.29.14/Cython/Compiler/AutoDocTransforms.py 2018-09-22 14:18:56.000000000 +0000 @@ -1,89 +1,59 @@ -from __future__ import absolute_import +from __future__ import absolute_import, print_function from .Visitor import CythonTransform from .StringEncoding import EncodedString from . import Options from . import PyrexTypes, ExprNodes +from ..CodeWriter import ExpressionWriter + + +class AnnotationWriter(ExpressionWriter): + + def visit_Node(self, node): + self.put(u"") + + def visit_LambdaNode(self, node): + # XXX Should we do better? + self.put("") + class EmbedSignature(CythonTransform): def __init__(self, context): super(EmbedSignature, self).__init__(context) - self.denv = None # XXX self.class_name = None self.class_node = None - unop_precedence = 11 - binop_precedence = { - 'or': 1, - 'and': 2, - 'not': 3, - 'in': 4, 'not in': 4, 'is': 4, 'is not': 4, '<': 4, '<=': 4, '>': 4, '>=': 4, '!=': 4, '==': 4, - '|': 5, - '^': 6, - '&': 7, - '<<': 8, '>>': 8, - '+': 9, '-': 9, - '*': 10, '/': 10, '//': 10, '%': 10, - # unary: '+': 11, '-': 11, '~': 11 - '**': 12} - - def _fmt_expr_node(self, node, precedence=0): - if isinstance(node, ExprNodes.BinopNode) and not node.inplace: - new_prec = self.binop_precedence.get(node.operator, 0) - result = '%s %s %s' % (self._fmt_expr_node(node.operand1, new_prec), - node.operator, - self._fmt_expr_node(node.operand2, new_prec)) - if precedence > new_prec: - result = '(%s)' % result - elif isinstance(node, ExprNodes.UnopNode): - result = '%s%s' % (node.operator, - self._fmt_expr_node(node.operand, self.unop_precedence)) - if precedence > self.unop_precedence: - result = '(%s)' % result - elif isinstance(node, ExprNodes.AttributeNode): - result = '%s.%s' % (self._fmt_expr_node(node.obj), node.attribute) - else: - result = node.name + def _fmt_expr(self, node): + writer = AnnotationWriter() + result = writer.write(node) + # print(type(node).__name__, '-->', result) return result - def _fmt_arg_defv(self, arg): - default_val = arg.default - if not default_val: - return None - if isinstance(default_val, ExprNodes.NullNode): - return 'NULL' - try: - denv = self.denv # XXX - ctval = default_val.compile_time_value(self.denv) - repr_val = repr(ctval) - if isinstance(default_val, ExprNodes.UnicodeNode): - if repr_val[:1] != 'u': - return u'u%s' % repr_val - elif isinstance(default_val, ExprNodes.BytesNode): - if repr_val[:1] != 'b': - return u'b%s' % repr_val - elif isinstance(default_val, ExprNodes.StringNode): - if repr_val[:1] in 'ub': - return repr_val[1:] - return repr_val - except Exception: - try: - return self._fmt_expr_node(default_val) - except AttributeError: - return '' - def _fmt_arg(self, arg): if arg.type is PyrexTypes.py_object_type or arg.is_self_arg: doc = arg.name else: doc = arg.type.declaration_code(arg.name, for_display=1) - if arg.default: - arg_defv = self._fmt_arg_defv(arg) - if arg_defv: - doc = doc + ('=%s' % arg_defv) + + if arg.annotation: + annotation = self._fmt_expr(arg.annotation) + doc = doc + (': %s' % annotation) + if arg.default: + default = self._fmt_expr(arg.default) + doc = doc + (' = %s' % default) + elif arg.default: + default = self._fmt_expr(arg.default) + doc = doc + ('=%s' % default) return doc + def _fmt_star_arg(self, arg): + arg_doc = arg.name + if arg.annotation: + annotation = self._fmt_expr(arg.annotation) + arg_doc = arg_doc + (': %s' % annotation) + return arg_doc + def _fmt_arglist(self, args, npargs=0, pargs=None, nkargs=0, kargs=None, @@ -94,11 +64,13 @@ arg_doc = self._fmt_arg(arg) arglist.append(arg_doc) if pargs: - arglist.insert(npargs, '*%s' % pargs.name) + arg_doc = self._fmt_star_arg(pargs) + arglist.insert(npargs, '*%s' % arg_doc) elif nkargs: arglist.insert(npargs, '*') if kargs: - arglist.append('**%s' % kargs.name) + arg_doc = self._fmt_star_arg(kargs) + arglist.append('**%s' % arg_doc) return arglist def _fmt_ret_type(self, ret): @@ -110,6 +82,7 @@ def _fmt_signature(self, cls_name, func_name, args, npargs=0, pargs=None, nkargs=0, kargs=None, + return_expr=None, return_type=None, hide_self=False): arglist = self._fmt_arglist(args, npargs, pargs, @@ -119,10 +92,13 @@ func_doc = '%s(%s)' % (func_name, arglist_doc) if cls_name: func_doc = '%s.%s' % (cls_name, func_doc) - if return_type: + ret_doc = None + if return_expr: + ret_doc = self._fmt_expr(return_expr) + elif return_type: ret_doc = self._fmt_ret_type(return_type) - if ret_doc: - func_doc = '%s -> %s' % (func_doc, ret_doc) + if ret_doc: + func_doc = '%s -> %s' % (func_doc, ret_doc) return func_doc def _embed_signature(self, signature, node_doc): @@ -177,6 +153,7 @@ class_name, func_name, node.args, npargs, node.star_arg, nkargs, node.starstar_arg, + return_expr=node.return_type_annotation, return_type=None, hide_self=hide_self) if signature: if is_constructor: diff -Nru cython-0.26.1/Cython/Compiler/Buffer.py cython-0.29.14/Cython/Compiler/Buffer.py --- cython-0.26.1/Cython/Compiler/Buffer.py 2017-08-25 16:06:31.000000000 +0000 +++ cython-0.29.14/Cython/Compiler/Buffer.py 2018-09-22 14:18:56.000000000 +0000 @@ -36,7 +36,6 @@ if self.buffers_exists: use_bufstruct_declare_code(node.scope) use_py2_buffer_functions(node.scope) - node.scope.use_utility_code(empty_bufstruct_utility) return result @@ -317,8 +316,8 @@ code.putln("%s.data = NULL;" % pybuffernd_struct) code.putln("%s.rcbuffer = &%s;" % (pybuffernd_struct, pybuffer_struct)) + def put_acquire_arg_buffer(entry, code, pos): - code.globalstate.use_utility_code(acquire_utility_code) buffer_aux = entry.buffer_aux getbuffer = get_getbuffer_call(code, entry.cname, buffer_aux, entry.type) @@ -327,14 +326,16 @@ code.putln("__Pyx_BufFmt_StackElem __pyx_stack[%d];" % entry.type.dtype.struct_nesting_depth()) code.putln(code.error_goto_if("%s == -1" % getbuffer, pos)) code.putln("}") - # An exception raised in arg parsing cannot be catched, so no + # An exception raised in arg parsing cannot be caught, so no # need to care about the buffer then. put_unpack_buffer_aux_into_scope(entry, code) + def put_release_buffer_code(code, entry): code.globalstate.use_utility_code(acquire_utility_code) code.putln("__Pyx_SafeReleaseBuffer(&%s.rcbuffer->pybuffer);" % entry.buffer_aux.buflocal_nd_var.cname) + def get_getbuffer_call(code, obj_cname, buffer_aux, buffer_type): ndim = buffer_type.ndim cast = int(buffer_type.cast) @@ -343,10 +344,12 @@ dtype_typeinfo = get_type_information_cname(code, buffer_type.dtype) + code.globalstate.use_utility_code(acquire_utility_code) return ("__Pyx_GetBufferAndValidate(&%(pybuffernd_struct)s.rcbuffer->pybuffer, " "(PyObject*)%(obj_cname)s, &%(dtype_typeinfo)s, %(flags)s, %(ndim)d, " "%(cast)d, __pyx_stack)" % locals()) + def put_assign_to_buffer(lhs_cname, rhs_cname, buf_entry, is_initialized, pos, code): """ @@ -364,11 +367,10 @@ """ buffer_aux, buffer_type = buf_entry.buffer_aux, buf_entry.type - code.globalstate.use_utility_code(acquire_utility_code) pybuffernd_struct = buffer_aux.buflocal_nd_var.cname flags = get_flags(buffer_aux, buffer_type) - code.putln("{") # Set up necesarry stack for getbuffer + code.putln("{") # Set up necessary stack for getbuffer code.putln("__Pyx_BufFmt_StackElem __pyx_stack[%d];" % buffer_type.dtype.struct_nesting_depth()) getbuffer = get_getbuffer_call(code, "%s", buffer_aux, buffer_type) # fill in object below @@ -384,18 +386,19 @@ # before raising the exception. A failure of reacquisition # will cause the reacquisition exception to be reported, one # can consider working around this later. - type, value, tb = [code.funcstate.allocate_temp(PyrexTypes.py_object_type, manage_ref=False) - for i in range(3)] - code.putln('PyErr_Fetch(&%s, &%s, &%s);' % (type, value, tb)) + exc_temps = tuple(code.funcstate.allocate_temp(PyrexTypes.py_object_type, manage_ref=False) + for _ in range(3)) + code.putln('PyErr_Fetch(&%s, &%s, &%s);' % exc_temps) code.putln('if (%s) {' % code.unlikely("%s == -1" % (getbuffer % lhs_cname))) - code.putln('Py_XDECREF(%s); Py_XDECREF(%s); Py_XDECREF(%s);' % (type, value, tb)) # Do not refnanny these! + code.putln('Py_XDECREF(%s); Py_XDECREF(%s); Py_XDECREF(%s);' % exc_temps) # Do not refnanny these! code.globalstate.use_utility_code(raise_buffer_fallback_code) code.putln('__Pyx_RaiseBufferFallbackError();') code.putln('} else {') - code.putln('PyErr_Restore(%s, %s, %s);' % (type, value, tb)) - for t in (type, value, tb): - code.funcstate.release_temp(t) + code.putln('PyErr_Restore(%s, %s, %s);' % exc_temps) code.putln('}') + code.putln('%s = %s = %s = 0;' % exc_temps) + for t in exc_temps: + code.funcstate.release_temp(t) code.putln('}') # Unpack indices put_unpack_buffer_aux_into_scope(buf_entry, code) @@ -489,15 +492,6 @@ env.use_utility_code(buffer_struct_declare_code) -def get_empty_bufstruct_code(max_ndim): - code = dedent(""" - static Py_ssize_t __Pyx_zeros[] = {%s}; - static Py_ssize_t __Pyx_minusones[] = {%s}; - """) % (", ".join(["0"] * max_ndim), ", ".join(["-1"] * max_ndim)) - return UtilityCode(proto=code) - -empty_bufstruct_utility = get_empty_bufstruct_code(Options.buffer_max_dims) - def buf_lookup_full_code(proto, defin, name, nd): """ Generates a buffer lookup function for the right number @@ -518,6 +512,7 @@ """) % (i, i, i, i) for i in range(nd)] ) + "\nreturn ptr;\n}") + def buf_lookup_strided_code(proto, defin, name, nd): """ Generates a buffer lookup function for the right number @@ -528,6 +523,7 @@ offset = " + ".join(["i%d * s%d" % (i, i) for i in range(nd)]) proto.putln("#define %s(type, buf, %s) (type)((char*)buf + %s)" % (name, args, offset)) + def buf_lookup_c_code(proto, defin, name, nd): """ Similar to strided lookup, but can assume that the last dimension @@ -541,6 +537,7 @@ offset = " + ".join(["i%d * s%d" % (i, i) for i in range(nd - 1)]) proto.putln("#define %s(type, buf, %s) ((type)((char*)buf + %s) + i%d)" % (name, args, offset, nd - 1)) + def buf_lookup_fortran_code(proto, defin, name, nd): """ Like C lookup, but the first index is optimized instead. @@ -556,6 +553,7 @@ def use_py2_buffer_functions(env): env.use_utility_code(GetAndReleaseBufferUtilityCode()) + class GetAndReleaseBufferUtilityCode(object): # Emulation of PyObject_GetBuffer and PyBuffer_Release for Python 2. # For >= 2.6 we do double mode -- use the new buffer interface on objects @@ -619,7 +617,7 @@ def mangle_dtype_name(dtype): - # Use prefixes to seperate user defined types from builtins + # Use prefixes to separate user defined types from builtins # (consider "typedef float unsigned_int") if dtype.is_pyobject: return "object" @@ -638,7 +636,7 @@ and return the name of the type info struct. Structs with two floats of the same size are encoded as complex numbers. - One can seperate between complex numbers declared as struct or with native + One can separate between complex numbers declared as struct or with native encoding by inspecting to see if the fields field of the type is filled in. """ @@ -723,26 +721,18 @@ else: return TempitaUtilityCode.load(util_code_name, "Buffer.c", context=context, **kwargs) -context = dict(max_dims=str(Options.buffer_max_dims)) -buffer_struct_declare_code = load_buffer_utility("BufferStructDeclare", - context=context) - +context = dict(max_dims=Options.buffer_max_dims) +buffer_struct_declare_code = load_buffer_utility("BufferStructDeclare", context=context) +buffer_formats_declare_code = load_buffer_utility("BufferFormatStructs") # Utility function to set the right exception # The caller should immediately goto_error raise_indexerror_code = load_buffer_utility("BufferIndexError") raise_indexerror_nogil = load_buffer_utility("BufferIndexErrorNogil") - raise_buffer_fallback_code = load_buffer_utility("BufferFallbackError") -buffer_structs_code = load_buffer_utility( - "BufferFormatStructs", proto_block='utility_code_proto_before_types') -acquire_utility_code = load_buffer_utility("BufferFormatCheck", - context=context, - requires=[buffer_structs_code, - UtilityCode.load_cached("IsLittleEndian", "ModuleSetupCode.c")]) + +acquire_utility_code = load_buffer_utility("BufferGetAndValidate", context=context) +buffer_format_check_code = load_buffer_utility("BufferFormatCheck", context=context) # See utility code BufferFormatFromTypeInfo -_typeinfo_to_format_code = load_buffer_utility("TypeInfoToFormat", context={}, - requires=[buffer_structs_code]) -typeinfo_compare_code = load_buffer_utility("TypeInfoCompare", context={}, - requires=[buffer_structs_code]) +_typeinfo_to_format_code = load_buffer_utility("TypeInfoToFormat") diff -Nru cython-0.26.1/Cython/Compiler/Builtin.py cython-0.29.14/Cython/Compiler/Builtin.py --- cython-0.26.1/Cython/Compiler/Builtin.py 2017-08-12 14:06:59.000000000 +0000 +++ cython-0.29.14/Cython/Compiler/Builtin.py 2018-09-22 14:18:56.000000000 +0000 @@ -95,16 +95,24 @@ is_strict_signature = True), BuiltinFunction('abs', "f", "f", "fabsf", is_strict_signature = True), + BuiltinFunction('abs', "i", "i", "abs", + is_strict_signature = True), + BuiltinFunction('abs', "l", "l", "labs", + is_strict_signature = True), + BuiltinFunction('abs', None, None, "__Pyx_abs_longlong", + utility_code = UtilityCode.load("abs_longlong", "Builtins.c"), + func_type = PyrexTypes.CFuncType( + PyrexTypes.c_longlong_type, [ + PyrexTypes.CFuncTypeArg("arg", PyrexTypes.c_longlong_type, None) + ], + is_strict_signature = True, nogil=True)), ] + list( - # uses getattr to get PyrexTypes.c_uint_type etc to allow easy iteration over a list - BuiltinFunction('abs', None, None, "__Pyx_abs_{0}".format(t), - utility_code = UtilityCode.load("abs_{0}".format(t), "Builtins.c"), + BuiltinFunction('abs', None, None, "/*abs_{0}*/".format(t.specialization_name()), func_type = PyrexTypes.CFuncType( - getattr(PyrexTypes,"c_u{0}_type".format(t)), [ - PyrexTypes.CFuncTypeArg("arg", getattr(PyrexTypes,"c_{0}_type".format(t)), None) - ], + t, + [PyrexTypes.CFuncTypeArg("arg", t, None)], is_strict_signature = True, nogil=True)) - for t in ("int", "long", "longlong") + for t in (PyrexTypes.c_uint_type, PyrexTypes.c_ulong_type, PyrexTypes.c_ulonglong_type) ) + list( BuiltinFunction('abs', None, None, "__Pyx_c_abs{0}".format(t.funcsuffix), func_type = PyrexTypes.CFuncType( @@ -116,7 +124,8 @@ PyrexTypes.c_double_complex_type, PyrexTypes.c_longdouble_complex_type) ) + [ - BuiltinFunction('abs', "O", "O", "PyNumber_Absolute"), + BuiltinFunction('abs', "O", "O", "__Pyx_PyNumber_Absolute", + utility_code=UtilityCode.load("py_abs", "Builtins.c")), #('all', "", "", ""), #('any', "", "", ""), #('ascii', "", "", ""), @@ -320,7 +329,10 @@ ("set", "PySet_Type", [BuiltinMethod("__contains__", "TO", "b", "PySequence_Contains"), BuiltinMethod("clear", "T", "r", "PySet_Clear"), # discard() and remove() have a special treatment for unhashable values -# BuiltinMethod("discard", "TO", "r", "PySet_Discard"), + BuiltinMethod("discard", "TO", "r", "__Pyx_PySet_Discard", + utility_code=UtilityCode.load("py_set_discard", "Optimize.c")), + BuiltinMethod("remove", "TO", "r", "__Pyx_PySet_Remove", + utility_code=UtilityCode.load("py_set_remove", "Optimize.c")), # update is actually variadic (see Github issue #1645) # BuiltinMethod("update", "TO", "r", "__Pyx_PySet_Update", # utility_code=UtilityCode.load_cached("PySet_Update", "Builtins.c")), @@ -380,6 +392,8 @@ utility = builtin_utility_code.get(name) if name == 'frozenset': objstruct_cname = 'PySetObject' + elif name == 'bytearray': + objstruct_cname = 'PyByteArrayObject' elif name == 'bool': objstruct_cname = None elif name == 'Exception': diff -Nru cython-0.26.1/Cython/Compiler/CmdLine.py cython-0.29.14/Cython/Compiler/CmdLine.py --- cython-0.26.1/Cython/Compiler/CmdLine.py 2015-09-10 16:25:36.000000000 +0000 +++ cython-0.29.14/Cython/Compiler/CmdLine.py 2018-11-24 09:20:06.000000000 +0000 @@ -40,6 +40,8 @@ --embed[=] Generate a main() function that embeds the Python interpreter. -2 Compile based on Python-2 syntax and code semantics. -3 Compile based on Python-3 syntax and code semantics. + --3str Compile based on Python-3 syntax and code semantics without + assuming unicode by default for string literals under Python 2. --lenient Change some compile time errors to runtime errors to improve Python compatibility --capi-reexport-cincludes Add cincluded headers to any auto-generated header files. @@ -47,10 +49,11 @@ --warning-errors, -Werror Make all warnings into errors --warning-extra, -Wextra Enable extra warnings -X, --directive =[,= 3', 'input'), } +ctypedef_builtins_map = { + # types of builtins in "ctypedef class" statements which we don't + # import either because the names conflict with C types or because + # the type simply is not exposed. + 'py_int' : '&PyInt_Type', + 'py_long' : '&PyLong_Type', + 'py_float' : '&PyFloat_Type', + 'wrapper_descriptor' : '&PyWrapperDescr_Type', +} + basicsize_builtins_map = { # builtins whose type has a different tp_basicsize than sizeof(...) 'PyTypeObject': 'PyHeapTypeObject', } uncachable_builtins = [ - # builtin names that cannot be cached because they may or may not - # be available at import time + # Global/builtin names that cannot be cached because they may or may not + # be available at import time, for various reasons: + ## - Py3.7+ + 'breakpoint', # might deserve an implementation in Cython + ## - Py3.4+ + '__loader__', + '__spec__', + ## - Py3+ + 'BlockingIOError', + 'BrokenPipeError', + 'ChildProcessError', + 'ConnectionAbortedError', + 'ConnectionError', + 'ConnectionRefusedError', + 'ConnectionResetError', + 'FileExistsError', + 'FileNotFoundError', + 'InterruptedError', + 'IsADirectoryError', + 'ModuleNotFoundError', + 'NotADirectoryError', + 'PermissionError', + 'ProcessLookupError', + 'RecursionError', + 'ResourceWarning', + #'StopAsyncIteration', # backported + 'TimeoutError', + '__build_class__', + 'ascii', # might deserve an implementation in Cython + #'exec', # implemented in Cython + ## - Py2.7+ + 'memoryview', + ## - platform specific 'WindowsError', - '_', # e.g. gettext + ## - others + '_', # e.g. used by gettext ] special_py_methods = set([ @@ -76,7 +120,81 @@ 'inline': 'CYTHON_INLINE' }.get -is_self_assignment = re.compile(r" *(\w+) = (\1);\s*$").match + +class IncludeCode(object): + """ + An include file and/or verbatim C code to be included in the + generated sources. + """ + # attributes: + # + # pieces {order: unicode}: pieces of C code to be generated. + # For the included file, the key "order" is zero. + # For verbatim include code, the "order" is the "order" + # attribute of the original IncludeCode where this piece + # of C code was first added. This is needed to prevent + # duplication if the same include code is found through + # multiple cimports. + # location int: where to put this include in the C sources, one + # of the constants INITIAL, EARLY, LATE + # order int: sorting order (automatically set by increasing counter) + + # Constants for location. If the same include occurs with different + # locations, the earliest one takes precedense. + INITIAL = 0 + EARLY = 1 + LATE = 2 + + counter = 1 # Counter for "order" + + def __init__(self, include=None, verbatim=None, late=True, initial=False): + self.order = self.counter + type(self).counter += 1 + self.pieces = {} + + if include: + if include[0] == '<' and include[-1] == '>': + self.pieces[0] = u'#include {0}'.format(include) + late = False # system include is never late + else: + self.pieces[0] = u'#include "{0}"'.format(include) + + if verbatim: + self.pieces[self.order] = verbatim + + if initial: + self.location = self.INITIAL + elif late: + self.location = self.LATE + else: + self.location = self.EARLY + + def dict_update(self, d, key): + """ + Insert `self` in dict `d` with key `key`. If that key already + exists, update the attributes of the existing value with `self`. + """ + if key in d: + other = d[key] + other.location = min(self.location, other.location) + other.pieces.update(self.pieces) + else: + d[key] = self + + def sortkey(self): + return self.order + + def mainpiece(self): + """ + Return the main piece of C code, corresponding to the include + file. If there was no include file, return None. + """ + return self.pieces.get(0) + + def write(self, code): + # Write values of self.pieces dict, sorted by the keys + for k in sorted(self.pieces): + code.putln(self.pieces[k]) def get_utility_dir(): @@ -116,7 +234,6 @@ """ is_cython_utility = False - requires = None _utility_cache = {} @classmethod @@ -138,19 +255,16 @@ if type == 'proto': utility[0] = code - elif type.startswith('proto.'): - utility[0] = code - utility[1] = type[6:] elif type == 'impl': - utility[2] = code + utility[1] = code else: - all_tags = utility[3] + all_tags = utility[2] if KEYWORDS_MUST_BE_BYTES: type = type.encode('ASCII') all_tags[type] = code if tags: - all_tags = utility[3] + all_tags = utility[2] for name, values in tags.items(): if KEYWORDS_MUST_BE_BYTES: name = name.encode('ASCII') @@ -176,12 +290,12 @@ (r'^%(C)s{5,30}\s*(?P(?:\w|\.)+)\s*%(C)s{5,30}|' r'^%(C)s+@(?P\w+)\s*:\s*(?P(?:\w|[.:])+)') % {'C': comment}).match - match_type = re.compile('(.+)[.](proto(?:[.]\S+)?|impl|init|cleanup)$').match + match_type = re.compile(r'(.+)[.](proto(?:[.]\S+)?|impl|init|cleanup)$').match with closing(Utils.open_source_file(filename, encoding='UTF-8')) as f: all_lines = f.readlines() - utilities = defaultdict(lambda: [None, None, None, {}]) + utilities = defaultdict(lambda: [None, None, {}]) lines = [] tags = defaultdict(set) utility = type = None @@ -255,7 +369,7 @@ from_file = files[0] utilities = cls.load_utilities_from_file(from_file) - proto, proto_block, impl, tags = utilities[util_code_name] + proto, impl, tags = utilities[util_code_name] if tags: orig_kwargs = kwargs.copy() @@ -274,13 +388,11 @@ elif not values: values = None elif len(values) == 1: - values = values[0] + values = list(values)[0] kwargs[name] = values if proto is not None: kwargs['proto'] = proto - if proto_block is not None: - kwargs['proto_block'] = proto_block if impl is not None: kwargs['impl'] = impl @@ -327,6 +439,10 @@ def get_tree(self, **kwargs): pass + def __deepcopy__(self, memodict=None): + # No need to deep-copy utility code since it's essentially immutable. + return self + class UtilityCode(UtilityCodeBase): """ @@ -337,7 +453,7 @@ hashes/equals by instance proto C prototypes - impl implemenation code + impl implementation code init code to call on module initialization requires utility code dependencies proto_block the place in the resulting file where the prototype should @@ -411,21 +527,22 @@ def inject_string_constants(self, impl, output): """Replace 'PYIDENT("xyz")' by a constant Python identifier cname. """ - if 'PYIDENT(' not in impl: + if 'PYIDENT(' not in impl and 'PYUNICODE(' not in impl: return False, impl replacements = {} def externalise(matchobj): - name = matchobj.group(1) + key = matchobj.groups() try: - cname = replacements[name] + cname = replacements[key] except KeyError: - cname = replacements[name] = output.get_interned_identifier( - StringEncoding.EncodedString(name)).cname + str_type, name = key + cname = replacements[key] = output.get_py_string_const( + StringEncoding.EncodedString(name), identifier=str_type == 'IDENT').cname return cname - impl = re.sub(r'PYIDENT\("([^"]+)"\)', externalise, impl) - assert 'PYIDENT(' not in impl + impl = re.sub(r'PY(IDENT|UNICODE)\("([^"]+)"\)', externalise, impl) + assert 'PYIDENT(' not in impl and 'PYUNICODE(' not in impl return bool(replacements), impl def inject_unbound_methods(self, impl, output): @@ -436,21 +553,18 @@ utility_code = set() def externalise(matchobj): - type_cname, method_name, args = matchobj.groups() - args = [arg.strip() for arg in args[1:].split(',')] - if len(args) == 1: - call = '__Pyx_CallUnboundCMethod0' - utility_code.add("CallUnboundCMethod0") - elif len(args) == 2: - call = '__Pyx_CallUnboundCMethod1' - utility_code.add("CallUnboundCMethod1") - else: - assert False, "CALL_UNBOUND_METHOD() requires 1 or 2 call arguments" - - cname = output.get_cached_unbound_method(type_cname, method_name, len(args)) - return '%s(&%s, %s)' % (call, cname, ', '.join(args)) - - impl = re.sub(r'CALL_UNBOUND_METHOD\(([a-zA-Z_]+),\s*"([^"]+)"((?:,\s*[^),]+)+)\)', externalise, impl) + type_cname, method_name, obj_cname, args = matchobj.groups() + args = [arg.strip() for arg in args[1:].split(',')] if args else [] + assert len(args) < 3, "CALL_UNBOUND_METHOD() does not support %d call arguments" % len(args) + return output.cached_unbound_method_call_code(obj_cname, type_cname, method_name, args) + + impl = re.sub( + r'CALL_UNBOUND_METHOD\(' + r'([a-zA-Z_]+),' # type cname + r'\s*"([^"]+)",' # method name + r'\s*([^),]+)' # object cname + r'((?:,\s*[^),]+)*)' # args* + r'\)', externalise, impl) assert 'CALL_UNBOUND_METHOD(' not in impl for helper in sorted(utility_code): @@ -564,6 +678,7 @@ available. Useful when you only have 'env' but not 'code'. """ __name__ = '' + requires = None def __init__(self, callback): self.callback = callback @@ -602,6 +717,7 @@ self.in_try_finally = 0 self.exc_vars = None + self.current_except = None self.can_trace = False self.gil_owned = True @@ -632,8 +748,8 @@ label += '_' + name return label - def new_yield_label(self): - label = self.new_label('resume_from_yield') + def new_yield_label(self, expr_type='yield'): + label = self.new_label('resume_from_%s' % expr_type) num_and_label = (len(self.yield_labels) + 1, label) self.yield_labels.append(num_and_label) return num_and_label @@ -790,9 +906,11 @@ try-except and try-finally blocks to clean up temps in the error case. """ - return [(cname, type) - for (type, manage_ref), freelist in self.temps_free.items() if manage_ref - for cname in freelist[0]] + return sorted([ # Enforce deterministic order. + (cname, type) + for (type, manage_ref), freelist in self.temps_free.items() if manage_ref + for cname in freelist[0] + ]) def start_collecting_temps(self): """ @@ -988,6 +1106,7 @@ 'global_var', 'string_decls', 'decls', + 'late_includes', 'all_the_rest', 'pystring_table', 'cached_builtins', @@ -1017,10 +1136,12 @@ self.const_cnames_used = {} self.string_const_index = {} + self.dedup_const_index = {} self.pyunicode_ptr_const_index = {} self.num_const_index = {} self.py_constants = [] self.cached_cmethods = {} + self.initialised_constants = set() writer.set_global_state(self) self.rootwriter = writer @@ -1035,19 +1156,19 @@ else: w = self.parts['cached_builtins'] w.enter_cfunc_scope() - w.putln("static int __Pyx_InitCachedBuiltins(void) {") + w.putln("static CYTHON_SMALL_CODE int __Pyx_InitCachedBuiltins(void) {") w = self.parts['cached_constants'] w.enter_cfunc_scope() w.putln("") - w.putln("static int __Pyx_InitCachedConstants(void) {") + w.putln("static CYTHON_SMALL_CODE int __Pyx_InitCachedConstants(void) {") w.put_declare_refcount_context() w.put_setup_refcount_context("__Pyx_InitCachedConstants") w = self.parts['init_globals'] w.enter_cfunc_scope() w.putln("") - w.putln("static int __Pyx_InitGlobals(void) {") + w.putln("static CYTHON_SMALL_CODE int __Pyx_InitGlobals(void) {") if not Options.generate_cleanup_code: del self.parts['cleanup_globals'] @@ -1055,7 +1176,7 @@ w = self.parts['cleanup_globals'] w.enter_cfunc_scope() w.putln("") - w.putln("static void __Pyx_CleanupGlobals(void) {") + w.putln("static CYTHON_SMALL_CODE void __Pyx_CleanupGlobals(void) {") code = self.parts['utility_code_proto'] code.putln("") @@ -1130,7 +1251,12 @@ # constant handling at code generation time - def get_cached_constants_writer(self): + def get_cached_constants_writer(self, target=None): + if target is not None: + if target in self.initialised_constants: + # Return None on second/later calls to prevent duplicate creation code. + return None + self.initialised_constants.add(target) return self.parts['cached_constants'] def get_int_const(self, str_value, longness=False): @@ -1148,13 +1274,19 @@ c = self.new_num_const(str_value, 'float', value_code) return c - def get_py_const(self, type, prefix='', cleanup_level=None): + def get_py_const(self, type, prefix='', cleanup_level=None, dedup_key=None): + if dedup_key is not None: + const = self.dedup_const_index.get(dedup_key) + if const is not None: + return const # create a new Python object constant const = self.new_py_const(type, prefix) if cleanup_level is not None \ and cleanup_level <= Options.generate_cleanup_code: cleanup_writer = self.parts['cleanup_globals'] cleanup_writer.putln('Py_CLEAR(%s);' % const.cname) + if dedup_key is not None: + self.dedup_const_index[dedup_key] = const return const def get_string_const(self, text, py_version=None): @@ -1242,8 +1374,8 @@ prefix = Naming.const_prefix return "%s%s" % (prefix, name_suffix) - def get_cached_unbound_method(self, type_cname, method_name, args_count): - key = (type_cname, method_name, args_count) + def get_cached_unbound_method(self, type_cname, method_name): + key = (type_cname, method_name) try: cname = self.cached_cmethods[key] except KeyError: @@ -1251,6 +1383,18 @@ 'umethod', '%s_%s' % (type_cname, method_name)) return cname + def cached_unbound_method_call_code(self, obj_cname, type_cname, method_name, arg_cnames): + # admittedly, not the best place to put this method, but it is reused by UtilityCode and ExprNodes ... + utility_code_name = "CallUnboundCMethod%d" % len(arg_cnames) + self.use_utility_code(UtilityCode.load_cached(utility_code_name, "ObjectHandling.c")) + cache_cname = self.get_cached_unbound_method(type_cname, method_name) + args = [obj_cname] + arg_cnames + return "__Pyx_%s(&%s, %s)" % ( + utility_code_name, + cache_cname, + ', '.join(args), + ) + def add_cached_builtin_decl(self, entry): if entry.is_builtin and entry.is_const: if self.should_declare(entry.cname, entry): @@ -1303,7 +1447,7 @@ decl = self.parts['decls'] init = self.parts['init_globals'] cnames = [] - for (type_cname, method_name, _), cname in sorted(self.cached_cmethods.items()): + for (type_cname, method_name), cname in sorted(self.cached_cmethods.items()): cnames.append(cname) method_name_cname = self.get_interned_identifier(StringEncoding.EncodedString(method_name)).cname decl.putln('static __Pyx_CachedCFunction %s = {0, &%s, 0, 0, 0};' % ( @@ -1493,7 +1637,8 @@ self.use_utility_code(entry.utility_code_definition) -def funccontext_property(name): +def funccontext_property(func): + name = func.__name__ attribute_of = operator.attrgetter(name) def get(self): return attribute_of(self.funcstate) @@ -1523,7 +1668,7 @@ as well - labels, temps, exc_vars: One must construct a scope in which these can exist by calling enter_cfunc_scope/exit_cfunc_scope (these are for - sanity checking and forward compatabilty). Created insertion points + sanity checking and forward compatibility). Created insertion points looses this scope and cannot access it. - marker: Not copied to insertion point - filename_table, filename_list, input_file_contents: All codewriters @@ -1544,8 +1689,7 @@ # about the current class one is in # code_config CCodeConfig configuration options for the C code writer - globalstate = code_config = None - + @cython.locals(create_from='CCodeWriter') def __init__(self, create_from=None, buffer=None, copy_formatting=False): if buffer is None: buffer = StringIOTree() self.buffer = buffer @@ -1554,6 +1698,8 @@ self.pyclass_stack = [] self.funcstate = None + self.globalstate = None + self.code_config = None self.level = 0 self.call_level = 0 self.bol = 1 @@ -1616,19 +1762,27 @@ self.buffer.insert(writer.buffer) # Properties delegated to function scope - label_counter = funccontext_property("label_counter") - return_label = funccontext_property("return_label") - error_label = funccontext_property("error_label") - labels_used = funccontext_property("labels_used") - continue_label = funccontext_property("continue_label") - break_label = funccontext_property("break_label") - return_from_error_cleanup_label = funccontext_property("return_from_error_cleanup_label") - yield_labels = funccontext_property("yield_labels") + @funccontext_property + def label_counter(self): pass + @funccontext_property + def return_label(self): pass + @funccontext_property + def error_label(self): pass + @funccontext_property + def labels_used(self): pass + @funccontext_property + def continue_label(self): pass + @funccontext_property + def break_label(self): pass + @funccontext_property + def return_from_error_cleanup_label(self): pass + @funccontext_property + def yield_labels(self): pass # Functions delegated to function scope def new_label(self, name=None): return self.funcstate.new_label(name) def new_error_label(self): return self.funcstate.new_error_label() - def new_yield_label(self): return self.funcstate.new_yield_label() + def new_yield_label(self, *args): return self.funcstate.new_yield_label(*args) def get_loop_labels(self): return self.funcstate.get_loop_labels() def set_loop_labels(self, labels): return self.funcstate.set_loop_labels(labels) def new_loop_labels(self): return self.funcstate.new_loop_labels() @@ -1653,8 +1807,8 @@ def get_py_float(self, str_value, value_code): return self.globalstate.get_float_const(str_value, value_code).cname - def get_py_const(self, type, prefix='', cleanup_level=None): - return self.globalstate.get_py_const(type, prefix, cleanup_level).cname + def get_py_const(self, type, prefix='', cleanup_level=None, dedup_key=None): + return self.globalstate.get_py_const(type, prefix, cleanup_level, dedup_key).cname def get_string_const(self, text): return self.globalstate.get_string_const(text).cname @@ -1676,8 +1830,8 @@ def intern_identifier(self, text): return self.get_py_string_const(text, identifier=True) - def get_cached_constants_writer(self): - return self.globalstate.get_cached_constants_writer() + def get_cached_constants_writer(self, target=None): + return self.globalstate.get_cached_constants_writer(target) # code generation @@ -1744,8 +1898,6 @@ self.put(code) def put(self, code): - if is_self_assignment(code): - return fix_indent = False if "{" in code: dl = code.count("{") @@ -1916,9 +2068,12 @@ if entry.type.is_pyobject: self.putln("__Pyx_XGIVEREF(%s);" % self.entry_as_pyobject(entry)) - def put_var_incref(self, entry): + def put_var_incref(self, entry, nanny=True): if entry.type.is_pyobject: - self.putln("__Pyx_INCREF(%s);" % self.entry_as_pyobject(entry)) + if nanny: + self.putln("__Pyx_INCREF(%s);" % self.entry_as_pyobject(entry)) + else: + self.putln("Py_INCREF(%s);" % self.entry_as_pyobject(entry)) def put_var_xincref(self, entry): if entry.type.is_pyobject: @@ -1942,8 +2097,8 @@ self.put_xdecref_memoryviewslice(cname, have_gil=have_gil) return - prefix = nanny and '__Pyx' or 'Py' - X = null_check and 'X' or '' + prefix = '__Pyx' if nanny else 'Py' + X = 'X' if null_check else '' if clear: if clear_before_decref: @@ -1967,9 +2122,12 @@ if entry.type.is_pyobject: self.putln("__Pyx_XDECREF(%s);" % self.entry_as_pyobject(entry)) - def put_var_xdecref(self, entry): + def put_var_xdecref(self, entry, nanny=True): if entry.type.is_pyobject: - self.putln("__Pyx_XDECREF(%s);" % self.entry_as_pyobject(entry)) + if nanny: + self.putln("__Pyx_XDECREF(%s);" % self.entry_as_pyobject(entry)) + else: + self.putln("Py_XDECREF(%s);" % self.entry_as_pyobject(entry)) def put_var_decref_clear(self, entry): self._put_var_decref_clear(entry, null_check=False) @@ -2036,7 +2194,7 @@ if entry.in_closure: self.put_giveref('Py_None') - def put_pymethoddef(self, entry, term, allow_skip=True): + def put_pymethoddef(self, entry, term, allow_skip=True, wrapper_code_writer=None): if entry.is_special or entry.name == '__getattribute__': if entry.name not in special_py_methods: if entry.name == '__getattr__' and not self.globalstate.directives['fast_getattr']: @@ -2046,22 +2204,38 @@ # that's better than ours. elif allow_skip: return - from .TypeSlots import method_coexist - if entry.doc: - doc_code = entry.doc_cname - else: - doc_code = 0 + method_flags = entry.signature.method_flags() - if method_flags: - if entry.is_special: - method_flags += [method_coexist] - self.putln( - '{"%s", (PyCFunction)%s, %s, %s}%s' % ( - entry.name, - entry.func_cname, - "|".join(method_flags), - doc_code, - term)) + if not method_flags: + return + if entry.is_special: + from . import TypeSlots + method_flags += [TypeSlots.method_coexist] + func_ptr = wrapper_code_writer.put_pymethoddef_wrapper(entry) if wrapper_code_writer else entry.func_cname + # Add required casts, but try not to shadow real warnings. + cast = '__Pyx_PyCFunctionFast' if 'METH_FASTCALL' in method_flags else 'PyCFunction' + if 'METH_KEYWORDS' in method_flags: + cast += 'WithKeywords' + if cast != 'PyCFunction': + func_ptr = '(void*)(%s)%s' % (cast, func_ptr) + self.putln( + '{"%s", (PyCFunction)%s, %s, %s}%s' % ( + entry.name, + func_ptr, + "|".join(method_flags), + entry.doc_cname if entry.doc else '0', + term)) + + def put_pymethoddef_wrapper(self, entry): + func_cname = entry.func_cname + if entry.is_special: + method_flags = entry.signature.method_flags() + if method_flags and 'METH_NOARGS' in method_flags: + # Special NOARGS methods really take no arguments besides 'self', but PyCFunction expects one. + func_cname = Naming.method_wrapper_prefix + func_cname + self.putln("static PyObject *%s(PyObject *self, CYTHON_UNUSED PyObject *arg) {return %s(self);}" % ( + func_cname, entry.func_cname)) + return func_cname # GIL methods @@ -2138,7 +2312,8 @@ # error handling def put_error_if_neg(self, pos, value): -# return self.putln("if (unlikely(%s < 0)) %s" % (value, self.error_goto(pos))) # TODO this path is almost _never_ taken, yet this macro makes is slower! + # TODO this path is almost _never_ taken, yet this macro makes is slower! + # return self.putln("if (unlikely(%s < 0)) %s" % (value, self.error_goto(pos))) return self.putln("if (%s < 0) %s" % (value, self.error_goto(pos))) def put_error_if_unbound(self, pos, entry, in_nogil_context=False): @@ -2182,6 +2357,8 @@ def error_goto(self, pos): lbl = self.funcstate.error_label self.funcstate.use_label(lbl) + if pos is None: + return 'goto %s;' % lbl return "__PYX_ERR(%s, %s, %s)" % ( self.lookup_filename(pos[0]), pos[1], @@ -2290,6 +2467,7 @@ self.putln(" #define unlikely(x) __builtin_expect(!!(x), 0)") self.putln("#endif") + class PyrexCodeWriter(object): # f file output file # level int indentation level diff -Nru cython-0.26.1/Cython/Compiler/CythonScope.py cython-0.29.14/Cython/Compiler/CythonScope.py --- cython-0.26.1/Cython/Compiler/CythonScope.py 2016-12-10 15:41:15.000000000 +0000 +++ cython-0.29.14/Cython/Compiler/CythonScope.py 2018-09-22 14:18:56.000000000 +0000 @@ -26,6 +26,10 @@ cname='') entry.in_cinclude = True + def is_cpp(self): + # Allow C++ utility code in C++ contexts. + return self.context.cpp + def lookup_type(self, name): # This function should go away when types are all first-level objects. type = parse_basic_type(name) diff -Nru cython-0.26.1/Cython/Compiler/Errors.py cython-0.29.14/Cython/Compiler/Errors.py --- cython-0.26.1/Cython/Compiler/Errors.py 2017-08-12 14:06:59.000000000 +0000 +++ cython-0.29.14/Cython/Compiler/Errors.py 2018-09-22 14:18:56.000000000 +0000 @@ -10,6 +10,7 @@ any_string_type = (bytes, str) import sys +from contextlib import contextmanager from ..Utils import open_new_file from . import DebugFlags @@ -228,19 +229,34 @@ error_stack = [] + def hold_errors(): error_stack.append([]) + def release_errors(ignore=False): held_errors = error_stack.pop() if not ignore: for err in held_errors: report_error(err) + def held_errors(): return error_stack[-1] +# same as context manager: + +@contextmanager +def local_errors(ignore=False): + errors = [] + error_stack.append(errors) + try: + yield errors + finally: + release_errors(ignore=ignore) + + # this module needs a redesign to support parallel cythonisation, but # for now, the following works at least in sequential compiler runs diff -Nru cython-0.26.1/Cython/Compiler/ExprNodes.py cython-0.29.14/Cython/Compiler/ExprNodes.py --- cython-0.26.1/Cython/Compiler/ExprNodes.py 2017-08-25 16:06:31.000000000 +0000 +++ cython-0.29.14/Cython/Compiler/ExprNodes.py 2019-05-28 19:54:08.000000000 +0000 @@ -7,7 +7,7 @@ import cython cython.declare(error=object, warning=object, warn_once=object, InternalError=object, CompileError=object, UtilityCode=object, TempitaUtilityCode=object, - StringEncoding=object, operator=object, + StringEncoding=object, operator=object, local_errors=object, report_error=object, Naming=object, Nodes=object, PyrexTypes=object, py_object_type=object, list_type=object, tuple_type=object, set_type=object, dict_type=object, unicode_type=object, str_type=object, bytes_type=object, type_type=object, @@ -16,18 +16,19 @@ bytearray_type=object, slice_type=object, _py_int_types=object, IS_PYTHON3=cython.bint) +import re import sys import copy import os.path import operator -from .Errors import error, warning, warn_once, InternalError, CompileError -from .Errors import hold_errors, release_errors, held_errors, report_error +from .Errors import ( + error, warning, InternalError, CompileError, report_error, local_errors) from .Code import UtilityCode, TempitaUtilityCode from . import StringEncoding from . import Naming from . import Nodes -from .Nodes import Node, utility_code_for_imports +from .Nodes import Node, utility_code_for_imports, analyse_type_annotation from . import PyrexTypes from .PyrexTypes import py_object_type, c_long_type, typecast, error_type, \ unspecified_type @@ -42,9 +43,10 @@ from ..Debugging import print_call_chain from .DebugFlags import debug_disposal_code, debug_temp_alloc, \ debug_coercion -from .Pythran import to_pythran, is_pythran_supported_type, is_pythran_supported_operation_type, \ - is_pythran_expr, pythran_func_type, pythran_binop_type, pythran_unaryop_type, has_np_pythran, \ - pythran_indexing_code, pythran_indexing_type, is_pythran_supported_node_or_none, pythran_type +from .Pythran import (to_pythran, is_pythran_supported_type, is_pythran_supported_operation_type, + is_pythran_expr, pythran_func_type, pythran_binop_type, pythran_unaryop_type, has_np_pythran, + pythran_indexing_code, pythran_indexing_type, is_pythran_supported_node_or_none, pythran_type, + pythran_is_numpy_func_supported, pythran_get_func_include_file, pythran_functor) from .PyrexTypes import PythranExpr try: @@ -185,20 +187,70 @@ return item_types.pop() return None + +def make_dedup_key(outer_type, item_nodes): + """ + Recursively generate a deduplication key from a sequence of values. + Includes Cython node types to work around the fact that (1, 2.0) == (1.0, 2), for example. + + @param outer_type: The type of the outer container. + @param item_nodes: A sequence of constant nodes that will be traversed recursively. + @return: A tuple that can be used as a dict key for deduplication. + """ + item_keys = [ + (py_object_type, None, type(None)) if node is None + # For sequences and their "mult_factor", see TupleNode. + else make_dedup_key(node.type, [node.mult_factor if node.is_literal else None] + node.args) if node.is_sequence_constructor + else make_dedup_key(node.type, (node.start, node.stop, node.step)) if node.is_slice + # For constants, look at the Python value type if we don't know the concrete Cython type. + else (node.type, node.constant_result, + type(node.constant_result) if node.type is py_object_type else None) if node.has_constant_result() + else None # something we cannot handle => short-circuit below + for node in item_nodes + ] + if None in item_keys: + return None + return outer_type, tuple(item_keys) + + +# Returns a block of code to translate the exception, +# plus a boolean indicating whether to check for Python exceptions. def get_exception_handler(exception_value): if exception_value is None: - return "__Pyx_CppExn2PyErr();" + return "__Pyx_CppExn2PyErr();", False + elif (exception_value.type == PyrexTypes.c_char_type + and exception_value.value == '*'): + return "__Pyx_CppExn2PyErr();", True elif exception_value.type.is_pyobject: - return 'try { throw; } catch(const std::exception& exn) { PyErr_SetString(%s, exn.what()); } catch(...) { PyErr_SetNone(%s); }' % ( - exception_value.entry.cname, - exception_value.entry.cname) + return ( + 'try { throw; } catch(const std::exception& exn) {' + 'PyErr_SetString(%s, exn.what());' + '} catch(...) { PyErr_SetNone(%s); }' % ( + exception_value.entry.cname, + exception_value.entry.cname), + False) else: - return '%s(); if (!PyErr_Occurred()) PyErr_SetString(PyExc_RuntimeError , "Error converting c++ exception.");' % exception_value.entry.cname + return ( + '%s(); if (!PyErr_Occurred())' + 'PyErr_SetString(PyExc_RuntimeError, ' + '"Error converting c++ exception.");' % ( + exception_value.entry.cname), + False) + +def maybe_check_py_error(code, check_py_exception, pos, nogil): + if check_py_exception: + if nogil: + code.putln(code.error_goto_if("__Pyx_ErrOccurredWithGIL()", pos)) + else: + code.putln(code.error_goto_if("PyErr_Occurred()", pos)) -def translate_cpp_exception(code, pos, inside, exception_value, nogil): - raise_py_exception = get_exception_handler(exception_value) +def translate_cpp_exception(code, pos, inside, py_result, exception_value, nogil): + raise_py_exception, check_py_exception = get_exception_handler(exception_value) code.putln("try {") code.putln("%s" % inside) + if py_result: + code.putln(code.error_goto_if_null(py_result, pos)) + maybe_check_py_error(code, check_py_exception, pos, nogil) code.putln("} catch(...) {") if nogil: code.put_ensure_gil(declare_gilstate=True) @@ -212,12 +264,14 @@ # both have an exception declaration. def translate_double_cpp_exception(code, pos, lhs_type, lhs_code, rhs_code, lhs_exc_val, assign_exc_val, nogil): - handle_lhs_exc = get_exception_handler(lhs_exc_val) - handle_assignment_exc = get_exception_handler(assign_exc_val) + handle_lhs_exc, lhc_check_py_exc = get_exception_handler(lhs_exc_val) + handle_assignment_exc, assignment_check_py_exc = get_exception_handler(assign_exc_val) code.putln("try {") code.putln(lhs_type.declaration_code("__pyx_local_lvalue = %s;" % lhs_code)) + maybe_check_py_error(code, lhc_check_py_exc, pos, nogil) code.putln("try {") code.putln("__pyx_local_lvalue = %s;" % rhs_code) + maybe_check_py_error(code, assignment_check_py_exc, pos, nogil) # Catch any exception from the overloaded assignment. code.putln("} catch(...) {") if nogil: @@ -254,9 +308,11 @@ # result_is_used boolean indicates that the result will be dropped and the # is_numpy_attribute boolean Is a Numpy module attribute # result_code/temp_result can safely be set to None + # annotation ExprNode or None PEP526 annotation for names or expressions result_ctype = None type = None + annotation = None temp_code = None old_temp = None # error checker for multiple frees etc. use_managed_ref = True # can be set by optimisation transforms @@ -847,6 +903,9 @@ if src_type.is_fused: error(self.pos, "Type is not specialized") + elif src_type.is_null_ptr and dst_type.is_ptr: + # NULL can be implicitly cast to any pointer type + return self else: error(self.pos, "Cannot coerce to a type that is not specialized") @@ -868,16 +927,19 @@ elif not src_type.is_error: error(self.pos, "Cannot convert '%s' to memoryviewslice" % (src_type,)) - elif not src.type.conforms_to(dst_type, broadcast=self.is_memview_broadcast, - copying=self.is_memview_copy_assignment): - if src.type.dtype.same_as(dst_type.dtype): - msg = "Memoryview '%s' not conformable to memoryview '%s'." - tup = src.type, dst_type - else: - msg = "Different base types for memoryviews (%s, %s)" - tup = src.type.dtype, dst_type.dtype + else: + if src.type.writable_needed: + dst_type.writable_needed = True + if not src.type.conforms_to(dst_type, broadcast=self.is_memview_broadcast, + copying=self.is_memview_copy_assignment): + if src.type.dtype.same_as(dst_type.dtype): + msg = "Memoryview '%s' not conformable to memoryview '%s'." + tup = src.type, dst_type + else: + msg = "Different base types for memoryviews (%s, %s)" + tup = src.type.dtype, dst_type.dtype - error(self.pos, msg % tup) + error(self.pos, msg % tup) elif dst_type.is_pyobject: if not src.type.is_pyobject: @@ -1079,6 +1141,12 @@ def may_be_none(self): return True + def coerce_to(self, dst_type, env): + if not (dst_type.is_pyobject or dst_type.is_memoryviewslice or dst_type.is_error): + # Catch this error early and loudly. + error(self.pos, "Cannot assign None to %s" % dst_type) + return super(NoneNode, self).coerce_to(dst_type, env) + class EllipsisNode(PyConstNode): # '...' in a subscript list. @@ -1141,6 +1209,10 @@ return str(int(self.value)) def coerce_to(self, dst_type, env): + if dst_type == self.type: + return self + if dst_type is py_object_type and self.type is Builtin.bool_type: + return self if dst_type.is_pyobject and self.type.is_int: return BoolNode( self.pos, value=self.value, @@ -1360,19 +1432,28 @@ type = PyrexTypes.parse_basic_type(name) if type is not None: return type - hold_errors() + + global_entry = env.global_scope().lookup(name) + if global_entry and global_entry.type and ( + global_entry.type.is_extension_type + or global_entry.type.is_struct_or_union + or global_entry.type.is_builtin_type + or global_entry.type.is_cpp_class): + return global_entry.type + from .TreeFragment import TreeFragment - pos = (pos[0], pos[1], pos[2]-7) - try: - declaration = TreeFragment(u"sizeof(%s)" % name, name=pos[0].filename, initial_pos=pos) - except CompileError: - sizeof_node = None - else: - sizeof_node = declaration.root.stats[0].expr - sizeof_node = sizeof_node.analyse_types(env) - release_errors(ignore=True) - if isinstance(sizeof_node, SizeofTypeNode): - return sizeof_node.arg_type + with local_errors(ignore=True): + pos = (pos[0], pos[1], pos[2]-7) + try: + declaration = TreeFragment(u"sizeof(%s)" % name, name=pos[0].filename, initial_pos=pos) + except CompileError: + pass + else: + sizeof_node = declaration.root.stats[0].expr + if isinstance(sizeof_node, SizeofTypeNode): + sizeof_node = sizeof_node.analyse_types(env) + if isinstance(sizeof_node, SizeofTypeNode): + return sizeof_node.arg_type return None @@ -1426,7 +1507,7 @@ node.type = Builtin.bytes_type else: self.check_for_coercion_error(dst_type, env, fail=True) - return node + return node elif dst_type in (PyrexTypes.c_char_ptr_type, PyrexTypes.c_const_char_ptr_type): node.type = dst_type return node @@ -1435,8 +1516,10 @@ else PyrexTypes.c_char_ptr_type) return CastNode(node, dst_type) elif dst_type.assignable_from(PyrexTypes.c_char_ptr_type): - node.type = dst_type - return node + # Exclude the case of passing a C string literal into a non-const C++ string. + if not dst_type.is_cpp_class or dst_type.is_const: + node.type = dst_type + return node # We still need to perform normal coerce_to processing on the # result, because we might be coercing to an extension type, @@ -1545,15 +1628,17 @@ # decoded by the UTF-8 codec in Py3.3 self.result_code = code.get_py_const(py_object_type, 'ustring') data_cname = code.get_pyunicode_ptr_const(self.value) - code = code.get_cached_constants_writer() - code.mark_pos(self.pos) - code.putln( + const_code = code.get_cached_constants_writer(self.result_code) + if const_code is None: + return # already initialised + const_code.mark_pos(self.pos) + const_code.putln( "%s = PyUnicode_FromUnicode(%s, (sizeof(%s) / sizeof(Py_UNICODE))-1); %s" % ( self.result_code, data_cname, data_cname, - code.error_goto_if_null(self.result_code, self.pos))) - code.put_error_if_neg( + const_code.error_goto_if_null(self.result_code, self.pos))) + const_code.put_error_if_neg( self.pos, "__Pyx_PyUnicode_READY(%s)" % self.result_code) else: self.result_code = code.get_py_string_const(self.value) @@ -1711,12 +1796,7 @@ self.type = error_type return self.cpp_check(env) - constructor = type.scope.lookup(u'') - if constructor is None: - func_type = PyrexTypes.CFuncType( - type, [], exception_check='+', nogil=True) - type.scope.declare_cfunction(u'', func_type, self.pos) - constructor = type.scope.lookup(u'') + constructor = type.get_constructor(self.pos) self.class_type = type self.entry = constructor self.type = constructor.type @@ -1830,6 +1910,34 @@ return super(NameNode, self).coerce_to(dst_type, env) + def declare_from_annotation(self, env, as_target=False): + """Implements PEP 526 annotation typing in a fairly relaxed way. + + Annotations are ignored for global variables, Python class attributes and already declared variables. + String literals are allowed and ignored. + The ambiguous Python types 'int' and 'long' are ignored and the 'cython.int' form must be used instead. + """ + if not env.directives['annotation_typing']: + return + if env.is_module_scope or env.is_py_class_scope: + # annotations never create global cdef names and Python classes don't support them anyway + return + name = self.name + if self.entry or env.lookup_here(name) is not None: + # already declared => ignore annotation + return + + annotation = self.annotation + if annotation.is_string_literal: + # name: "description" => not a type, but still a declared variable or attribute + atype = None + else: + _, atype = analyse_type_annotation(annotation, env) + if atype is None: + atype = unspecified_type if as_target and env.directives['infer_types'] != False else py_object_type + self.entry = env.declare_var(name, atype, self.pos, is_cdef=not as_target) + self.entry.annotation = annotation + def analyse_as_module(self, env): # Try to interpret this as a reference to a cimported module. # Returns the module scope, or None. @@ -1869,6 +1977,9 @@ def analyse_target_declaration(self, env): if not self.entry: self.entry = env.lookup_here(self.name) + if not self.entry and self.annotation is not None: + # name : type = ... + self.declare_from_annotation(env, as_target=True) if not self.entry: if env.directives['warn.undeclared']: warning(self.pos, "implicit declaration of '%s'" % self.name, 1) @@ -1885,19 +1996,21 @@ def analyse_types(self, env): self.initialized_check = env.directives['initializedcheck'] - if self.entry is None: - self.entry = env.lookup(self.name) - if not self.entry: - self.entry = env.declare_builtin(self.name, self.pos) - if not self.entry: - self.type = PyrexTypes.error_type - return self entry = self.entry - if entry: - entry.used = 1 - if entry.type.is_buffer: - from . import Buffer - Buffer.used_buffer_aux_vars(entry) + if entry is None: + entry = env.lookup(self.name) + if not entry: + entry = env.declare_builtin(self.name, self.pos) + if entry and entry.is_builtin and entry.is_const: + self.is_literal = True + if not entry: + self.type = PyrexTypes.error_type + return self + self.entry = entry + entry.used = 1 + if entry.type.is_buffer: + from . import Buffer + Buffer.used_buffer_aux_vars(entry) self.analyse_rvalue_entry(env) return self @@ -1982,14 +2095,13 @@ py_entry.is_pyglobal = True py_entry.scope = self.entry.scope self.entry = py_entry - elif not (entry.is_const or entry.is_variable - or entry.is_builtin or entry.is_cfunction - or entry.is_cpp_class): - if self.entry.as_variable: - self.entry = self.entry.as_variable - elif not self.is_cython_module: - error(self.pos, - "'%s' is not a constant, variable or function identifier" % self.name) + elif not (entry.is_const or entry.is_variable or + entry.is_builtin or entry.is_cfunction or + entry.is_cpp_class): + if self.entry.as_variable: + self.entry = self.entry.as_variable + elif not self.is_cython_module: + error(self.pos, "'%s' is not a constant, variable or function identifier" % self.name) def is_cimported_module_without_shadow(self, env): if self.is_cython_module or self.cython_attribute: @@ -2035,7 +2147,11 @@ def check_const(self): entry = self.entry - if entry is not None and not (entry.is_const or entry.is_cfunction or entry.is_builtin): + if entry is not None and not ( + entry.is_const or + entry.is_cfunction or + entry.is_builtin or + entry.type.is_const): self.not_const() return False return True @@ -2075,6 +2191,8 @@ entry = self.entry if entry is None: return # There was an error earlier + if entry.utility_code: + code.globalstate.use_utility_code(entry.utility_code) if entry.is_builtin and entry.is_const: return # Lookup already cached elif entry.is_pyclass_attr: @@ -2095,7 +2213,7 @@ code.globalstate.use_utility_code( UtilityCode.load_cached("GetModuleGlobalName", "ObjectHandling.c")) code.putln( - '%s = __Pyx_GetModuleGlobalName(%s);' % ( + '__Pyx_GetModuleGlobalName(%s, %s);' % ( self.result(), interned_cname)) if not self.cf_is_null: @@ -2124,7 +2242,7 @@ code.globalstate.use_utility_code( UtilityCode.load_cached("GetModuleGlobalName", "ObjectHandling.c")) code.putln( - '%s = __Pyx_GetModuleGlobalName(%s); %s' % ( + '__Pyx_GetModuleGlobalName(%s, %s); %s' % ( self.result(), interned_cname, code.error_goto_if_null(self.result(), self.pos))) @@ -2133,7 +2251,7 @@ code.globalstate.use_utility_code( UtilityCode.load_cached("GetNameInClass", "ObjectHandling.c")) code.putln( - '%s = __Pyx_GetNameInClass(%s, %s); %s' % ( + '__Pyx_GetNameInClass(%s, %s, %s); %s' % ( self.result(), entry.scope.namespace_cname, interned_cname, @@ -2177,7 +2295,8 @@ setter = 'PyDict_SetItem' namespace = Naming.moddict_cname elif entry.is_pyclass_attr: - setter = 'PyObject_SetItem' + code.globalstate.use_utility_code(UtilityCode.load_cached("SetNameInClass", "ObjectHandling.c")) + setter = '__Pyx_SetNameInClass' else: assert False, repr(entry) code.put_error_if_neg( @@ -2245,12 +2364,20 @@ if overloaded_assignment: result = rhs.result() if exception_check == '+': - translate_cpp_exception(code, self.pos, '%s = %s;' % (self.result(), result), exception_value, self.in_nogil_context) + translate_cpp_exception( + code, self.pos, + '%s = %s;' % (self.result(), result), + self.result() if self.type.is_pyobject else None, + exception_value, self.in_nogil_context) else: code.putln('%s = %s;' % (self.result(), result)) else: result = rhs.result_as(self.ctype()) - code.putln('%s = %s;' % (self.result(), result)) + + if is_pythran_expr(self.type): + code.putln('new (&%s) decltype(%s){%s};' % (self.result(), self.result(), result)) + elif result != self.result(): + code.putln('%s = %s;' % (self.result(), result)) if debug_disposal_code: print("NameNode.generate_assignment_code:") print("...generating post-assignment code for %s" % rhs) @@ -2700,8 +2827,7 @@ code.putln("if (unlikely(!%s)) {" % result_name) code.putln("PyObject* exc_type = PyErr_Occurred();") code.putln("if (exc_type) {") - code.putln("if (likely(exc_type == PyExc_StopIteration ||" - " PyErr_GivenExceptionMatches(exc_type, PyExc_StopIteration))) PyErr_Clear();") + code.putln("if (likely(__Pyx_PyErr_GivenExceptionMatches(exc_type, PyExc_StopIteration))) PyErr_Clear();") code.putln("else %s" % code.error_goto(self.pos)) code.putln("}") code.putln("break;") @@ -2835,18 +2961,18 @@ # The __exit__() call of a 'with' statement. Used in both the # except and finally clauses. - # with_stat WithStatNode the surrounding 'with' statement - # args TupleNode or ResultStatNode the exception info tuple - # await AwaitExprNode the await expression of an 'async with' statement + # with_stat WithStatNode the surrounding 'with' statement + # args TupleNode or ResultStatNode the exception info tuple + # await_expr AwaitExprNode the await expression of an 'async with' statement - subexprs = ['args', 'await'] + subexprs = ['args', 'await_expr'] test_if_run = True - await = None + await_expr = None def analyse_types(self, env): self.args = self.args.analyse_types(env) - if self.await: - self.await = self.await.analyse_types(env) + if self.await_expr: + self.await_expr = self.await_expr.analyse_types(env) self.type = PyrexTypes.c_bint_type self.is_temp = True return self @@ -2873,12 +2999,12 @@ code.putln(code.error_goto_if_null(result_var, self.pos)) code.put_gotref(result_var) - if self.await: + if self.await_expr: # FIXME: result_var temp currently leaks into the closure - self.await.generate_evaluation_code(code, source_cname=result_var, decref_source=True) - code.putln("%s = %s;" % (result_var, self.await.py_result())) - self.await.generate_post_assignment_code(code) - self.await.free_temps(code) + self.await_expr.generate_evaluation_code(code, source_cname=result_var, decref_source=True) + code.putln("%s = %s;" % (result_var, self.await_expr.py_result())) + self.await_expr.generate_post_assignment_code(code) + self.await_expr.free_temps(code) if self.result_is_used: self.allocate_temp_result(code) @@ -3038,12 +3164,27 @@ is_ascii = False if isinstance(node, UnicodeNode): try: + # most strings will be ASCII or at least Latin-1 node.value.encode('iso8859-1') max_char_value = '255' node.value.encode('us-ascii') is_ascii = True except UnicodeEncodeError: - pass + if max_char_value != '255': + # not ISO8859-1 => check BMP limit + max_char = max(map(ord, node.value)) + if max_char < 0xD800: + # BMP-only, no surrogate pairs used + max_char_value = '65535' + ulength = str(len(node.value)) + elif max_char >= 65536: + # cleary outside of BMP, and not on a 16-bit Unicode system + max_char_value = '1114111' + ulength = str(len(node.value)) + else: + # not really worth implementing a check for surrogate pairs here + # drawback: C code can differ when generating on Py2 with 2-byte Unicode + pass else: ulength = str(len(node.value)) elif isinstance(node, FormattedValueNode) and node.value.type.is_numeric: @@ -3092,7 +3233,7 @@ c_format_spec = None find_conversion_func = { - 's': 'PyObject_Str', + 's': 'PyObject_Unicode', 'r': 'PyObject_Repr', 'a': 'PyObject_ASCII', # NOTE: mapped to PyObject_Repr() in Py2 }.get @@ -3112,7 +3253,7 @@ self.format_spec = self.format_spec.analyse_types(env).coerce_to_pyobject(env) if self.c_format_spec is None: self.value = self.value.coerce_to_pyobject(env) - if not self.format_spec and not self.conversion_char: + if not self.format_spec and (not self.conversion_char or self.conversion_char == 's'): if self.value.type is unicode_type and not self.value.may_be_none(): # value is definitely a unicode string and we don't format it any special return self.value @@ -3242,7 +3383,7 @@ # in most cases, indexing will return a safe reference to an object in a container, # so we consider the result safe if the base object is return self.base.is_ephemeral() or self.base.type in ( - basestring_type, str_type, bytes_type, unicode_type) + basestring_type, str_type, bytes_type, bytearray_type, unicode_type) def check_const_addr(self): return self.base.check_const_addr() and self.index.check_const() @@ -3279,10 +3420,6 @@ is_subscript = True is_fused_index = False - def __init__(self, pos, index, **kw): - ExprNode.__init__(self, pos, index=index, **kw) - self._index = index - def calculate_constant_result(self): self.constant_result = self.base.constant_result[self.index.constant_result] @@ -3306,7 +3443,7 @@ return False if isinstance(self.index, SliceNode): # slicing! - if base_type in (bytes_type, str_type, unicode_type, + if base_type in (bytes_type, bytearray_type, str_type, unicode_type, basestring_type, list_type, tuple_type): return False return ExprNode.may_be_none(self) @@ -3327,10 +3464,22 @@ positional_args=template_values, keyword_args=None) return type_node.analyse(env, base_type=base_type) + elif self.index.is_slice or self.index.is_sequence_constructor: + # memory view + from . import MemoryView + env.use_utility_code(MemoryView.view_utility_code) + axes = [self.index] if self.index.is_slice else list(self.index.args) + return PyrexTypes.MemoryViewSliceType(base_type, MemoryView.get_axes_specs(env, axes)) else: + # C array index = self.index.compile_time_value(env) if index is not None: - return PyrexTypes.CArrayType(base_type, int(index)) + try: + index = int(index) + except (ValueError, TypeError): + pass + else: + return PyrexTypes.CArrayType(base_type, index) error(self.pos, "Array size must be a compile time constant") return None @@ -3406,6 +3555,10 @@ if index_func is not None: return index_func.type.return_type + if is_pythran_expr(base_type) and is_pythran_expr(index_type): + index_with_type = (self.index, index_type) + return PythranExpr(pythran_indexing_type(base_type, [index_with_type])) + # may be slicing or indexing, we don't know if base_type in (unicode_type, str_type): # these types always returns their own type on Python indexing/slicing @@ -3531,7 +3684,7 @@ else: # not using 'uchar' to enable fast and safe error reporting as '-1' self.type = PyrexTypes.c_int_type - elif is_slice and base_type in (bytes_type, str_type, unicode_type, list_type, tuple_type): + elif is_slice and base_type in (bytes_type, bytearray_type, str_type, unicode_type, list_type, tuple_type): self.type = base_type else: item_type = None @@ -3632,23 +3785,33 @@ else: indices = [self.index] - base_type = self.base.type + base = self.base + base_type = base.type replacement_node = None if base_type.is_memoryviewslice: # memoryviewslice indexing or slicing from . import MemoryView + if base.is_memview_slice: + # For memory views, "view[i][j]" is the same as "view[i, j]" => use the latter for speed. + merged_indices = base.merged_indices(indices) + if merged_indices is not None: + base = base.base + base_type = base.type + indices = merged_indices have_slices, indices, newaxes = MemoryView.unellipsify(indices, base_type.ndim) if have_slices: - replacement_node = MemoryViewSliceNode(self.pos, indices=indices, base=self.base) + replacement_node = MemoryViewSliceNode(self.pos, indices=indices, base=base) else: - replacement_node = MemoryViewIndexNode(self.pos, indices=indices, base=self.base) + replacement_node = MemoryViewIndexNode(self.pos, indices=indices, base=base) elif base_type.is_buffer or base_type.is_pythran_expr: if base_type.is_pythran_expr or len(indices) == base_type.ndim: # Buffer indexing is_buffer_access = True indices = [index.analyse_types(env) for index in indices] if base_type.is_pythran_expr: - do_replacement = all(index.type.is_int or index.is_slice or index.type.is_pythran_expr for index in indices) + do_replacement = all( + index.type.is_int or index.is_slice or index.type.is_pythran_expr + for index in indices) if do_replacement: for i,index in enumerate(indices): if index.is_slice: @@ -3658,7 +3821,7 @@ else: do_replacement = all(index.type.is_int for index in indices) if do_replacement: - replacement_node = BufferIndexNode(self.pos, indices=indices, base=self.base) + replacement_node = BufferIndexNode(self.pos, indices=indices, base=base) # On cloning, indices is cloned. Otherwise, unpack index into indices. assert not isinstance(self.index, CloneNode) @@ -3825,6 +3988,8 @@ if not self.is_temp: # all handled in self.calculate_result_code() return + + utility_code = None if self.type.is_pyobject: error_value = 'NULL' if self.index.type.is_int: @@ -3834,32 +3999,38 @@ function = "__Pyx_GetItemInt_Tuple" else: function = "__Pyx_GetItemInt" - code.globalstate.use_utility_code( - TempitaUtilityCode.load_cached("GetItemInt", "ObjectHandling.c")) + utility_code = TempitaUtilityCode.load_cached("GetItemInt", "ObjectHandling.c") else: if self.base.type is dict_type: function = "__Pyx_PyDict_GetItem" - code.globalstate.use_utility_code( - UtilityCode.load_cached("DictGetItem", "ObjectHandling.c")) + utility_code = UtilityCode.load_cached("DictGetItem", "ObjectHandling.c") + elif self.base.type is py_object_type and self.index.type in (str_type, unicode_type): + # obj[str] is probably doing a dict lookup + function = "__Pyx_PyObject_Dict_GetItem" + utility_code = UtilityCode.load_cached("DictGetItem", "ObjectHandling.c") else: - function = "PyObject_GetItem" + function = "__Pyx_PyObject_GetItem" + code.globalstate.use_utility_code( + TempitaUtilityCode.load_cached("GetItemInt", "ObjectHandling.c")) + utility_code = UtilityCode.load_cached("ObjectGetItem", "ObjectHandling.c") elif self.type.is_unicode_char and self.base.type is unicode_type: assert self.index.type.is_int function = "__Pyx_GetItemInt_Unicode" error_value = '(Py_UCS4)-1' - code.globalstate.use_utility_code( - UtilityCode.load_cached("GetItemIntUnicode", "StringTools.c")) + utility_code = UtilityCode.load_cached("GetItemIntUnicode", "StringTools.c") elif self.base.type is bytearray_type: assert self.index.type.is_int assert self.type.is_int function = "__Pyx_GetItemInt_ByteArray" error_value = '-1' - code.globalstate.use_utility_code( - UtilityCode.load_cached("GetItemIntByteArray", "StringTools.c")) + utility_code = UtilityCode.load_cached("GetItemIntByteArray", "StringTools.c") elif not (self.base.type.is_cpp_class and self.exception_check): assert False, "unexpected type %s and base type %s for indexing" % ( self.type, self.base.type) + if utility_code is not None: + code.globalstate.use_utility_code(utility_code) + if self.index.type.is_int: index_code = self.index.result() else: @@ -3869,6 +4040,7 @@ translate_cpp_exception(code, self.pos, "%s = %s[%s];" % (self.result(), self.base.result(), self.index.result()), + self.result() if self.type.is_pyobject else None, self.exception_value, self.in_nogil_context) else: error_check = '!%s' if error_value == 'NULL' else '%%s == %s' % error_value @@ -3939,6 +4111,7 @@ # both exception handlers are the same. translate_cpp_exception(code, self.pos, "%s = %s;" % (self.result(), rhs.result()), + self.result() if self.type.is_pyobject else None, self.exception_value, self.in_nogil_context) else: code.putln( @@ -4056,7 +4229,8 @@ def analyse_buffer_index(self, env, getting): if is_pythran_expr(self.base.type): - self.type = PythranExpr(pythran_indexing_type(self.base.type, self.indices)) + index_with_type_list = [(idx, idx.type) for idx in self.indices] + self.type = PythranExpr(pythran_indexing_type(self.base.type, index_with_type_list)) else: self.base = self.base.coerce_to_simple(env) self.type = self.base.type.dtype @@ -4078,10 +4252,6 @@ def nogil_check(self, env): if self.is_buffer_access or self.is_memview_index: - if env.directives['boundscheck']: - warning(self.pos, "Use boundscheck(False) for faster access", - level=1) - if self.type.is_pyobject: error(self.pos, "Cannot access buffer with object dtype without gil") self.type = error_type @@ -4108,6 +4278,11 @@ """ ndarray[1, 2, 3] and memslice[1, 2, 3] """ + if self.in_nogil_context: + if self.is_buffer_access or self.is_memview_index: + if code.globalstate.directives['boundscheck']: + warning(self.pos, "Use boundscheck(False) for faster access", level=1) + # Assign indices to temps of at least (s)size_t to allow further index calculations. index_temps = [self.get_index_in_temp(code,ivar) for ivar in self.indices] @@ -4141,7 +4316,7 @@ if is_pythran_expr(base_type) and is_pythran_supported_type(rhs.type): obj = code.funcstate.allocate_temp(PythranExpr(pythran_type(self.base.type)), manage_ref=False) # We have got to do this because we have to declare pythran objects - # at the beggining of the functions. + # at the beginning of the functions. # Indeed, Cython uses "goto" statement for error management, and # RAII doesn't work with that kind of construction. # Moreover, the way Pythran expressions are made is that they don't @@ -4150,7 +4325,7 @@ # case. code.putln("__Pyx_call_destructor(%s);" % obj) code.putln("new (&%s) decltype(%s){%s};" % (obj, obj, self.base.pythran_result())) - code.putln("%s(%s) %s= %s;" % ( + code.putln("%s%s %s= %s;" % ( obj, pythran_indexing_code(self.indices), op, @@ -4181,7 +4356,7 @@ if is_pythran_expr(self.base.type): res = self.result() code.putln("__Pyx_call_destructor(%s);" % res) - code.putln("new (&%s) decltype(%s){%s(%s)};" % ( + code.putln("new (&%s) decltype(%s){%s%s};" % ( res, res, self.base.pythran_result(), @@ -4210,6 +4385,11 @@ indices = self.indices have_slices, indices, newaxes = MemoryView.unellipsify(indices, self.base.type.ndim) + if not getting: + self.writable_needed = True + if self.base.is_name or self.base.is_attribute: + self.base.entry.type.writable_needed = True + self.memslice_index = (not newaxes and len(indices) == self.base.type.ndim) axes = [] @@ -4357,6 +4537,37 @@ else: return MemoryCopySlice(self.pos, self) + def merged_indices(self, indices): + """Return a new list of indices/slices with 'indices' merged into the current ones + according to slicing rules. + Is used to implement "view[i][j]" => "view[i, j]". + Return None if the indices cannot (easily) be merged at compile time. + """ + if not indices: + return None + # NOTE: Need to evaluate "self.original_indices" here as they might differ from "self.indices". + new_indices = self.original_indices[:] + indices = indices[:] + for i, s in enumerate(self.original_indices): + if s.is_slice: + if s.start.is_none and s.stop.is_none and s.step.is_none: + # Full slice found, replace by index. + new_indices[i] = indices[0] + indices.pop(0) + if not indices: + return new_indices + else: + # Found something non-trivial, e.g. a partial slice. + return None + elif not s.type.is_int: + # Not a slice, not an integer index => could be anything... + return None + if indices: + if len(new_indices) + len(indices) > self.base.type.ndim: + return None + new_indices += indices + return new_indices + def is_simple(self): if self.is_ellipsis_noop: # TODO: fix SimpleCallNode.is_simple() @@ -4528,7 +4739,7 @@ return bytes_type elif base_type.is_pyunicode_ptr: return unicode_type - elif base_type in (bytes_type, str_type, unicode_type, + elif base_type in (bytes_type, bytearray_type, str_type, unicode_type, basestring_type, list_type, tuple_type): return base_type elif base_type.is_ptr or base_type.is_array: @@ -4599,7 +4810,7 @@ start=self.start or none_node, stop=self.stop or none_node, step=none_node) - index_node = IndexNode(self.pos, index, base=self.base) + index_node = IndexNode(self.pos, index=index, base=self.base) return index_node.analyse_base_and_index_types( env, getting=getting, setting=not getting, analyse_base=False) @@ -4651,13 +4862,59 @@ ).analyse_types(env) else: c_int = PyrexTypes.c_py_ssize_t_type + + def allow_none(node, default_value, env): + # Coerce to Py_ssize_t, but allow None as meaning the default slice bound. + from .UtilNodes import EvalWithTempExprNode, ResultRefNode + + node_ref = ResultRefNode(node) + new_expr = CondExprNode( + node.pos, + true_val=IntNode( + node.pos, + type=c_int, + value=default_value, + constant_result=int(default_value) if default_value.isdigit() else not_a_constant, + ), + false_val=node_ref.coerce_to(c_int, env), + test=PrimaryCmpNode( + node.pos, + operand1=node_ref, + operator='is', + operand2=NoneNode(node.pos), + ).analyse_types(env) + ).analyse_result_type(env) + return EvalWithTempExprNode(node_ref, new_expr) + if self.start: + if self.start.type.is_pyobject: + self.start = allow_none(self.start, '0', env) self.start = self.start.coerce_to(c_int, env) if self.stop: + if self.stop.type.is_pyobject: + self.stop = allow_none(self.stop, 'PY_SSIZE_T_MAX', env) self.stop = self.stop.coerce_to(c_int, env) self.is_temp = 1 return self + def analyse_as_type(self, env): + base_type = self.base.analyse_as_type(env) + if base_type and not base_type.is_pyobject: + if not self.start and not self.stop: + # memory view + from . import MemoryView + env.use_utility_code(MemoryView.view_utility_code) + none_node = NoneNode(self.pos) + slice_node = SliceNode( + self.pos, + start=none_node, + stop=none_node, + step=none_node, + ) + return PyrexTypes.MemoryViewSliceType( + base_type, MemoryView.get_axes_specs(env, [slice_node])) + return None + nogil_check = Node.gil_error gil_message = "Slicing Python object" @@ -5003,8 +5260,11 @@ def generate_result_code(self, code): if self.is_literal: - self.result_code = code.get_py_const(py_object_type, 'slice', cleanup_level=2) - code = code.get_cached_constants_writer() + dedup_key = make_dedup_key(self.type, (self,)) + self.result_code = code.get_py_const(py_object_type, 'slice', cleanup_level=2, dedup_key=dedup_key) + code = code.get_cached_constants_writer(self.result_code) + if code is None: + return # already initialised code.mark_pos(self.pos) code.putln( @@ -5140,6 +5400,32 @@ return False return ExprNode.may_be_none(self) + def set_py_result_type(self, function, func_type=None): + if func_type is None: + func_type = function.type + if func_type is Builtin.type_type and ( + function.is_name and + function.entry and + function.entry.is_builtin and + function.entry.name in Builtin.types_that_construct_their_instance): + # calling a builtin type that returns a specific object type + if function.entry.name == 'float': + # the following will come true later on in a transform + self.type = PyrexTypes.c_double_type + self.result_ctype = PyrexTypes.c_double_type + else: + self.type = Builtin.builtin_types[function.entry.name] + self.result_ctype = py_object_type + self.may_return_none = False + elif function.is_name and function.type_entry: + # We are calling an extension type constructor. As long as we do not + # support __new__(), the result type is clear + self.type = function.type_entry.type + self.result_ctype = py_object_type + self.may_return_none = False + else: + self.type = py_object_type + def analyse_as_type_constructor(self, env): type = self.function.analyse_as_type(env) if type and type.is_struct_or_union: @@ -5202,6 +5488,7 @@ has_optional_args = False nogil = False analysed = False + overflowcheck = False def compile_time_value(self, denv): function = self.function.compile_time_value(denv) @@ -5222,6 +5509,11 @@ error(self.args[0].pos, "Unknown type") else: return PyrexTypes.CPtrType(type) + elif attr == 'typeof': + if len(self.args) != 1: + error(self.args.pos, "only one type allowed.") + operand = self.args[0].analyse_types(env) + return operand.type def explicit_args_kwds(self): return self.args, None @@ -5244,7 +5536,8 @@ func_type = self.function_type() self.is_numpy_call_with_exprs = False - if has_np_pythran(env) and self.function.is_numpy_attribute: + if (has_np_pythran(env) and function.is_numpy_attribute and + pythran_is_numpy_func_supported(function)): has_pythran_args = True self.arg_tuple = TupleNode(self.pos, args = self.args) self.arg_tuple = self.arg_tuple.analyse_types(env) @@ -5252,37 +5545,18 @@ has_pythran_args &= is_pythran_supported_node_or_none(arg) self.is_numpy_call_with_exprs = bool(has_pythran_args) if self.is_numpy_call_with_exprs: - self.args = None - env.add_include_file("pythonic/numpy/%s.hpp" % self.function.attribute) - self.type = PythranExpr(pythran_func_type(self.function.attribute, self.arg_tuple.args)) - self.may_return_none = True - self.is_temp = 1 + env.add_include_file(pythran_get_func_include_file(function)) + return NumPyMethodCallNode.from_node( + self, + function=function, + arg_tuple=self.arg_tuple, + type=PythranExpr(pythran_func_type(function, self.arg_tuple.args)), + ) elif func_type.is_pyobject: self.arg_tuple = TupleNode(self.pos, args = self.args) self.arg_tuple = self.arg_tuple.analyse_types(env).coerce_to_pyobject(env) self.args = None - if func_type is Builtin.type_type and function.is_name and \ - function.entry and \ - function.entry.is_builtin and \ - function.entry.name in Builtin.types_that_construct_their_instance: - # calling a builtin type that returns a specific object type - if function.entry.name == 'float': - # the following will come true later on in a transform - self.type = PyrexTypes.c_double_type - self.result_ctype = PyrexTypes.c_double_type - else: - self.type = Builtin.builtin_types[function.entry.name] - self.result_ctype = py_object_type - self.may_return_none = False - elif function.is_name and function.type_entry: - # We are calling an extension type constructor. As - # long as we do not support __new__(), the result type - # is clear - self.type = function.type_entry.type - self.result_ctype = py_object_type - self.may_return_none = False - else: - self.type = py_object_type + self.set_py_result_type(function, func_type) self.is_temp = 1 else: self.args = [ arg.analyse_types(env) for arg in self.args ] @@ -5377,7 +5651,7 @@ if formal_arg.not_none: if self.self: self.self = self.self.as_none_safe_node( - "'NoneType' object has no attribute '%s'", + "'NoneType' object has no attribute '%{0}s'".format('.30' if len(entry.name) <= 30 else ''), error='PyExc_AttributeError', format_args=[entry.name]) else: @@ -5403,8 +5677,6 @@ for i in range(min(max_nargs, actual_nargs)): formal_arg = func_type.args[i] formal_type = formal_arg.type - if formal_type.is_const: - formal_type = formal_type.const_base_type arg = args[i].coerce_to(formal_type, env) if formal_arg.not_none: # C methods must do the None checks at *call* time @@ -5511,6 +5783,8 @@ if func_type.exception_value is None: env.use_utility_code(UtilityCode.load_cached("CppExceptionConversion", "CppSupport.cpp")) + self.overflowcheck = env.directives['overflowcheck'] + def calculate_result_code(self): return self.c_call_code() @@ -5550,29 +5824,64 @@ return False # skip allocation of unused result temp return True + def generate_evaluation_code(self, code): + function = self.function + if function.is_name or function.is_attribute: + code.globalstate.use_entry_utility_code(function.entry) + + if not function.type.is_pyobject or len(self.arg_tuple.args) > 1 or ( + self.arg_tuple.args and self.arg_tuple.is_literal): + super(SimpleCallNode, self).generate_evaluation_code(code) + return + + # Special case 0-args and try to avoid explicit tuple creation for Python calls with 1 arg. + arg = self.arg_tuple.args[0] if self.arg_tuple.args else None + subexprs = (self.self, self.coerced_self, function, arg) + for subexpr in subexprs: + if subexpr is not None: + subexpr.generate_evaluation_code(code) + + code.mark_pos(self.pos) + assert self.is_temp + self.allocate_temp_result(code) + + if arg is None: + code.globalstate.use_utility_code(UtilityCode.load_cached( + "PyObjectCallNoArg", "ObjectHandling.c")) + code.putln( + "%s = __Pyx_PyObject_CallNoArg(%s); %s" % ( + self.result(), + function.py_result(), + code.error_goto_if_null(self.result(), self.pos))) + else: + code.globalstate.use_utility_code(UtilityCode.load_cached( + "PyObjectCallOneArg", "ObjectHandling.c")) + code.putln( + "%s = __Pyx_PyObject_CallOneArg(%s, %s); %s" % ( + self.result(), + function.py_result(), + arg.py_result(), + code.error_goto_if_null(self.result(), self.pos))) + + code.put_gotref(self.py_result()) + + for subexpr in subexprs: + if subexpr is not None: + subexpr.generate_disposal_code(code) + subexpr.free_temps(code) + def generate_result_code(self, code): func_type = self.function_type() - if self.function.is_name or self.function.is_attribute: - code.globalstate.use_entry_utility_code(self.function.entry) if func_type.is_pyobject: - if func_type is not type_type and not self.arg_tuple.args and self.arg_tuple.is_literal: - code.globalstate.use_utility_code(UtilityCode.load_cached( - "PyObjectCallNoArg", "ObjectHandling.c")) - code.putln( - "%s = __Pyx_PyObject_CallNoArg(%s); %s" % ( - self.result(), - self.function.py_result(), - code.error_goto_if_null(self.result(), self.pos))) - else: - arg_code = self.arg_tuple.py_result() - code.globalstate.use_utility_code(UtilityCode.load_cached( - "PyObjectCall", "ObjectHandling.c")) - code.putln( - "%s = __Pyx_PyObject_Call(%s, %s, NULL); %s" % ( - self.result(), - self.function.py_result(), - arg_code, - code.error_goto_if_null(self.result(), self.pos))) + arg_code = self.arg_tuple.py_result() + code.globalstate.use_utility_code(UtilityCode.load_cached( + "PyObjectCall", "ObjectHandling.c")) + code.putln( + "%s = __Pyx_PyObject_Call(%s, %s, NULL); %s" % ( + self.result(), + self.function.py_result(), + arg_code, + code.error_goto_if_null(self.result(), self.pos))) code.put_gotref(self.py_result()) elif func_type.is_cfunction: if self.has_optional_args: @@ -5596,11 +5905,11 @@ elif self.type.is_memoryviewslice: assert self.is_temp exc_checks.append(self.type.error_condition(self.result())) - else: + elif func_type.exception_check != '+': exc_val = func_type.exception_value exc_check = func_type.exception_check if exc_val is not None: - exc_checks.append("%s == %s" % (self.result(), exc_val)) + exc_checks.append("%s == %s" % (self.result(), func_type.return_type.cast_code(exc_val))) if exc_check: if self.nogil: exc_checks.append("__Pyx_ErrOccurredWithGIL()") @@ -5619,9 +5928,16 @@ lhs = "" if func_type.exception_check == '+': translate_cpp_exception(code, self.pos, '%s%s;' % (lhs, rhs), + self.result() if self.type.is_pyobject else None, func_type.exception_value, self.nogil) else: - if exc_checks: + if (self.overflowcheck + and self.type.is_int + and self.type.signed + and self.function.result() in ('abs', 'labs', '__Pyx_abs_longlong')): + goto_error = 'if (unlikely(%s < 0)) { PyErr_SetString(PyExc_OverflowError, "value too large"); %s; }' % ( + self.result(), code.error_goto(self.pos)) + elif exc_checks: goto_error = code.error_goto_if(" && ".join(exc_checks), self.pos) else: goto_error = "" @@ -5631,11 +5947,34 @@ if self.has_optional_args: code.funcstate.release_temp(self.opt_arg_struct) - @classmethod - def from_node(cls, node, **kwargs): - ret = super(SimpleCallNode, cls).from_node(node, **kwargs) - ret.is_numpy_call_with_exprs = node.is_numpy_call_with_exprs - return ret + +class NumPyMethodCallNode(SimpleCallNode): + # Pythran call to a NumPy function or method. + # + # function ExprNode the function/method to call + # arg_tuple TupleNode the arguments as an args tuple + + subexprs = ['function', 'arg_tuple'] + is_temp = True + may_return_none = True + + def generate_evaluation_code(self, code): + code.mark_pos(self.pos) + self.allocate_temp_result(code) + + self.function.generate_evaluation_code(code) + assert self.arg_tuple.mult_factor is None + args = self.arg_tuple.args + for arg in args: + arg.generate_evaluation_code(code) + + code.putln("// function evaluation code for numpy function") + code.putln("__Pyx_call_destructor(%s);" % self.result()) + code.putln("new (&%s) decltype(%s){%s{}(%s)};" % ( + self.result(), + self.result(), + pythran_functor(self.function), + ", ".join(a.pythran_result() for a in args))) class PyMethodCallNode(SimpleCallNode): @@ -5658,16 +5997,6 @@ for arg in args: arg.generate_evaluation_code(code) - if self.is_numpy_call_with_exprs: - code.putln("// function evaluation code for numpy function") - code.putln("__Pyx_call_destructor(%s);" % self.result()) - code.putln("new (&%s) decltype(%s){pythonic::numpy::functor::%s{}(%s)};" % ( - self.result(), - self.result(), - self.function.attribute, - ", ".join(a.pythran_result() for a in self.arg_tuple.args))) - return - # make sure function is in temp so that we can replace the reference below if it's a method reuse_function_temp = self.function.is_temp if reuse_function_temp: @@ -5724,44 +6053,38 @@ if not args: # fastest special case: try to avoid tuple creation - code.putln("if (%s) {" % self_arg) + code.globalstate.use_utility_code( + UtilityCode.load_cached("PyObjectCallNoArg", "ObjectHandling.c")) code.globalstate.use_utility_code( UtilityCode.load_cached("PyObjectCallOneArg", "ObjectHandling.c")) code.putln( - "%s = __Pyx_PyObject_CallOneArg(%s, %s); %s" % ( - self.result(), + "%s = (%s) ? __Pyx_PyObject_CallOneArg(%s, %s) : __Pyx_PyObject_CallNoArg(%s);" % ( + self.result(), self_arg, function, self_arg, - code.error_goto_if_null(self.result(), self.pos))) - code.put_decref_clear(self_arg, py_object_type) + function)) + code.put_xdecref_clear(self_arg, py_object_type) code.funcstate.release_temp(self_arg) - code.putln("} else {") + code.putln(code.error_goto_if_null(self.result(), self.pos)) + code.put_gotref(self.py_result()) + elif len(args) == 1: + # fastest special case: try to avoid tuple creation code.globalstate.use_utility_code( - UtilityCode.load_cached("PyObjectCallNoArg", "ObjectHandling.c")) + UtilityCode.load_cached("PyObjectCall2Args", "ObjectHandling.c")) + code.globalstate.use_utility_code( + UtilityCode.load_cached("PyObjectCallOneArg", "ObjectHandling.c")) + arg = args[0] code.putln( - "%s = __Pyx_PyObject_CallNoArg(%s); %s" % ( - self.result(), - function, - code.error_goto_if_null(self.result(), self.pos))) - code.putln("}") + "%s = (%s) ? __Pyx_PyObject_Call2Args(%s, %s, %s) : __Pyx_PyObject_CallOneArg(%s, %s);" % ( + self.result(), self_arg, + function, self_arg, arg.py_result(), + function, arg.py_result())) + code.put_xdecref_clear(self_arg, py_object_type) + code.funcstate.release_temp(self_arg) + arg.generate_disposal_code(code) + arg.free_temps(code) + code.putln(code.error_goto_if_null(self.result(), self.pos)) code.put_gotref(self.py_result()) else: - if len(args) == 1: - code.putln("if (!%s) {" % self_arg) - code.globalstate.use_utility_code( - UtilityCode.load_cached("PyObjectCallOneArg", "ObjectHandling.c")) - arg = args[0] - code.putln( - "%s = __Pyx_PyObject_CallOneArg(%s, %s); %s" % ( - self.result(), - function, arg.py_result(), - code.error_goto_if_null(self.result(), self.pos))) - arg.generate_disposal_code(code) - code.put_gotref(self.py_result()) - code.putln("} else {") - arg_offset = 1 - else: - arg_offset = arg_offset_cname - code.globalstate.use_utility_code( UtilityCode.load_cached("PyFunctionFastCall", "ObjectHandling.c")) code.globalstate.use_utility_code( @@ -5779,9 +6102,9 @@ call_prefix, function, Naming.quick_temp_cname, - arg_offset, + arg_offset_cname, len(args), - arg_offset, + arg_offset_cname, code.error_goto_if_null(self.result(), self.pos))) code.put_xdecref_clear(self_arg, py_object_type) code.put_gotref(self.py_result()) @@ -5793,7 +6116,7 @@ code.putln("{") args_tuple = code.funcstate.allocate_temp(py_object_type, manage_ref=True) code.putln("%s = PyTuple_New(%d+%s); %s" % ( - args_tuple, len(args), arg_offset, + args_tuple, len(args), arg_offset_cname, code.error_goto_if_null(args_tuple, self.pos))) code.put_gotref(args_tuple) @@ -5809,7 +6132,7 @@ arg.make_owned_reference(code) code.put_giveref(arg.py_result()) code.putln("PyTuple_SET_ITEM(%s, %d+%s, %s);" % ( - args_tuple, i, arg_offset, arg.py_result())) + args_tuple, i, arg_offset_cname, arg.py_result())) if len(args) > 1: code.funcstate.release_temp(arg_offset_cname) @@ -5978,6 +6301,37 @@ SimpleCallNode.__init__(self, pos, **kwargs) +class CachedBuiltinMethodCallNode(CallNode): + # Python call to a method of a known Python builtin (only created in transforms) + + subexprs = ['obj', 'args'] + is_temp = True + + def __init__(self, call_node, obj, method_name, args): + super(CachedBuiltinMethodCallNode, self).__init__( + call_node.pos, + obj=obj, method_name=method_name, args=args, + may_return_none=call_node.may_return_none, + type=call_node.type) + + def may_be_none(self): + if self.may_return_none is not None: + return self.may_return_none + return ExprNode.may_be_none(self) + + def generate_result_code(self, code): + type_cname = self.obj.type.cname + obj_cname = self.obj.py_result() + args = [arg.py_result() for arg in self.args] + call_code = code.globalstate.cached_unbound_method_call_code( + obj_cname, type_cname, self.method_name, args) + code.putln("%s = %s; %s" % ( + self.result(), call_code, + code.error_goto_if_null(self.result(), self.pos) + )) + code.put_gotref(self.result()) + + class GeneralCallNode(CallNode): # General Python function call, including keyword, # * and ** arguments. @@ -6036,15 +6390,7 @@ self.positional_args = self.positional_args.analyse_types(env) self.positional_args = \ self.positional_args.coerce_to_pyobject(env) - function = self.function - if function.is_name and function.type_entry: - # We are calling an extension type constructor. As long - # as we do not support __new__(), the result type is clear - self.type = function.type_entry.type - self.result_ctype = py_object_type - self.may_return_none = False - else: - self.type = py_object_type + self.set_py_result_type(self.function) self.is_temp = 1 return self @@ -6211,6 +6557,7 @@ # arg ExprNode subexprs = ['arg'] + is_temp = 1 def calculate_constant_result(self): self.constant_result = tuple(self.arg.constant_result) @@ -6227,7 +6574,6 @@ if self.arg.type is tuple_type: return self.arg.as_none_safe_node("'NoneType' object is not iterable") self.type = tuple_type - self.is_temp = 1 return self def may_be_none(self): @@ -6237,10 +6583,11 @@ gil_message = "Constructing Python tuple" def generate_result_code(self, code): + cfunc = "__Pyx_PySequence_Tuple" if self.arg.type in (py_object_type, tuple_type) else "PySequence_Tuple" code.putln( - "%s = PySequence_Tuple(%s); %s" % ( + "%s = %s(%s); %s" % ( self.result(), - self.arg.py_result(), + cfunc, self.arg.py_result(), code.error_goto_if_null(self.result(), self.pos))) code.put_gotref(self.py_result()) @@ -6759,7 +7106,7 @@ format_args = () if (self.obj.type.is_extension_type and self.needs_none_check and not self.is_py_attr): - msg = "'NoneType' object has no attribute '%s'" + msg = "'NoneType' object has no attribute '%{0}s'".format('.30' if len(self.attribute) <= 30 else '') format_args = (self.attribute,) elif self.obj.type.is_memoryviewslice: if self.is_memslice_transpose: @@ -7308,17 +7655,14 @@ code.putln("PyObject* sequence = %s;" % rhs.py_result()) # list/tuple => check size - code.putln("#if !CYTHON_COMPILING_IN_PYPY") - code.putln("Py_ssize_t size = Py_SIZE(sequence);") - code.putln("#else") - code.putln("Py_ssize_t size = PySequence_Size(sequence);") # < 0 => exception - code.putln("#endif") + code.putln("Py_ssize_t size = __Pyx_PySequence_SIZE(sequence);") code.putln("if (unlikely(size != %d)) {" % len(self.args)) code.globalstate.use_utility_code(raise_too_many_values_to_unpack) code.putln("if (size > %d) __Pyx_RaiseTooManyValuesError(%d);" % ( len(self.args), len(self.args))) code.globalstate.use_utility_code(raise_need_more_values_to_unpack) code.putln("else if (size >= 0) __Pyx_RaiseNeedMoreValuesError(size);") + # < 0 => exception code.putln(code.error_goto(self.pos)) code.putln("}") @@ -7520,7 +7864,7 @@ code.put_decref(target_list, py_object_type) code.putln('%s = %s; %s = NULL;' % (target_list, sublist_temp, sublist_temp)) code.putln('#else') - code.putln('%s = %s;' % (sublist_temp, sublist_temp)) # avoid warning about unused variable + code.putln('(void)%s;' % sublist_temp) # avoid warning about unused variable code.funcstate.release_temp(sublist_temp) code.putln('#endif') @@ -7549,10 +7893,10 @@ if self.mult_factor or not self.args: return tuple_type arg_types = [arg.infer_type(env) for arg in self.args] - if any(type.is_pyobject or type.is_unspecified or type.is_fused for type in arg_types): + if any(type.is_pyobject or type.is_memoryviewslice or type.is_unspecified or type.is_fused + for type in arg_types): return tuple_type - else: - return env.declare_tuple_type(self.pos, arg_types).type + return env.declare_tuple_type(self.pos, arg_types).type def analyse_types(self, env, skip_children=False): if len(self.args) == 0: @@ -7566,7 +7910,8 @@ arg.starred_expr_allowed_here = True self.args[i] = arg.analyse_types(env) if (not self.mult_factor and - not any((arg.is_starred or arg.type.is_pyobject or arg.type.is_fused) for arg in self.args)): + not any((arg.is_starred or arg.type.is_pyobject or arg.type.is_memoryviewslice or arg.type.is_fused) + for arg in self.args)): self.type = env.declare_tuple_type(self.pos, (arg.type for arg in self.args)).type self.is_temp = 1 return self @@ -7649,26 +7994,26 @@ if len(self.args) == 0: # result_code is Naming.empty_tuple return - if self.is_partly_literal: - # underlying tuple is const, but factor is not - tuple_target = code.get_py_const(py_object_type, 'tuple', cleanup_level=2) - const_code = code.get_cached_constants_writer() - const_code.mark_pos(self.pos) - self.generate_sequence_packing_code(const_code, tuple_target, plain=True) - const_code.put_giveref(tuple_target) - code.putln('%s = PyNumber_Multiply(%s, %s); %s' % ( - self.result(), tuple_target, self.mult_factor.py_result(), - code.error_goto_if_null(self.result(), self.pos) + + if self.is_literal or self.is_partly_literal: + # The "mult_factor" is part of the deduplication if it is also constant, i.e. when + # we deduplicate the multiplied result. Otherwise, only deduplicate the constant part. + dedup_key = make_dedup_key(self.type, [self.mult_factor if self.is_literal else None] + self.args) + tuple_target = code.get_py_const(py_object_type, 'tuple', cleanup_level=2, dedup_key=dedup_key) + const_code = code.get_cached_constants_writer(tuple_target) + if const_code is not None: + # constant is not yet initialised + const_code.mark_pos(self.pos) + self.generate_sequence_packing_code(const_code, tuple_target, plain=not self.is_literal) + const_code.put_giveref(tuple_target) + if self.is_literal: + self.result_code = tuple_target + else: + code.putln('%s = PyNumber_Multiply(%s, %s); %s' % ( + self.result(), tuple_target, self.mult_factor.py_result(), + code.error_goto_if_null(self.result(), self.pos) )) - code.put_gotref(self.py_result()) - elif self.is_literal: - # non-empty cached tuple => result is global constant, - # creation code goes into separate code writer - self.result_code = code.get_py_const(py_object_type, 'tuple', cleanup_level=2) - code = code.get_cached_constants_writer() - code.mark_pos(self.pos) - self.generate_sequence_packing_code(code) - code.put_giveref(self.py_result()) + code.put_gotref(self.py_result()) else: self.type.entry.used = True self.generate_sequence_packing_code(code) @@ -7690,7 +8035,7 @@ return () def infer_type(self, env): - # TOOD: Infer non-object list arrays. + # TODO: Infer non-object list arrays. return list_type def analyse_expressions(self, env): @@ -7701,11 +8046,10 @@ return node.coerce_to_pyobject(env) def analyse_types(self, env): - hold_errors() - self.original_args = list(self.args) - node = SequenceNode.analyse_types(self, env) - node.obj_conversion_errors = held_errors() - release_errors(ignore=True) + with local_errors(ignore=True) as errors: + self.original_args = list(self.args) + node = SequenceNode.analyse_types(self, env) + node.obj_conversion_errors = errors if env.is_module_scope: self.in_module_scope = True node = node._create_merge_node_if_necessary(env) @@ -7883,9 +8227,8 @@ code.putln('{ /* enter inner scope */') py_entries = [] - for entry in self.expr_scope.var_entries: + for _, entry in sorted(item for item in self.expr_scope.entries.items() if item[0]): if not entry.in_closure: - code.put_var_declaration(entry) if entry.type.is_pyobject and entry.used: py_entries.append(entry) if not py_entries: @@ -7895,14 +8238,13 @@ return # must free all local Python references at each exit point - old_loop_labels = tuple(code.new_loop_labels()) + old_loop_labels = code.new_loop_labels() old_error_label = code.new_error_label() generate_inner_evaluation_code(code) # normal (non-error) exit - for entry in py_entries: - code.put_var_decref(entry) + self._generate_vars_cleanup(code, py_entries) # error/loop body exit points exit_scope = code.new_label('exit_scope') @@ -7911,8 +8253,7 @@ list(zip(code.get_loop_labels(), old_loop_labels))): if code.label_used(label): code.put_label(label) - for entry in py_entries: - code.put_var_decref(entry) + self._generate_vars_cleanup(code, py_entries) code.put_goto(old_label) code.put_label(exit_scope) code.putln('} /* exit inner scope */') @@ -7920,6 +8261,14 @@ code.set_loop_labels(old_loop_labels) code.error_label = old_error_label + def _generate_vars_cleanup(self, code, py_entries): + for entry in py_entries: + if entry.is_cglobal: + code.put_var_gotref(entry) + code.put_decref_set(entry.cname, "Py_None") + else: + code.put_var_xdecref_clear(entry) + class ComprehensionNode(ScopedExprNode): # A list/set/dict comprehension @@ -7927,6 +8276,7 @@ child_attrs = ["loop"] is_temp = True + constant_result = not_a_constant def infer_type(self, env): return self.type @@ -8350,15 +8700,16 @@ return () def infer_type(self, env): - # TOOD: Infer struct constructors. + # TODO: Infer struct constructors. return dict_type def analyse_types(self, env): - hold_errors() - self.key_value_pairs = [ item.analyse_types(env) - for item in self.key_value_pairs ] - self.obj_conversion_errors = held_errors() - release_errors(ignore=True) + with local_errors(ignore=True) as errors: + self.key_value_pairs = [ + item.analyse_types(env) + for item in self.key_value_pairs + ] + self.obj_conversion_errors = errors return self def may_be_none(self): @@ -8420,8 +8771,9 @@ if is_dict: self.release_errors() code.putln( - "%s = PyDict_New(); %s" % ( + "%s = __Pyx_PyDict_NewPresized(%d); %s" % ( self.result(), + len(self.key_value_pairs), code.error_goto_if_null(self.result(), self.pos))) code.put_gotref(self.py_result()) @@ -8782,9 +9134,6 @@ is_active = False def analyse_expressions(self, env): - if self.is_active: - env.use_utility_code( - UtilityCode.load_cached("CyFunctionClassCell", "CythonFunction.c")) return self def generate_evaluation_code(self, code): @@ -8798,6 +9147,8 @@ def generate_injection_code(self, code, classobj_cname): if self.is_active: + code.globalstate.use_utility_code( + UtilityCode.load_cached("CyFunctionClassCell", "CythonFunction.c")) code.put_error_if_neg(self.pos, '__Pyx_CyFunction_InitClassCell(%s, %s)' % ( self.result(), classobj_cname)) @@ -8828,66 +9179,6 @@ code.put_incref(self.result(), py_object_type) -class BoundMethodNode(ExprNode): - # Helper class used in the implementation of Python - # class definitions. Constructs an bound method - # object from a class and a function. - # - # function ExprNode Function object - # self_object ExprNode self object - - subexprs = ['function'] - - def analyse_types(self, env): - self.function = self.function.analyse_types(env) - self.type = py_object_type - self.is_temp = 1 - return self - - gil_message = "Constructing a bound method" - - def generate_result_code(self, code): - code.putln( - "%s = __Pyx_PyMethod_New(%s, %s, (PyObject*)%s->ob_type); %s" % ( - self.result(), - self.function.py_result(), - self.self_object.py_result(), - self.self_object.py_result(), - code.error_goto_if_null(self.result(), self.pos))) - code.put_gotref(self.py_result()) - -class UnboundMethodNode(ExprNode): - # Helper class used in the implementation of Python - # class definitions. Constructs an unbound method - # object from a class and a function. - # - # function ExprNode Function object - - type = py_object_type - is_temp = 1 - - subexprs = ['function'] - - def analyse_types(self, env): - self.function = self.function.analyse_types(env) - return self - - def may_be_none(self): - return False - - gil_message = "Constructing an unbound method" - - def generate_result_code(self, code): - class_cname = code.pyclass_stack[-1].classobj.result() - code.putln( - "%s = __Pyx_PyMethod_New(%s, 0, %s); %s" % ( - self.result(), - self.function.py_result(), - class_cname, - code.error_goto_if_null(self.result(), self.pos))) - code.put_gotref(self.py_result()) - - class PyCFunctionNode(ExprNode, ModuleNameMixin): # Helper class used in the implementation of Python # functions. Constructs a PyCFunction object @@ -8966,22 +9257,19 @@ else: default_args.append(arg) if arg.annotation: - arg.annotation = arg.annotation.analyse_types(env) - if not arg.annotation.type.is_pyobject: - arg.annotation = arg.annotation.coerce_to_pyobject(env) + arg.annotation = self.analyse_annotation(env, arg.annotation) annotations.append((arg.pos, arg.name, arg.annotation)) for arg in (self.def_node.star_arg, self.def_node.starstar_arg): if arg and arg.annotation: - arg.annotation = arg.annotation.analyse_types(env) - if not arg.annotation.type.is_pyobject: - arg.annotation = arg.annotation.coerce_to_pyobject(env) + arg.annotation = self.analyse_annotation(env, arg.annotation) annotations.append((arg.pos, arg.name, arg.annotation)) - if self.def_node.return_type_annotation: - annotations.append((self.def_node.return_type_annotation.pos, - StringEncoding.EncodedString("return"), - self.def_node.return_type_annotation)) + annotation = self.def_node.return_type_annotation + if annotation: + annotation = self.analyse_annotation(env, annotation) + self.def_node.return_type_annotation = annotation + annotations.append((annotation.pos, StringEncoding.EncodedString("return"), annotation)) if nonliteral_objects or nonliteral_other: module_scope = env.global_scope() @@ -8996,7 +9284,7 @@ for arg in nonliteral_other: entry = scope.declare_var(arg.name, arg.type, None, Naming.arg_prefix + arg.name, - allow_pyobject=False) + allow_pyobject=False, allow_memoryview=True) self.defaults.append((arg, entry)) entry = module_scope.declare_struct_or_union( None, 'struct', scope, 1, None, cname=cname) @@ -9058,6 +9346,20 @@ for pos, name, value in annotations]) self.annotations_dict = annotations_dict.analyse_types(env) + def analyse_annotation(self, env, annotation): + if annotation is None: + return None + atype = annotation.analyse_as_type(env) + if atype is not None: + # Keep parsed types as strings as they might not be Python representable. + annotation = UnicodeNode( + annotation.pos, + value=StringEncoding.EncodedString(atype.declaration_code('', for_display=True))) + annotation = annotation.analyse_types(env) + if not annotation.type.is_pyobject: + annotation = annotation.coerce_to_pyobject(env) + return annotation + def may_be_none(self): return False @@ -9164,7 +9466,8 @@ if self.defaults_kwdict: code.putln('__Pyx_CyFunction_SetDefaultsKwDict(%s, %s);' % ( self.result(), self.defaults_kwdict.py_result())) - if def_node.defaults_getter: + if def_node.defaults_getter and not self.specialized_cpdefs: + # Fused functions do not support dynamic defaults, only their specialisations can have them for now. code.putln('__Pyx_CyFunction_SetDefaultsGetter(%s, %s);' % ( self.result(), def_node.defaults_getter.entry.pyfunc_cname)) if self.annotations_dict: @@ -9219,7 +9522,9 @@ if self.result_code is None: self.result_code = code.get_py_const(py_object_type, 'codeobj', cleanup_level=2) - code = code.get_cached_constants_writer() + code = code.get_cached_constants_writer(self.result_code) + if code is None: + return # already initialised code.mark_pos(self.pos) func = self.def_node func_name = code.get_py_string_const( @@ -9228,7 +9533,9 @@ file_path = StringEncoding.bytes_literal(func.pos[0].get_filenametable_entry().encode('utf8'), 'utf8') file_path_const = code.get_py_string_const(file_path, identifier=False, is_str=True) - flags = [] + # This combination makes CPython create a new dict for "frame.f_locals" (see GH #1836). + flags = ['CO_OPTIMIZED', 'CO_NEWLOCALS'] + if self.def_node.star_arg: flags.append('CO_VARARGS') if self.def_node.starstar_arg: @@ -9415,10 +9722,11 @@ label_num = 0 is_yield_from = False is_await = False + in_async_gen = False expr_keyword = 'yield' def analyse_types(self, env): - if not self.label_num: + if not self.label_num or (self.is_yield_from and self.in_async_gen): error(self.pos, "'%s' not supported here" % self.expr_keyword) self.is_temp = 1 if self.arg is not None: @@ -9449,7 +9757,8 @@ Generate the code to return the argument in 'Naming.retval_cname' and to continue at the yield label. """ - label_num, label_name = code.new_yield_label() + label_num, label_name = code.new_yield_label( + self.expr_keyword.replace(' ', '_')) code.use_label(label_name) saved = [] @@ -9469,10 +9778,23 @@ nogil=not code.funcstate.gil_owned) code.put_finish_refcount_context() - code.putln("/* return from generator, yielding value */") + if code.funcstate.current_except is not None: + # inside of an except block => save away currently handled exception + code.putln("__Pyx_Coroutine_SwapException(%s);" % Naming.generator_cname) + else: + # no exceptions being handled => restore exception state of caller + code.putln("__Pyx_Coroutine_ResetAndClearException(%s);" % Naming.generator_cname) + + code.putln("/* return from %sgenerator, %sing value */" % ( + 'async ' if self.in_async_gen else '', + 'await' if self.is_await else 'yield')) code.putln("%s->resume_label = %d;" % ( Naming.generator_cname, label_num)) - code.putln("return %s;" % Naming.retval_cname) + if self.in_async_gen and not self.is_await: + # __Pyx__PyAsyncGenValueWrapperNew() steals a reference to the return value + code.putln("return __Pyx__PyAsyncGenValueWrapperNew(%s);" % Naming.retval_cname) + else: + code.putln("return %s;" % Naming.retval_cname) code.put_label(label_name) for cname, save_cname, type in saved: @@ -9480,27 +9802,19 @@ if type.is_pyobject: code.putln('%s->%s = 0;' % (Naming.cur_scope_cname, save_cname)) code.put_xgotref(cname) - code.putln(code.error_goto_if_null(Naming.sent_value_cname, self.pos)) + self.generate_sent_value_handling_code(code, Naming.sent_value_cname) if self.result_is_used: self.allocate_temp_result(code) code.put('%s = %s; ' % (self.result(), Naming.sent_value_cname)) code.put_incref(self.result(), py_object_type) + def generate_sent_value_handling_code(self, code, value_cname): + code.putln(code.error_goto_if_null(value_cname, self.pos)) -class YieldFromExprNode(YieldExprNode): - # "yield from GEN" expression - is_yield_from = True - expr_keyword = 'yield from' - - def coerce_yield_argument(self, env): - if not self.arg.type.is_string: - # FIXME: support C arrays and C++ iterators? - error(self.pos, "yielding from non-Python object not supported") - self.arg = self.arg.coerce_to_pyobject(env) +class _YieldDelegationExprNode(YieldExprNode): def yield_from_func(self, code): - code.globalstate.use_utility_code(UtilityCode.load_cached("GeneratorYieldFrom", "Coroutine.c")) - return "__Pyx_Generator_Yield_From" + raise NotImplementedError() def generate_evaluation_code(self, code, source_cname=None, decref_source=False): if source_cname is None: @@ -9534,15 +9848,31 @@ code.put_gotref(self.result()) def handle_iteration_exception(self, code): - code.putln("PyObject* exc_type = PyErr_Occurred();") + code.putln("PyObject* exc_type = __Pyx_PyErr_Occurred();") code.putln("if (exc_type) {") - code.putln("if (likely(exc_type == PyExc_StopIteration ||" - " PyErr_GivenExceptionMatches(exc_type, PyExc_StopIteration))) PyErr_Clear();") + code.putln("if (likely(exc_type == PyExc_StopIteration || (exc_type != PyExc_GeneratorExit &&" + " __Pyx_PyErr_GivenExceptionMatches(exc_type, PyExc_StopIteration)))) PyErr_Clear();") code.putln("else %s" % code.error_goto(self.pos)) code.putln("}") -class AwaitExprNode(YieldFromExprNode): +class YieldFromExprNode(_YieldDelegationExprNode): + # "yield from GEN" expression + is_yield_from = True + expr_keyword = 'yield from' + + def coerce_yield_argument(self, env): + if not self.arg.type.is_string: + # FIXME: support C arrays and C++ iterators? + error(self.pos, "yielding from non-Python object not supported") + self.arg = self.arg.coerce_to_pyobject(env) + + def yield_from_func(self, code): + code.globalstate.use_utility_code(UtilityCode.load_cached("GeneratorYieldFrom", "Coroutine.c")) + return "__Pyx_Generator_Yield_From" + + +class AwaitExprNode(_YieldDelegationExprNode): # 'await' expression node # # arg ExprNode the Awaitable value to await @@ -9561,29 +9891,34 @@ return "__Pyx_Coroutine_Yield_From" -class AIterAwaitExprNode(AwaitExprNode): - # 'await' expression node used in async-for loops to support the pre-Py3.5.2 'aiter' protocol - def yield_from_func(self, code): - code.globalstate.use_utility_code(UtilityCode.load_cached("CoroutineAIterYieldFrom", "Coroutine.c")) - return "__Pyx_Coroutine_AIter_Yield_From" - - class AwaitIterNextExprNode(AwaitExprNode): # 'await' expression node as part of 'async for' iteration # # Breaks out of loop on StopAsyncIteration exception. - def fetch_iteration_result(self, code): - assert code.break_label, "AwaitIterNextExprNode outside of 'async for' loop" + def _generate_break(self, code): code.globalstate.use_utility_code(UtilityCode.load_cached("StopAsyncIteration", "Coroutine.c")) - code.putln("PyObject* exc_type = PyErr_Occurred();") - code.putln("if (exc_type && likely(exc_type == __Pyx_PyExc_StopAsyncIteration ||" - " PyErr_GivenExceptionMatches(exc_type, __Pyx_PyExc_StopAsyncIteration))) {") + code.putln("PyObject* exc_type = __Pyx_PyErr_Occurred();") + code.putln("if (unlikely(exc_type && (exc_type == __Pyx_PyExc_StopAsyncIteration || (" + " exc_type != PyExc_StopIteration && exc_type != PyExc_GeneratorExit &&" + " __Pyx_PyErr_GivenExceptionMatches(exc_type, __Pyx_PyExc_StopAsyncIteration))))) {") code.putln("PyErr_Clear();") code.putln("break;") code.putln("}") + + def fetch_iteration_result(self, code): + assert code.break_label, "AwaitIterNextExprNode outside of 'async for' loop" + self._generate_break(code) super(AwaitIterNextExprNode, self).fetch_iteration_result(code) + def generate_sent_value_handling_code(self, code, value_cname): + assert code.break_label, "AwaitIterNextExprNode outside of 'async for' loop" + code.putln("if (unlikely(!%s)) {" % value_cname) + self._generate_break(code) + # all non-break exceptions are errors, as in parent class + code.putln(code.error_goto(self.pos)) + code.putln("}") + class GlobalsExprNode(AtomicExprNode): type = dict_type @@ -9779,6 +10114,7 @@ if self.is_cpp_operation() and self.exception_check == '+': translate_cpp_exception(code, self.pos, "%s = %s %s;" % (self.result(), self.operator, self.operand.result()), + self.result() if self.type.is_pyobject else None, self.exception_value, self.in_nogil_context) else: code.putln("%s = %s %s;" % (self.result(), self.operator, self.operand.result())) @@ -10018,6 +10354,7 @@ if (self.operand.type.is_cpp_class and self.exception_check == '+'): translate_cpp_exception(code, self.pos, "%s = %s %s;" % (self.result(), self.operator, self.operand.result()), + self.result() if self.type.is_pyobject else None, self.exception_value, self.in_nogil_context) @@ -10095,7 +10432,8 @@ error(self.pos, "Python objects cannot be cast from pointers of primitive types") else: # Should this be an error? - warning(self.pos, "No conversion from %s to %s, python object pointer used." % (self.operand.type, self.type)) + warning(self.pos, "No conversion from %s to %s, python object pointer used." % ( + self.operand.type, self.type)) self.operand = self.operand.coerce_to_simple(env) elif from_py and not to_py: if self.type.create_from_py_utility_code(env): @@ -10104,7 +10442,8 @@ if not (self.type.base_type.is_void or self.type.base_type.is_struct): error(self.pos, "Python objects cannot be cast to pointers of primitive types") else: - warning(self.pos, "No conversion from %s to %s, python object pointer used." % (self.type, self.operand.type)) + warning(self.pos, "No conversion from %s to %s, python object pointer used." % ( + self.type, self.operand.type)) elif from_py and to_py: if self.typecheck: self.operand = PyTypeTestNode(self.operand, self.type, env, notnone=True) @@ -10116,6 +10455,13 @@ elif self.operand.type.is_fused: self.operand = self.operand.coerce_to(self.type, env) #self.type = self.operand.type + if self.type.is_ptr and self.type.base_type.is_cfunction and self.type.base_type.nogil: + op_type = self.operand.type + if op_type.is_ptr: + op_type = op_type.base_type + if op_type.is_cfunction and not op_type.nogil: + warning(self.pos, + "Casting a GIL-requiring function into a nogil function circumvents GIL validation", 1) return self def is_simple(self): @@ -10322,7 +10668,7 @@ def allocate_temp_result(self, code): if self.temp_code: - raise RuntimeError("temp allocated mulitple times") + raise RuntimeError("temp allocated multiple times") self.temp_code = code.funcstate.allocate_temp(self.type, True) @@ -10438,9 +10784,7 @@ for attr in path[1:]: operand = AttributeNode(pos=self.pos, obj=operand, attribute=attr) operand = AttributeNode(pos=self.pos, obj=operand, attribute=self.base_type.name) - self.operand = operand - self.__class__ = SizeofVarNode - node = self.analyse_types(env) + node = SizeofVarNode(self.pos, operand=operand).analyse_types(env) return node if self.arg_type is None: base_type = self.base_type.analyse(env) @@ -10564,7 +10908,7 @@ arg_code = self.arg_type.result() translate_cpp_exception(code, self.pos, "%s = typeid(%s);" % (self.temp_code, arg_code), - None, self.in_nogil_context) + None, None, self.in_nogil_context) class TypeofNode(ExprNode): # Compile-time type of an expression, as a string. @@ -10585,6 +10929,10 @@ self.literal = literal.coerce_to_pyobject(env) return self + def analyse_as_type(self, env): + self.operand = self.operand.analyse_types(env) + return self.operand.type + def may_be_none(self): return False @@ -10792,12 +11140,19 @@ if self.type.is_pythran_expr: code.putln("// Pythran binop") code.putln("__Pyx_call_destructor(%s);" % self.result()) - code.putln("new (&%s) decltype(%s){%s %s %s};" % ( - self.result(), - self.result(), - self.operand1.pythran_result(), - self.operator, - self.operand2.pythran_result())) + if self.operator == '**': + code.putln("new (&%s) decltype(%s){pythonic::numpy::functor::power{}(%s, %s)};" % ( + self.result(), + self.result(), + self.operand1.pythran_result(), + self.operand2.pythran_result())) + else: + code.putln("new (&%s) decltype(%s){%s %s %s};" % ( + self.result(), + self.result(), + self.operand1.pythran_result(), + self.operator, + self.operand2.pythran_result())) elif self.operand1.type.is_pyobject: function = self.py_operation_function(code) if self.operator == '**': @@ -10819,6 +11174,7 @@ if self.is_cpp_operation() and self.exception_check == '+': translate_cpp_exception(code, self.pos, "%s = %s;" % (self.result(), self.calculate_result_code()), + self.result() if self.type.is_pyobject else None, self.exception_value, self.in_nogil_context) else: code.putln("%s = %s;" % (self.result(), self.calculate_result_code())) @@ -10853,9 +11209,8 @@ cpp_type = None if type1.is_cpp_class or type1.is_ptr: cpp_type = type1.find_cpp_operation_type(self.operator, type2) - # FIXME: handle the reversed case? - #if cpp_type is None and (type2.is_cpp_class or type2.is_ptr): - # cpp_type = type2.find_cpp_operation_type(self.operator, type1) + if cpp_type is None and (type2.is_cpp_class or type2.is_ptr): + cpp_type = type2.find_cpp_operation_type(self.operator, type1) # FIXME: do we need to handle other cases here? return cpp_type @@ -10960,10 +11315,11 @@ self.operand2.result(), self.overflow_bit_node.overflow_bit) elif self.type.is_cpp_class or self.infix: - return "(%s %s %s)" % ( - self.operand1.result(), - self.operator, - self.operand2.result()) + if is_pythran_expr(self.type): + result1, result2 = self.operand1.pythran_result(), self.operand2.pythran_result() + else: + result1, result2 = self.operand1.result(), self.operand2.result() + return "(%s %s %s)" % (result1, self.operator, result2) else: func = self.type.binary_op(self.operator) if func is None: @@ -11029,7 +11385,7 @@ def infer_builtin_types_operation(self, type1, type2): # b'abc' + 'abc' raises an exception in Py3, # so we can safely infer the Py2 type for bytes here - string_types = (bytes_type, str_type, basestring_type, unicode_type) + string_types = (bytes_type, bytearray_type, str_type, basestring_type, unicode_type) if type1 in string_types and type2 in string_types: return string_types[max(string_types.index(type1), string_types.index(type2))] @@ -11088,7 +11444,7 @@ def infer_builtin_types_operation(self, type1, type2): # let's assume that whatever builtin type you multiply a string with # will either return a string of the same type or fail with an exception - string_types = (bytes_type, str_type, basestring_type, unicode_type) + string_types = (bytes_type, bytearray_type, str_type, basestring_type, unicode_type) if type1 in string_types and type2.is_builtin_type: return type1 if type2 in string_types and type1.is_builtin_type: @@ -11176,7 +11532,7 @@ self.operand2 = self.operand2.coerce_to_simple(env) def compute_c_result_type(self, type1, type2): - if self.operator == '/' and self.ctruedivision: + if self.operator == '/' and self.ctruedivision and not type1.is_cpp_class and not type2.is_cpp_class: if not type1.is_float and not type2.is_float: widest_type = PyrexTypes.widest_numeric_type(type1, PyrexTypes.c_double_type) widest_type = PyrexTypes.widest_numeric_type(type2, widest_type) @@ -11192,9 +11548,11 @@ def generate_evaluation_code(self, code): if not self.type.is_pyobject and not self.type.is_complex: if self.cdivision is None: - self.cdivision = (code.globalstate.directives['cdivision'] - or not self.type.signed - or self.type.is_float) + self.cdivision = ( + code.globalstate.directives['cdivision'] + or self.type.is_float + or ((self.type.is_numeric or self.type.is_enum) and not self.type.signed) + ) if not self.cdivision: code.globalstate.use_utility_code( UtilityCode.load_cached("DivInt", "CMath.c").specialize(self.type)) @@ -11265,7 +11623,7 @@ code.putln("}") def calculate_result_code(self): - if self.type.is_complex: + if self.type.is_complex or self.is_cpp_operation(): return NumBinopNode.calculate_result_code(self) elif self.type.is_float and self.operator == '//': return "floor(%s / %s)" % ( @@ -11287,6 +11645,20 @@ self.operand2.result()) +_find_formatting_types = re.compile( + br"%" + br"(?:%|" # %% + br"(?:\([^)]+\))?" # %(name) + br"[-+#,0-9 ]*([a-z])" # %.2f etc. + br")").findall + +# These format conversion types can never trigger a Unicode string conversion in Py2. +_safe_bytes_formats = set([ + # Excludes 's' and 'r', which can generate non-bytes strings. + b'd', b'i', b'o', b'u', b'x', b'X', b'e', b'E', b'f', b'F', b'g', b'G', b'c', b'b', b'a', +]) + + class ModNode(DivNode): # '%' operator. @@ -11296,7 +11668,7 @@ or NumBinopNode.is_py_operation_types(self, type1, type2)) def infer_builtin_types_operation(self, type1, type2): - # b'%s' % xyz raises an exception in Py3, so it's safe to infer the type for Py2 + # b'%s' % xyz raises an exception in Py3<3.5, so it's safe to infer the type for Py2 and later Py3's. if type1 is unicode_type: # None + xyz may be implemented by RHS if type2.is_builtin_type or not self.operand1.may_be_none(): @@ -11306,6 +11678,11 @@ return type2 elif type2.is_numeric: return type1 + elif self.operand1.is_string_literal: + if type1 is str_type or type1 is bytes_type: + if set(_find_formatting_types(self.operand1.value)) <= _safe_bytes_formats: + return type1 + return basestring_type elif type1 is bytes_type and not type2.is_builtin_type: return None # RHS might implement '% operator differently in Py3 else: @@ -11357,13 +11734,19 @@ self.operand2.result()) def py_operation_function(self, code): - if self.operand1.type is unicode_type: - if self.operand1.may_be_none(): + type1, type2 = self.operand1.type, self.operand2.type + # ("..." % x) must call "x.__rmod__()" for string subtypes. + if type1 is unicode_type: + if self.operand1.may_be_none() or ( + type2.is_extension_type and type2.subtype_of(type1) or + type2 is py_object_type and not isinstance(self.operand2, CoerceToPyTypeNode)): return '__Pyx_PyUnicode_FormatSafe' else: return 'PyUnicode_Format' - elif self.operand1.type is str_type: - if self.operand1.may_be_none(): + elif type1 is str_type: + if self.operand1.may_be_none() or ( + type2.is_extension_type and type2.subtype_of(type1) or + type2 is py_object_type and not isinstance(self.operand2, CoerceToPyTypeNode)): return '__Pyx_PyString_FormatSafe' else: return '__Pyx_PyString_Format' @@ -11504,7 +11887,7 @@ operator=self.operator, operand1=operand1, operand2=operand2) - def generate_bool_evaluation_code(self, code, final_result_temp, and_label, or_label, end_label, fall_through): + def generate_bool_evaluation_code(self, code, final_result_temp, final_result_type, and_label, or_label, end_label, fall_through): code.mark_pos(self.pos) outer_labels = (and_label, or_label) @@ -11513,19 +11896,20 @@ else: my_label = or_label = code.new_label('next_or') self.operand1.generate_bool_evaluation_code( - code, final_result_temp, and_label, or_label, end_label, my_label) + code, final_result_temp, final_result_type, and_label, or_label, end_label, my_label) and_label, or_label = outer_labels code.put_label(my_label) self.operand2.generate_bool_evaluation_code( - code, final_result_temp, and_label, or_label, end_label, fall_through) + code, final_result_temp, final_result_type, and_label, or_label, end_label, fall_through) def generate_evaluation_code(self, code): self.allocate_temp_result(code) + result_type = PyrexTypes.py_object_type if self.type.is_pyobject else self.type or_label = and_label = None end_label = code.new_label('bool_binop_done') - self.generate_bool_evaluation_code(code, self.result(), and_label, or_label, end_label, end_label) + self.generate_bool_evaluation_code(code, self.result(), result_type, and_label, or_label, end_label, end_label) code.put_label(end_label) gil_message = "Truth-testing Python object" @@ -11610,7 +11994,7 @@ test_result = self.arg.result() return (test_result, self.arg.type.is_pyobject) - def generate_bool_evaluation_code(self, code, final_result_temp, and_label, or_label, end_label, fall_through): + def generate_bool_evaluation_code(self, code, final_result_temp, final_result_type, and_label, or_label, end_label, fall_through): code.mark_pos(self.pos) # x => x @@ -11653,7 +12037,7 @@ code.putln("} else {") self.value.generate_evaluation_code(code) self.value.make_owned_reference(code) - code.putln("%s = %s;" % (final_result_temp, self.value.result())) + code.putln("%s = %s;" % (final_result_temp, self.value.result_as(final_result_type))) self.value.generate_post_assignment_code(code) # disposal: {not (and_label and or_label) [else]} self.arg.generate_disposal_code(code) @@ -11675,6 +12059,7 @@ true_val = None false_val = None + is_temp = True subexprs = ['test', 'true_val', 'false_val'] @@ -11699,7 +12084,6 @@ self.test = self.test.analyse_types(env).coerce_to_boolean(env) self.true_val = self.true_val.analyse_types(env) self.false_val = self.false_val.analyse_types(env) - self.is_temp = 1 return self.analyse_result_type(env) def analyse_result_type(self, env): @@ -12014,6 +12398,11 @@ self.special_bool_cmp_utility_code = UtilityCode.load_cached("PyDictContains", "ObjectHandling.c") self.special_bool_cmp_function = "__Pyx_PyDict_ContainsTF" return True + elif self.operand2.type is Builtin.set_type: + self.operand2 = self.operand2.as_none_safe_node("'NoneType' object is not iterable") + self.special_bool_cmp_utility_code = UtilityCode.load_cached("PySetContains", "ObjectHandling.c") + self.special_bool_cmp_function = "__Pyx_PySet_ContainsTF" + return True elif self.operand2.type is Builtin.unicode_type: self.operand2 = self.operand2.as_none_safe_node("'NoneType' object is not iterable") self.special_bool_cmp_utility_code = UtilityCode.load_cached("PyUnicodeContains", "StringTools.c") @@ -12101,7 +12490,13 @@ self.c_operator(op), code2) if self.is_cpp_comparison() and self.exception_check == '+': - translate_cpp_exception(code, self.pos, statement, self.exception_value, self.in_nogil_context) + translate_cpp_exception( + code, + self.pos, + statement, + result_code if self.type.is_pyobject else None, + self.exception_value, + self.in_nogil_context) code.putln(statement) def c_operator(self, op): @@ -12133,7 +12528,14 @@ is_memslice_nonecheck = False def infer_type(self, env): - # TODO: Actually implement this (after merging with -unstable). + type1 = self.operand1.infer_type(env) + type2 = self.operand2.infer_type(env) + + if is_pythran_expr(type1) or is_pythran_expr(type2): + if is_pythran_supported_type(type1) and is_pythran_supported_type(type2): + return PythranExpr(pythran_binop_type(self.operator, type1, type2)) + + # TODO: implement this for other types. return py_object_type def type_dependencies(self, env): @@ -12303,18 +12705,19 @@ return self.operand1.check_const() and self.operand2.check_const() def calculate_result_code(self): - if self.operand1.type.is_complex: + operand1, operand2 = self.operand1, self.operand2 + if operand1.type.is_complex: if self.operator == "!=": negation = "!" else: negation = "" return "(%s%s(%s, %s))" % ( negation, - self.operand1.type.binary_op('=='), - self.operand1.result(), - self.operand2.result()) + operand1.type.binary_op('=='), + operand1.result(), + operand2.result()) elif self.is_c_string_contains(): - if self.operand2.type is unicode_type: + if operand2.type is unicode_type: method = "__Pyx_UnicodeContainsUCS4" else: method = "__Pyx_BytesContains" @@ -12325,16 +12728,18 @@ return "(%s%s(%s, %s))" % ( negation, method, - self.operand2.result(), - self.operand1.result()) + operand2.result(), + operand1.result()) else: - result1 = self.operand1.result() - result2 = self.operand2.result() - if self.is_memslice_nonecheck: - if self.operand1.type.is_memoryviewslice: - result1 = "((PyObject *) %s.memview)" % result1 - else: - result2 = "((PyObject *) %s.memview)" % result2 + if is_pythran_expr(self.type): + result1, result2 = operand1.pythran_result(), operand2.pythran_result() + else: + result1, result2 = operand1.result(), operand2.result() + if self.is_memslice_nonecheck: + if operand1.type.is_memoryviewslice: + result1 = "((PyObject *) %s.memview)" % result1 + else: + result2 = "((PyObject *) %s.memview)" % result2 return "(%s %s %s)" % ( result1, @@ -12556,12 +12961,12 @@ def generate_result_code(self, code): self.type.create_from_py_utility_code(self.env) - code.putln("%s = %s(%s);" % (self.result(), - self.type.from_py_function, - self.arg.py_result())) - - error_cond = self.type.error_condition(self.result()) - code.putln(code.error_goto_if(error_cond, self.pos)) + code.putln(self.type.from_py_call_code( + self.arg.py_result(), + self.result(), + self.pos, + code + )) class CastNode(CoercionNode): @@ -12620,6 +13025,15 @@ def nonlocally_immutable(self): return self.arg.nonlocally_immutable() + def reanalyse(self): + if self.type != self.arg.type or not self.arg.is_temp: + return self + if not self.type.typeobj_is_available(): + return self + if self.arg.may_be_none() and self.notnone: + return self.arg.as_none_safe_node("Cannot convert NoneType to %.200s" % self.type.name) + return self.arg + def calculate_constant_result(self): # FIXME pass @@ -12659,7 +13073,7 @@ is_nonecheck = True def __init__(self, arg, exception_type_cname, exception_message, - exception_format_args): + exception_format_args=()): CoercionNode.__init__(self, arg) self.type = arg.type self.result_ctype = arg.ctype() @@ -12695,6 +13109,19 @@ else: raise Exception("unsupported type") + @classmethod + def generate(cls, arg, code, exception_message, + exception_type_cname="PyExc_TypeError", exception_format_args=(), in_nogil_context=False): + node = cls(arg, exception_type_cname, exception_message, exception_format_args) + node.in_nogil_context = in_nogil_context + node.put_nonecheck(code) + + @classmethod + def generate_if_needed(cls, arg, code, exception_message, + exception_type_cname="PyExc_TypeError", exception_format_args=(), in_nogil_context=False): + if arg.may_be_none(): + cls.generate(arg, code, exception_message, exception_type_cname, exception_format_args, in_nogil_context) + def put_nonecheck(self, code): code.putln( "if (unlikely(%s == Py_None)) {" % self.condition()) @@ -12869,8 +13296,15 @@ return (self.type.is_ptr and not self.type.is_array) and self.arg.is_ephemeral() def generate_result_code(self, code): + from_py_function = None + # for certain source types, we can do better than the generic coercion + if self.type.is_string and self.arg.type is bytes_type: + if self.type.from_py_function.startswith('__Pyx_PyObject_As'): + from_py_function = '__Pyx_PyBytes' + self.type.from_py_function[len('__Pyx_PyObject'):] + NoneCheckNode.generate_if_needed(self.arg, code, "expected bytes, NoneType found") + code.putln(self.type.from_py_call_code( - self.arg.py_result(), self.result(), self.pos, code)) + self.arg.py_result(), self.result(), self.pos, code, from_py_function=from_py_function)) if self.type.is_pyobject: code.put_gotref(self.py_result()) @@ -12890,6 +13324,7 @@ Builtin.set_type: 'PySet_GET_SIZE', Builtin.frozenset_type: 'PySet_GET_SIZE', Builtin.bytes_type: 'PyBytes_GET_SIZE', + Builtin.bytearray_type: 'PyByteArray_GET_SIZE', Builtin.unicode_type: '__Pyx_PyUnicode_IS_TRUE', } @@ -12918,11 +13353,9 @@ return test_func = self._special_builtins.get(self.arg.type) if test_func is not None: - code.putln("%s = (%s != Py_None) && (%s(%s) != 0);" % ( - self.result(), - self.arg.py_result(), - test_func, - self.arg.py_result())) + checks = ["(%s != Py_None)" % self.arg.py_result()] if self.arg.may_be_none() else [] + checks.append("(%s(%s) != 0)" % (test_func, self.arg.py_result())) + code.putln("%s = %s;" % (self.result(), '&&'.join(checks))) else: code.putln( "%s = __Pyx_PyObject_IsTrue(%s); %s" % ( diff -Nru cython-0.26.1/Cython/Compiler/FlowControl.pxd cython-0.29.14/Cython/Compiler/FlowControl.pxd --- cython-0.26.1/Cython/Compiler/FlowControl.pxd 2015-09-10 16:25:36.000000000 +0000 +++ cython-0.29.14/Cython/Compiler/FlowControl.pxd 2019-04-14 10:00:58.000000000 +0000 @@ -11,10 +11,8 @@ cdef public list stats cdef public dict gen cdef public set bounded - cdef public dict input - cdef public dict output - # Big integer it bitsets + # Big integer bitsets cdef public object i_input cdef public object i_output cdef public object i_gen diff -Nru cython-0.26.1/Cython/Compiler/FlowControl.py cython-0.29.14/Cython/Compiler/FlowControl.py --- cython-0.26.1/Cython/Compiler/FlowControl.py 2016-12-10 15:41:15.000000000 +0000 +++ cython-0.29.14/Cython/Compiler/FlowControl.py 2018-09-22 14:18:56.000000000 +0000 @@ -341,14 +341,6 @@ return self.entry.type return self.inferred_type - def __getstate__(self): - return (self.lhs, self.rhs, self.entry, self.pos, - self.refs, self.is_arg, self.is_deletion, self.inferred_type) - - def __setstate__(self, state): - (self.lhs, self.rhs, self.entry, self.pos, - self.refs, self.is_arg, self.is_deletion, self.inferred_type) = state - class StaticAssignment(NameAssignment): """Initialised at declaration time, e.g. stack allocation.""" diff -Nru cython-0.26.1/Cython/Compiler/FusedNode.py cython-0.29.14/Cython/Compiler/FusedNode.py --- cython-0.26.1/Cython/Compiler/FusedNode.py 2017-08-25 16:06:31.000000000 +0000 +++ cython-0.29.14/Cython/Compiler/FusedNode.py 2019-11-01 14:13:39.000000000 +0000 @@ -127,9 +127,6 @@ # len(permutations)) # import pprint; pprint.pprint([d for cname, d in permutations]) - if self.node.entry in env.cfunc_entries: - env.cfunc_entries.remove(self.node.entry) - # Prevent copying of the python function self.orig_py_func = orig_py_func = self.node.py_func self.node.py_func = None @@ -139,12 +136,26 @@ fused_types = self.node.type.get_fused_types() self.fused_compound_types = fused_types + new_cfunc_entries = [] for cname, fused_to_specific in permutations: copied_node = copy.deepcopy(self.node) - # Make the types in our CFuncType specific + # Make the types in our CFuncType specific. type = copied_node.type.specialize(fused_to_specific) entry = copied_node.entry + type.specialize_entry(entry, cname) + + # Reuse existing Entries (e.g. from .pxd files). + for i, orig_entry in enumerate(env.cfunc_entries): + if entry.cname == orig_entry.cname and type.same_as_resolved_type(orig_entry.type): + copied_node.entry = env.cfunc_entries[i] + if not copied_node.entry.func_cname: + copied_node.entry.func_cname = entry.func_cname + entry = copied_node.entry + type = entry.type + break + else: + new_cfunc_entries.append(entry) copied_node.type = type entry.type, type.entry = type, entry @@ -165,9 +176,6 @@ self._specialize_function_args(copied_node.cfunc_declarator.args, fused_to_specific) - type.specialize_entry(entry, cname) - env.cfunc_entries.append(entry) - # If a cpdef, declare all specialized cpdefs (this # also calls analyse_declarations) copied_node.declare_cpdef_wrapper(env) @@ -181,6 +189,14 @@ if not self.replace_fused_typechecks(copied_node): break + # replace old entry with new entries + try: + cindex = env.cfunc_entries.index(self.node.entry) + except ValueError: + env.cfunc_entries.extend(new_cfunc_entries) + else: + env.cfunc_entries[cindex:cindex+1] = new_cfunc_entries + if orig_py_func: self.py_func = self.make_fused_cpdef(orig_py_func, env, is_def=False) @@ -209,7 +225,7 @@ """ Create a new local scope for the copied node and append it to self.nodes. A new local scope is needed because the arguments with the - fused types are aready in the local scope, and we need the specialized + fused types are already in the local scope, and we need the specialized entries created after analyse_declarations on each specialized version of the (CFunc)DefNode. f2s is a dict mapping each fused type to its specialized version @@ -260,7 +276,7 @@ def _fused_instance_checks(self, normal_types, pyx_code, env): """ - Genereate Cython code for instance checks, matching an object to + Generate Cython code for instance checks, matching an object to specialized types. """ for specialized_type in normal_types: @@ -374,7 +390,7 @@ coerce_from_py_func=memslice_type.from_py_function, dtype=dtype) decl_code.putln( - "{{memviewslice_cname}} {{coerce_from_py_func}}(object)") + "{{memviewslice_cname}} {{coerce_from_py_func}}(object, int)") pyx_code.context.update( specialized_type_name=specialized_type.specialization_string, @@ -384,7 +400,7 @@ u""" # try {{dtype}} if itemsize == -1 or itemsize == {{sizeof_dtype}}: - memslice = {{coerce_from_py_func}}(arg) + memslice = {{coerce_from_py_func}}(arg, 0) if memslice.memview: __PYX_XDEC_MEMVIEW(&memslice, 1) # print 'found a match for the buffer through format parsing' @@ -405,10 +421,11 @@ # The first thing to find a match in this loop breaks out of the loop pyx_code.put_chunk( u""" + """ + (u"arg_is_pythran_compatible = False" if pythran_types else u"") + u""" if ndarray is not None: if isinstance(arg, ndarray): dtype = arg.dtype - arg_is_pythran_compatible = True + """ + (u"arg_is_pythran_compatible = True" if pythran_types else u"") + u""" elif __pyx_memoryview_check(arg): arg_base = arg.base if isinstance(arg_base, ndarray): @@ -422,24 +439,30 @@ if dtype is not None: itemsize = dtype.itemsize kind = ord(dtype.kind) - # We only support the endianess of the current compiler + dtype_signed = kind == 'i' + """) + pyx_code.indent(2) + if pythran_types: + pyx_code.put_chunk( + u""" + # Pythran only supports the endianness of the current compiler byteorder = dtype.byteorder if byteorder == "<" and not __Pyx_Is_Little_Endian(): arg_is_pythran_compatible = False - if byteorder == ">" and __Pyx_Is_Little_Endian(): + elif byteorder == ">" and __Pyx_Is_Little_Endian(): arg_is_pythran_compatible = False - dtype_signed = kind == 'i' if arg_is_pythran_compatible: cur_stride = itemsize - for dim,stride in zip(reversed(arg.shape),reversed(arg.strides)): - if stride != cur_stride: + shape = arg.shape + strides = arg.strides + for i in range(arg.ndim-1, -1, -1): + if (strides[i]) != cur_stride: arg_is_pythran_compatible = False break - cur_stride *= dim + cur_stride *= shape[i] else: - arg_is_pythran_compatible = not (arg.flags.f_contiguous and arg.ndim > 1) - """) - pyx_code.indent(2) + arg_is_pythran_compatible = not (arg.flags.f_contiguous and (arg.ndim) > 1) + """) pyx_code.named_insertion_point("numpy_dtype_checks") self._buffer_check_numpy_dtype(pyx_code, buffer_types, pythran_types) pyx_code.dedent(2) @@ -448,7 +471,7 @@ self._buffer_parse_format_string_check( pyx_code, decl_code, specialized_type, env) - def _buffer_declarations(self, pyx_code, decl_code, all_buffer_types): + def _buffer_declarations(self, pyx_code, decl_code, all_buffer_types, pythran_types): """ If we have any buffer specializations, write out some variable declarations and imports. @@ -468,10 +491,14 @@ cdef Py_ssize_t itemsize cdef bint dtype_signed cdef char kind - cdef bint arg_is_pythran_compatible itemsize = -1 - arg_is_pythran_compatible = False + """) + + if pythran_types: + pyx_code.local_variable_declarations.put_chunk(u""" + cdef bint arg_is_pythran_compatible + cdef Py_ssize_t cur_stride """) pyx_code.imports.put_chunk( @@ -480,25 +507,27 @@ ndarray = __Pyx_ImportNumPyArrayTypeIfAvailable() """) + seen_typedefs = set() seen_int_dtypes = set() for buffer_type in all_buffer_types: dtype = buffer_type.dtype + dtype_name = self._dtype_name(dtype) if dtype.is_typedef: - #decl_code.putln("ctypedef %s %s" % (dtype.resolve(), - # self._dtype_name(dtype))) - decl_code.putln('ctypedef %s %s "%s"' % (dtype.resolve(), - self._dtype_name(dtype), - dtype.empty_declaration_code())) + if dtype_name not in seen_typedefs: + seen_typedefs.add(dtype_name) + decl_code.putln( + 'ctypedef %s %s "%s"' % (dtype.resolve(), dtype_name, + dtype.empty_declaration_code())) if buffer_type.dtype.is_int: if str(dtype) not in seen_int_dtypes: seen_int_dtypes.add(str(dtype)) - pyx_code.context.update(dtype_name=self._dtype_name(dtype), + pyx_code.context.update(dtype_name=dtype_name, dtype_type=self._dtype_type(dtype)) pyx_code.local_variable_declarations.put_chunk( u""" cdef bint {{dtype_name}}_is_signed - {{dtype_name}}_is_signed = <{{dtype_type}}> -1 < 0 + {{dtype_name}}_is_signed = not (<{{dtype_type}}> -1 > 0) """) def _split_fused_types(self, arg): @@ -654,7 +683,7 @@ default_idx += 1 if all_buffer_types: - self._buffer_declarations(pyx_code, decl_code, all_buffer_types) + self._buffer_declarations(pyx_code, decl_code, all_buffer_types, pythran_types) env.use_utility_code(Code.UtilityCode.load_cached("Import", "ImportExport.c")) env.use_utility_code(Code.UtilityCode.load_cached("ImportNumPyArray", "ImportExport.c")) diff -Nru cython-0.26.1/Cython/Compiler/Future.py cython-0.29.14/Cython/Compiler/Future.py --- cython-0.26.1/Cython/Compiler/Future.py 2015-06-22 12:53:11.000000000 +0000 +++ cython-0.29.14/Cython/Compiler/Future.py 2018-11-24 09:20:06.000000000 +0000 @@ -4,7 +4,7 @@ return getattr(__future__, name, object()) unicode_literals = _get_feature("unicode_literals") -with_statement = _get_feature("with_statement") +with_statement = _get_feature("with_statement") # dummy division = _get_feature("division") print_function = _get_feature("print_function") absolute_import = _get_feature("absolute_import") diff -Nru cython-0.26.1/Cython/Compiler/Lexicon.py cython-0.29.14/Cython/Compiler/Lexicon.py --- cython-0.26.1/Cython/Compiler/Lexicon.py 2016-12-10 15:41:15.000000000 +0000 +++ cython-0.29.14/Cython/Compiler/Lexicon.py 2018-11-24 09:20:06.000000000 +0000 @@ -3,7 +3,7 @@ # Cython Scanner - Lexical Definitions # -from __future__ import absolute_import +from __future__ import absolute_import, unicode_literals raw_prefixes = "rR" bytes_prefixes = "bB" diff -Nru cython-0.26.1/Cython/Compiler/Main.py cython-0.29.14/Cython/Compiler/Main.py --- cython-0.26.1/Cython/Compiler/Main.py 2017-08-12 14:06:59.000000000 +0000 +++ cython-0.29.14/Cython/Compiler/Main.py 2019-06-30 06:50:51.000000000 +0000 @@ -9,8 +9,8 @@ import sys import io -if sys.version_info[:2] < (2, 6) or (3, 0) <= sys.version_info[:2] < (3, 2): - sys.stderr.write("Sorry, Cython requires Python 2.6+ or 3.2+, found %d.%d\n" % tuple(sys.version_info[:2])) +if sys.version_info[:2] < (2, 6) or (3, 0) <= sys.version_info[:2] < (3, 3): + sys.stderr.write("Sorry, Cython requires Python 2.6+ or 3.3+, found %d.%d\n" % tuple(sys.version_info[:2])) sys.exit(1) try: @@ -18,12 +18,12 @@ except ImportError: basestring = str -from . import Errors # Do not import Parsing here, import it when needed, because Parsing imports # Nodes, which globally needs debug command line options initialized to set a # conditional metaclass. These options are processed by CmdLine called from # main() in this file. # import Parsing +from . import Errors from .StringEncoding import EncodedString from .Scanning import PyrexScanner, FileSourceDescriptor from .Errors import PyrexError, CompileError, error, warning @@ -38,6 +38,9 @@ verbose = 0 +standard_include_path = os.path.abspath(os.path.join(os.path.dirname(__file__), + os.path.pardir, 'Includes')) + class CompilationData(object): # Bundles the information that is passed from transform to transform. # (For now, this is only) @@ -52,6 +55,7 @@ # result CompilationResult pass + class Context(object): # This class encapsulates the context needed for compiling # one or more Cython implementation files along with their @@ -65,9 +69,10 @@ # language_level int currently 2 or 3 for Python 2/3 cython_scope = None + language_level = None # warn when not set but default to Py2 def __init__(self, include_directories, compiler_directives, cpp=False, - language_level=2, options=None, create_testscope=True): + language_level=None, options=None): # cython_scope is a hack, set to False by subclasses, in order to break # an infinite loop. # Better code organization would fix it. @@ -85,19 +90,25 @@ self.pxds = {} # full name -> node tree self._interned = {} # (type(value), value, *key_args) -> interned_value - standard_include_path = os.path.abspath(os.path.normpath( - os.path.join(os.path.dirname(__file__), os.path.pardir, 'Includes'))) - self.include_directories = include_directories + [standard_include_path] - - self.set_language_level(language_level) + if language_level is not None: + self.set_language_level(language_level) self.gdb_debug_outputwriter = None def set_language_level(self, level): + from .Future import print_function, unicode_literals, absolute_import, division + future_directives = set() + if level == '3str': + level = 3 + else: + level = int(level) + if level >= 3: + future_directives.add(unicode_literals) + if level >= 3: + future_directives.update([print_function, absolute_import, division]) self.language_level = level + self.future_directives = future_directives if level >= 3: - from .Future import print_function, unicode_literals, absolute_import, division - self.future_directives.update([print_function, unicode_literals, absolute_import, division]) self.modules['builtins'] = self.modules['__builtin__'] def intern_ustring(self, value, encoding=None): @@ -239,7 +250,7 @@ pxd = self.search_include_directories(qualified_name, ".pxd", pos, sys_path=sys_path) if pxd is None: # XXX Keep this until Includes/Deprecated is removed if (qualified_name.startswith('python') or - qualified_name in ('stdlib', 'stdio', 'stl')): + qualified_name in ('stdlib', 'stdio', 'stl')): standard_include_path = os.path.abspath(os.path.normpath( os.path.join(os.path.dirname(__file__), os.path.pardir, 'Includes'))) deprecated_include_path = os.path.join(standard_include_path, 'Deprecated') @@ -276,8 +287,13 @@ def search_include_directories(self, qualified_name, suffix, pos, include=False, sys_path=False): - return Utils.search_include_directories( - tuple(self.include_directories), qualified_name, suffix, pos, include, sys_path) + include_dirs = self.include_directories + if sys_path: + include_dirs = include_dirs + sys.path + # include_dirs must be hashable for caching in @cached_function + include_dirs = tuple(include_dirs + [standard_include_path]) + return search_include_directories(include_dirs, qualified_name, + suffix, pos, include) def find_root_package_dir(self, file_path): return Utils.find_root_package_dir(file_path) @@ -356,7 +372,7 @@ from ..Parser import ConcreteSyntaxTree except ImportError: raise RuntimeError( - "Formal grammer can only be used with compiled Cython with an available pgen.") + "Formal grammar can only be used with compiled Cython with an available pgen.") ConcreteSyntaxTree.p_module(source_filename) except UnicodeDecodeError as e: #import traceback @@ -426,6 +442,7 @@ pass result.c_file = None + def get_output_filename(source_filename, cwd, options): if options.cplus: c_suffix = ".cpp" @@ -441,6 +458,7 @@ else: return suggested_file_name + def create_default_resultobj(compilation_source, options): result = CompilationResult() result.main_source_file = compilation_source.source_desc.filename @@ -451,6 +469,7 @@ result.embedded_metadata = options.embedded_metadata return result + def run_pipeline(source, options, full_module_name=None, context=None): from . import Pipeline @@ -464,6 +483,8 @@ abs_path = os.path.abspath(source) full_module_name = full_module_name or context.extract_module_name(source, options) + Utils.raise_error_if_module_name_forbidden(full_module_name) + if options.relative_path_in_code_position_comments: rel_path = full_module_name.replace('.', os.sep) + source_ext if not abs_path.endswith(rel_path): @@ -496,15 +517,15 @@ return result -#------------------------------------------------------------------------ +# ------------------------------------------------------------------------ # # Main Python entry points # -#------------------------------------------------------------------------ +# ------------------------------------------------------------------------ class CompilationSource(object): """ - Contains the data necesarry to start up a compilation pipeline for + Contains the data necessary to start up a compilation pipeline for a single compilation unit. """ def __init__(self, source_desc, full_module_name, cwd): @@ -512,30 +533,13 @@ self.full_module_name = full_module_name self.cwd = cwd -class CompilationOptions(object): - """ - Options to the Cython compiler: - - show_version boolean Display version number - use_listing_file boolean Generate a .lis file - errors_to_stderr boolean Echo errors to stderr when using .lis - include_path [string] Directories to search for include files - output_file string Name of generated .c file - generate_pxi boolean Generate .pxi file for public declarations - capi_reexport_cincludes - boolean Add cincluded headers to any auto-generated - header files. - timestamps boolean Only compile changed source files. - verbose boolean Always print source names being compiled - compiler_directives dict Overrides for pragma options (see Options.py) - embedded_metadata dict Metadata to embed in the C file as json. - evaluate_tree_assertions boolean Test support: evaluate parse tree assertions - language_level integer The Python language level: 2 or 3 - formal_grammar boolean Parse the file with the formal grammar - cplus boolean Compile as c++ code +class CompilationOptions(object): + r""" + See default_options at the end of this module for a list of all possible + options and CmdLine.usage and CmdLine.parse_command_line() for their + meaning. """ - def __init__(self, defaults=None, **kw): self.include_path = [] if defaults: @@ -558,9 +562,10 @@ ', '.join(unknown_options)) raise ValueError(message) + directive_defaults = Options.get_directive_defaults() directives = dict(options['compiler_directives']) # copy mutable field # check for invalid directives - unknown_directives = set(directives) - set(Options.get_directive_defaults()) + unknown_directives = set(directives) - set(directive_defaults) if unknown_directives: message = "got unknown compiler directive%s: %s" % ( 's' if len(unknown_directives) > 1 else '', @@ -572,11 +577,13 @@ warnings.warn("C++ mode forced when in Pythran mode!") options['cplus'] = True if 'language_level' in directives and 'language_level' not in kw: - options['language_level'] = int(directives['language_level']) + options['language_level'] = directives['language_level'] + elif not options.get('language_level'): + options['language_level'] = directive_defaults.get('language_level') if 'formal_grammar' in directives and 'formal_grammar' not in kw: options['formal_grammar'] = directives['formal_grammar'] if options['cache'] is True: - options['cache'] = os.path.expanduser("~/.cycache") + options['cache'] = os.path.join(Utils.get_cython_cache_dir(), 'compiler') self.__dict__.update(options) @@ -589,6 +596,83 @@ return Context(self.include_path, self.compiler_directives, self.cplus, self.language_level, options=self) + def get_fingerprint(self): + r""" + Return a string that contains all the options that are relevant for cache invalidation. + """ + # Collect only the data that can affect the generated file(s). + data = {} + + for key, value in self.__dict__.items(): + if key in ['show_version', 'errors_to_stderr', 'verbose', 'quiet']: + # verbosity flags have no influence on the compilation result + continue + elif key in ['output_file', 'output_dir']: + # ignore the exact name of the output file + continue + elif key in ['timestamps']: + # the cache cares about the content of files, not about the timestamps of sources + continue + elif key in ['cache']: + # hopefully caching has no influence on the compilation result + continue + elif key in ['compiler_directives']: + # directives passed on to the C compiler do not influence the generated C code + continue + elif key in ['include_path']: + # this path changes which headers are tracked as dependencies, + # it has no influence on the generated C code + continue + elif key in ['working_path']: + # this path changes where modules and pxd files are found; + # their content is part of the fingerprint anyway, their + # absolute path does not matter + continue + elif key in ['create_extension']: + # create_extension() has already mangled the options, e.g., + # embedded_metadata, when the fingerprint is computed so we + # ignore it here. + continue + elif key in ['build_dir']: + # the (temporary) directory where we collect dependencies + # has no influence on the C output + continue + elif key in ['use_listing_file', 'generate_pxi', 'annotate', 'annotate_coverage_xml']: + # all output files are contained in the cache so the types of + # files generated must be part of the fingerprint + data[key] = value + elif key in ['formal_grammar', 'evaluate_tree_assertions']: + # these bits can change whether compilation to C passes/fails + data[key] = value + elif key in ['embedded_metadata', 'emit_linenums', 'c_line_in_traceback', 'gdb_debug', 'relative_path_in_code_position_comments']: + # the generated code contains additional bits when these are set + data[key] = value + elif key in ['cplus', 'language_level', 'compile_time_env', 'np_pythran']: + # assorted bits that, e.g., influence the parser + data[key] = value + elif key == ['capi_reexport_cincludes']: + if self.capi_reexport_cincludes: + # our caching implementation does not yet include fingerprints of all the header files + raise NotImplementedError('capi_reexport_cincludes is not compatible with Cython caching') + elif key == ['common_utility_include_dir']: + if self.common_utility_include_dir: + raise NotImplementedError('common_utility_include_dir is not compatible with Cython caching yet') + else: + # any unexpected option should go into the fingerprint; it's better + # to recompile than to return incorrect results from the cache. + data[key] = value + + def to_fingerprint(item): + r""" + Recursively turn item into a string, turning dicts into lists with + deterministic ordering. + """ + if isinstance(item, dict): + item = sorted([(repr(key), to_fingerprint(value)) for key, value in item.items()]) + return repr(item) + + return to_fingerprint(data) + class CompilationResult(object): """ @@ -678,13 +762,14 @@ processed.add(source) return results + def compile(source, options = None, full_module_name = None, **kwds): """ compile(source [, options], [,