pax_global_header00006660000000000000000000000064146442104500014512gustar00rootroot0000000000000052 comment=10cb0c23cd25756769a1a388ab0976245e7f6590 sip-6.8.6/000077500000000000000000000000001464421045000123265ustar00rootroot00000000000000sip-6.8.6/.git_archival.txt000066400000000000000000000001511464421045000155760ustar00rootroot00000000000000node: 10cb0c23cd25756769a1a388ab0976245e7f6590 node-date: 2024-07-12T12:19:04+01:00 describe-name: 6.8.6 sip-6.8.6/.gitattributes000066400000000000000000000000371464421045000152210ustar00rootroot00000000000000.git_archival.txt export-subst sip-6.8.6/.gitignore000066400000000000000000000011571464421045000143220ustar00rootroot00000000000000# Local additions to Python.gitignore from GitHub. sipbuild/_version.py # Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] *$py.class # Distribution / packaging .Python build/ develop-eggs/ dist/ downloads/ eggs/ .eggs/ lib/ lib64/ parts/ sdist/ var/ wheels/ share/python-wheels/ *.egg-info/ .installed.cfg *.egg MANIFEST # Installer logs pip-log.txt pip-delete-this-directory.txt # Unit test / coverage reports htmlcov/ .tox/ .nox/ .coverage .coverage.* .cache nosetests.xml coverage.xml *.cover *.py,cover .hypothesis/ .pytest_cache/ cover/ # Translations *.mo *.pot # Sphinx documentation docs/_build/ sip-6.8.6/.readthedocs.yaml000066400000000000000000000013071464421045000155560ustar00rootroot00000000000000# .readthedocs.yaml # Read the Docs configuration file # See https://docs.readthedocs.io/en/stable/config-file/v2.html for details # Required version: 2 # Set the OS, Python version and other tools you might need build: os: ubuntu-22.04 tools: python: "3.12" # Build documentation in the "docs/" directory with Sphinx sphinx: configuration: docs/conf.py # Optionally build your docs in additional formats such as PDF and ePub # formats: # - pdf # - epub # Optional but recommended, declare the Python requirements required # to build your documentation # See https://docs.readthedocs.io/en/stable/guides/reproducible-builds.html python: install: - requirements: docs/requirements.txt sip-6.8.6/LICENSE000066400000000000000000000024241464421045000133350ustar00rootroot00000000000000Copyright 2024 Phil Thompson Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. sip-6.8.6/MANIFEST.in000066400000000000000000000001151464421045000140610ustar00rootroot00000000000000exclude .git* exclude .readthedocs.yaml prune docs prune examples prune test sip-6.8.6/README.md000066400000000000000000000064501464421045000136120ustar00rootroot00000000000000# SIP - A Python Bindings Generator for C and C++ Libraries One of the features of Python that makes it so powerful is the ability to take existing libraries, written in C or C++, and make them available as Python extension modules. Such extension modules are often called bindings for the library. SIP is a collection of tools that makes it very easy to create Python bindings for C and C++ libraries. It was originally developed in 1998 to create [PyQt](https://pypi.org/project/PyQt6/), the Python bindings for the Qt toolkit, but can be used to create bindings for any C or C++ library. For example it is also used to generate [wxPython](https://wxpython.org/), the Python bindings for [wxWidgets](https://wxwidgets.org/). SIP comprises a set of build tools and a `sip` module. The build tools process a set of `.sip` specification files and generates C or C++ code which is then compiled to create the bindings extension module. Several extension modules may be installed in the same Python package. Extension modules can be built so that they are are independent of the version of Python being used. The specification files contain a description of the interface of the C or C++ library, i.e. the classes, methods, functions and variables. The format of a specification file is almost identical to a C or C++ header file, so much so that the easiest way of creating a specification file is to edit a copy of the corresponding header file. The `sip` module provides support functions to the automatically generated code. The `sip` module is installed as part of the same Python package as the generated extension modules. Unlike the extension modules the `sip` module is specific to a particular version of Python (e.g. v3.8, v3.9, v3.10, v3.11, v3.12). SIP makes it easy to exploit existing C or C++ libraries in a productive interpretive programming environment. SIP also makes it easy to take a Python application (maybe a prototype) and selectively implement parts of the application (maybe for performance reasons) in C or C++. [MetaSIP](https://github.com/Python-SIP/metasip/) is a GUI development for SIP that can take the header files of a C/C++ library into a project from which API items can be managed, compared with new versions etc. and from which the `.sip` specification files can be generated from. ## Documentation The documentation can be found at [Read the Docs](https://python-sip.readthedocs.io). ## License SIP is licensed under the BSD 2 clause license. SIP includes a copy of [ply](https://github.com/dabeaz/ply/) which is licensed under the BSD 3 clause license. ## Installation To install SIP, run: pip install sip ## Creating Packages for Distribution Python sdists and wheels can be created with any standard Python build frontend. For example, using [build](https://pypi.org/project/build/) an sdist and wheel will be created from a checkout in the current directory by running: python -m build --outdir . ## Building the Documentation The documentation is built using [Sphinx](https://pypi.org/project/Sphinx/), [myst_parser](https://pypi.org/project/myst-parser/) and the [sphinx-rtd-theme](https://pypi.org/project/sphinx-rtd-theme/) theme. Change to the `docs` directory of a checkout and run: make html The HTML documentation can then be found in the `_build/html` subdirectory. sip-6.8.6/docs/000077500000000000000000000000001464421045000132565ustar00rootroot00000000000000sip-6.8.6/docs/Makefile000066400000000000000000000011721464421045000147170ustar00rootroot00000000000000# Minimal makefile for Sphinx documentation # # You can set these variables from the command line, and also # from the environment for the first two. SPHINXOPTS ?= SPHINXBUILD ?= sphinx-build SOURCEDIR = . BUILDDIR = _build # Put it first so that "make" without argument is like "make help". help: @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) .PHONY: help Makefile # Catch-all target: route all unknown targets to Sphinx using the new # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). %: Makefile @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) sip-6.8.6/docs/abi_12.rst000066400000000000000000002135011464421045000150470ustar00rootroot00000000000000ABI v12 for Handwritten Code ============================ In this section we describe the v12 of the ABI, provided by the :mod:`sip` module, that can be used by handwritten code in specification files. .. c:macro:: SIP_API_MAJOR_NR This is a C preprocessor symbol that defines the major number of the SIP API. Its value is a number. There is no direct relationship between this and the SIP version number. .. c:macro:: SIP_API_MINOR_NR This is a C preprocessor symbol that defines the minor number of the SIP API. Its value is a number. There is no direct relationship between this and the SIP version number. .. c:macro:: SIP_BLOCK_THREADS This is a C preprocessor macro that will make sure the Python Global Interpreter Lock (GIL) is acquired. Python API calls must only be made when the GIL has been acquired. There must be a corresponding :c:macro:`SIP_UNBLOCK_THREADS` at the same lexical scope. .. c:macro:: SIP_NO_CONVERTORS This is a flag used by various type convertors that suppresses the use of a type's :directive:`%ConvertToTypeCode`. .. c:macro:: SIP_NOT_NONE This is a flag used by various type convertors that causes the conversion to fail if the Python object being converted is ``Py_None``. .. c:macro:: SIP_NULLPTR This is a C preprocessor macro that should be used instead of ``NULL`` or ``nullptr``. It ensures the correct value is used depending on whether C or C++ is being generated and which language standard the compiler supports. .. c:macro:: SIP_OWNS_MEMORY This is a flag used by various array constructors that species that the array owns the memory that holds the array's contents. .. c:macro:: SIP_PROTECTED_IS_PUBLIC This is a C preprocessor symbol that is defined automatically by the build system to specify that the generated code is being compiled with ``protected`` redefined as ``public``. This allows handwritten code to determine if the generated helper functions for accessing protected C++ functions are available (see :directive:`%MethodCode`). .. c:macro:: SIP_READ_ONLY This is a flag used by various array constructors that species that the array is read-only. .. c:function:: void SIP_RELEASE_GIL(sip_gilstate_t sipGILState) This is called from the handwritten code specified with the :directive:`VirtualErrorHandler` in order to release the Python Global Interpreter Lock (GIL) prior to changing the execution path (e.g. by throwing a C++ exception). It should not be called under any other circumstances. :param sipGILState: an opaque value provided to the handwritten code by SIP. .. c:macro:: SIP_SSIZE_T .. deprecated:: 12.0 This will be removed in ABI v13, use ``Py_ssize_t`` instead. This is a C preprocessor macro that is defined as ``Py_ssize_t``. .. c:macro:: SIP_SSIZE_T_FORMAT .. deprecated:: 12.0 This will be removed in v13, use ``%zd`` instead. This is a C preprocessor macro that is defined as ``%zd``. .. c:macro:: SIP_UNBLOCK_THREADS This is a C preprocessor macro that will restore the Python Global Interpreter Lock (GIL) to the state it was prior to the corresponding :c:macro:`SIP_BLOCK_THREADS`. .. c:macro:: SIP_USE_PYCAPSULE .. deprecated:: 12.0 This will be removed in v13. It will always be defined. This is a C preprocessor symbol that is defined when ``PyCapsule`` objects are being used rather than the (now deprecated) ``PyCObject`` objects. .. c:macro:: SIP_VERSION This is a C preprocessor symbol that defines the SIP version number represented as a 3 part hexadecimal number (e.g. v5.0.0 is represented as ``0x050000``). .. c:macro:: SIP_VERSION_STR This is a C preprocessor symbol that defines the SIP version number represented as a string. For development versions it will contain ``.dev``. .. c:function:: sipErrorState sipBadCallableArg(int arg_nr, PyObject *arg) This is called from :directive:`%MethodCode` to raise a Python exception when an argument to a function, a C++ constructor or method is found to have an unexpected type. This should be used when the :directive:`%MethodCode` does additional type checking of the supplied arguments. :param arg_nr: the number of the argument. Arguments are numbered from 0 but are numbered from 1 in the detail of the exception. :param arg: the argument. :return: the value that should be assigned to ``sipError``. .. c:function:: void sipBadCatcherResult(PyObject *method) This raises a Python exception when the result of a Python reimplementation of a C++ method doesn't have the expected type. It is normally called by handwritten code specified with the :directive:`%VirtualCatcherCode` directive. :param method: the Python method and would normally be the supplied ``sipMethod``. .. c:function:: void sipBadLengthForSlice(Py_ssize_t seqlen, Py_ssize_t slicelen) This raises a Python exception when the length of a slice object is inappropriate for a sequence-like object. It is normally called by handwritten code specified for :meth:`__setitem__` methods. :param seqlen: the length of the sequence. :param slicelen: the length of the slice. .. c:type:: sipBufferInfoDef This C structure is used with :c:func:`sipGetBufferInfo()` and :c:func:`sipReleaseBufferInfo()` and encapsulates information provided by a Python object that implements the buffer protocol. The structure elements are as follows. .. c:member:: void *bi_buf The address of the buffer. .. c:member:: PyObject *bi_obj A reference to the object that implements the buffer protocol. .. c:member:: Py_ssize_t bi_len The length of the buffer in bytes. .. c:member:: char *bi_format The format of each element of the buffer. .. c:function:: PyObject *sipBuildResult(int *iserr, const char *format, ...) This creates a Python object based on a format string and associated values in a similar way to the Python :c:func:`Py_BuildValue()` function. :param iserr: if this is not ``NULL`` then the location it points to is set to a non-zero value. :param format: the string of format characters. :return: If there was an error then ``NULL`` is returned and a Python exception is raised. If the format string begins and ends with parentheses then a tuple of objects is created. If it contains more than one format character then parentheses must be specified. In the following description the first letter is the format character, the entry in parentheses is the Python object type that the format character will create, and the entry in brackets are the types of the C/C++ values to be passed. ``a`` (string) [char] Convert a C/C++ ``char`` to a Python ``str`` object. ``b`` (boolean) [int] Convert a C/C++ ``int`` to a Python boolean. ``c`` (string/bytes) [char] Convert a C/C++ ``char`` to a Python ``bytes`` object. ``d`` (float) [double] Convert a C/C++ ``double`` to a Python floating point number. ``e`` (integer) [enum] Convert an anonymous C/C++ ``enum`` to a Python integer. ``f`` (float) [float] Convert a C/C++ ``float`` to a Python floating point number. ``g`` (string/bytes) [char \*, :c:macro:`Py_ssize_t`] Convert a C/C++ character array and its length to a Python ``bytes`` object. If the array is ``NULL`` then the length is ignored and the result is ``Py_None``. ``h`` (integer) [short] Convert a C/C++ ``short`` to a Python integer. ``i`` (integer) [int] Convert a C/C++ ``int`` to a Python integer. ``l`` (long) [long] Convert a C/C++ ``long`` to a Python integer. ``m`` (long) [unsigned long] Convert a C/C++ ``unsigned long`` to a Python long. ``n`` (long) [long long] Convert a C/C++ ``long long`` to a Python long. ``o`` (long) [unsigned long long] Convert a C/C++ ``unsigned long long`` to a Python long. ``r`` (wrapped instance) [*type* \*, :c:macro:`Py_ssize_t`, const :c:type:`sipTypeDef` \*] Convert an array of C structures, C++ classes or mapped type instances to a Python tuple. Note that copies of the array elements are made. ``s`` (string/bytes) [char \*] Convert a C/C++ ``'\0'`` terminated string to a Python ``bytes`` object. If the string pointer is ``NULL`` then the result is ``Py_None``. ``t`` (long) [unsigned short] Convert a C/C++ ``unsigned short`` to a Python long. ``u`` (long) [unsigned int] Convert a C/C++ ``unsigned int`` to a Python long. ``w`` (unicode/string) [wchar_t] Convert a C/C++ wide character to a Python ``str`` object. ``x`` (unicode/string) [wchar_t \*] Convert a C/C++ ``L'\0'`` terminated wide character string to a Python ``str`` object. If the string pointer is ``NULL`` then the result is ``Py_None``. ``A`` (string) [char \*] Convert a C/C++ ``'\0'`` terminated string to a Python ``str`` object. If the string pointer is ``NULL`` then the result is ``Py_None``. ``D`` (wrapped instance) [*type* \*, const :c:type:`sipTypeDef` \*, PyObject \*] Convert a C structure, C++ class or mapped type instance to a Python object. If the instance has already been wrapped then the result is a new reference to the existing object. Ownership of the instance is determined by the ``PyObject *`` argument. If it is ``NULL`` and the instance has already been wrapped then the ownership is unchanged. If it is ``NULL`` and the instance is newly wrapped then ownership will be with C/C++. If it is ``Py_None`` then ownership is transferred to Python via a call to :c:func:`sipTransferBack()`. Otherwise ownership is transferred to C/C++ and the instance associated with the ``PyObject *`` argument via a call to :c:func:`sipTransferTo()`. The Python class is influenced by any applicable :directive:`%ConvertToSubClassCode` code. ``F`` (wrapped enum) [enum, :c:type:`sipTypeDef` \*] Convert a named C/C++ ``enum`` to an instance of the corresponding Python named enum type. ``G`` (unicode) [wchar_t \*, :c:macro:`Py_ssize_t`] Convert a C/C++ wide character array and its length to a Python unicode object. If the array is ``NULL`` then the length is ignored and the result is ``Py_None``. ``L`` (integer) [char] Convert a C/C++ ``char`` to a Python integer. ``M`` (long) [unsigned char] Convert a C/C++ ``unsigned char`` to a Python long. ``N`` (wrapped instance) [*type* \*, :c:type:`sipTypeDef` \*, PyObject \*] Convert a new C structure, C++ class or mapped type instance to a Python object. Ownership of the instance is determined by the ``PyObject *`` argument. If it is ``NULL`` and the instance has already been wrapped then the ownership is unchanged. If it is ``NULL`` or ``Py_None`` then ownership will be with Python. Otherwise ownership will be with C/C++ and the instance associated with the ``PyObject *`` argument. The Python class is influenced by any applicable :directive:`%ConvertToSubClassCode` code. ``R`` (object) [PyObject \*] The result is value passed without any conversions. The reference count is unaffected, i.e. a reference is taken. ``S`` (object) [PyObject \*] The result is value passed without any conversions. The reference count is incremented. ``V`` (sip.voidptr) [void \*] Convert a C/C++ ``void *`` to a Python :class:`sip.voidptr` object. ``z`` (object) [const char \*, void \*] Convert a C/C++ ``void *`` to a Python named capsule object. ``=`` (long) [size_t] Convert a C/C++ ``size_t`` to a Python long. .. c:function:: PyObject *sipCallMethod(int *iserr, PyObject *method, const char *format, ...) This calls a Python method passing a tuple of arguments based on a format string and associated values in a similar way to the Python :c:func:`PyObject_CallObject()` function. :param iserr: if this is not ``NULL`` then the location it points to is set to a non-zero value if there was an error. :param method: the Python bound method to call. :param format: the string of format characters (see :c:func:`sipBuildResult()`). :return: If there was an error then ``NULL`` is returned and a Python exception is raised. It is normally called by handwritten code specified with the :directive:`%VirtualCatcherCode` directive with method being the supplied ``sipMethod``. .. c:function:: int sipCanConvertToType(PyObject *obj, const sipTypeDef *td, int flags) This checks if a Python object can be converted to an instance of a C structure, C++ class or mapped type. :param obj: the Python object. :param td: the C/C++ type's :ref:`generated type structure `. :param flags: any combination of the :c:macro:`SIP_NOT_NONE` and :c:macro:`SIP_NO_CONVERTORS` flags. :return: a non-zero value if the object can be converted. .. c:type:: sipCFunctionDef This C structure is used with :c:func:`sipGetCFunction()` and encapsulates the components parts of a Python C function. The structure elements are as follows. .. c:member:: PyMethodDef *cf_function The C function. .. c:member:: PyObject *cf_self The optional bound object. .. c:function:: PyObject *sipConvertFromConstVoidPtr(const void *cpp) This creates a :class:`sip.voidptr` object for a memory address. The object will not be writeable and has no associated size. :param cpp: the memory address. :return: the :class:`sip.voidptr` object. .. c:function:: PyObject *sipConvertFromConstVoidPtrAndSize(const void *cpp, Py_ssize_t size) This creates a :class:`sip.voidptr` object for a memory address. The object will not be writeable and can be used as an immutable buffer object. :param cpp: the memory address. :param size: the size associated with the address. :return: the :class:`sip.voidptr` object. .. c:function:: PyObject *sipConvertFromEnum(int eval, const sipTypeDef *td) This converts a named C/C++ ``enum`` to a Python object. If the enum is a C++11 scoped enum then the Python object is created using the :py:mod:`enum` module. Otherwise a SIP generated type is used that can itself be converted to an ``int``. :param eval: the enumerated value to convert. :param td: the enum's :ref:`generated type structure `. :return: the Python object. .. c:function:: PyObject *sipConvertFromNewPyType(void *cpp, PyTypeObject *py_type, sipWrapper *owner, sipSimpleWrapper **selfp, const char *format, ...) This converts a new C structure or a C++ class instance to an instance of a corresponding Python type (as opposed to the corresponding generated Python type). This is useful when the C/C++ library provides some sort of mechanism whereby handwritten code has some control over the exact type of structure or class being created. Typically it would be used to create an instance of the generated derived class which would then allow Python re-implementations of C++ virtual methods to function properly. :param cpp: the C/C++ instance. :param py_type: the Python type object. This is called to create the Python object and is passed the arguments defined by the string of format characters. :param owner: is the optional owner of the Python object. :param selfp: is an optional pointer to the ``sipPySelf`` instance variable of the C/C++ instance if that instance's type is a generated derived class. Otherwise it should be ``NULL``. :param format: the string of format characters (see :c:func:`sipBuildResult()`). :return: the Python object. If there was an error then ``NULL`` is returned and a Python exception is raised. .. c:function:: PyObject *sipConvertFromNewType(void *cpp, const sipTypeDef *td, PyObject *transferObj) This converts a new C structure or a C++ class instance to an instance of the corresponding generated Python type. :param cpp: the C/C++ instance. :param td: the type's :ref:`generated type structure `. :param transferObj: this controls the ownership of the returned value. :return: the Python object. If *transferObj* is ``NULL`` or ``Py_None`` then ownership will be with Python. Otherwise ownership will be with C/C++ and the instance associated with *transferObj*. The Python type is influenced by any applicable :directive:`%ConvertToSubClassCode` code. .. c:function:: Py_ssize_t sipConvertFromSequenceIndex(Py_ssize_t idx, Py_ssize_t len) This converts a Python sequence index (i.e. where a negative value refers to the offset from the end of the sequence) to a C/C++ array index. If the index was out of range then a negative value is returned and a Python exception raised. :param idx: the sequence index. :param len: the length of the sequence. :return: the unsigned array index. .. c:function:: int sipConvertFromSliceObject(PyObject *slice, Py_ssize_t length, Py_ssize_t *start, Py_ssize_t *stop, Py_ssize_t *step, Py_ssize_t *slicelength) This is a thin wrapper around Python's :c:func:`PySlice_Unpack()` and :c:func:`PySlice_AdjustIndices()` functions. .. c:function:: PyObject *sipConvertFromType(void *cpp, const sipTypeDef *td, PyObject *transferObj) This converts a C structure or a C++ class instance to an instance of the corresponding generated Python type. :param cpp: the C/C++ instance. :param td: the type's :ref:`generated type structure `. :param transferObj: this controls the ownership of the returned value. :return: the Python object. If the C/C++ instance has already been wrapped then the result is a new reference to the existing object. If *transferObj* is ``NULL`` and the instance has already been wrapped then the ownership is unchanged. If *transferObj* is ``NULL`` and the instance is newly wrapped then ownership will be with C/C++. If *transferObj* is ``Py_None`` then ownership is transferred to Python via a call to :c:func:`sipTransferBack()`. Otherwise ownership is transferred to C/C++ and the instance associated with *transferObj* via a call to :c:func:`sipTransferTo()`. The Python class is influenced by any applicable :directive:`%ConvertToSubClassCode` code. .. c:function:: PyObject *sipConvertFromVoidPtr(void *cpp) This creates a :class:`sip.voidptr` object for a memory address. The object will be writeable but has no associated size. :param cpp: the memory address. :return: the :class:`sip.voidptr` object. .. c:function:: PyObject *sipConvertFromVoidPtrAndSize(void *cpp, Py_ssize_t size) This creates a :class:`sip.voidptr` object for a memory address. The object will be writeable and can be used as a mutable buffer object. :param cpp: the memory address. :param size: the size associated with the address. :return: the :class:`sip.voidptr` object. .. c:function:: PyObject *sipConvertToArray(void *data, const char *format, Py_ssize_t len, int flags) This converts a one dimensional array of fundamental types to a :class:`sip.array` object. An array is very like a Python :class:`memoryview` object. The underlying memory is not copied and may be modified in situ. Arrays support the buffer protocol and so can be passed to other modules, again without the underlying memory being copied. :param data: the address of the start of the C/C++ array. :param format: the format, as defined by the :mod:`struct` module, of an array element. At the moment only ``b`` (char), ``B`` (unsigned char), ``h`` (short), ``H`` (unsigned short), ``i`` (int), ``I`` (unsigned int), ``f`` (float) and ``d`` (double) are supported. :param len: the number of elements in the array. :param readonly: is non-zero if the array is read-only. :param flags: any combination of the :c:macro:`SIP_READ_ONLY` and :c:macro:`SIP_OWNS_MEMORY` flags. :return: the :class:`sip.array` object. .. c:function:: int sipConvertToBool(PyObject *obj) This converts a Python object to an integer corresponding to a C++ ``bool``. :param obj: the Python object to convert. :return: the boolean value as an integer. ``1`` corresponds to ``true`` and ``0`` corresponds to ``false``. ``-1`` is returned, and an exception is raised, if there was an error. .. c:function:: int sipConvertToEnum(PyObject *obj, const sipTypeDef *td) This converts a Python object to the value of a named C/C++ ``enum`` member. If the enum is a C++11 scoped enum then the Python object must be a member of the enum. Otherwise it may also be an ``int`` corresponding to the name of the member. :param obj: the Python object to convert. :param td: the enum's :ref:`generated type structure `. :return: the integer value. An exception is raised if there was an error. .. c:function:: void *sipConvertToType(PyObject *obj, const sipTypeDef *td, PyObject *transferObj, int flags, int *state, int *iserr) This converts a Python object to an instance of a C structure, C++ class or mapped type assuming that a previous call to :c:func:`sipCanConvertToType()` has been successful. :param obj: the Python object. :param td: the type's :ref:`generated type structure `. :param transferObj: this controls any ownership changes to *obj*. :param flags: any combination of the :c:macro:`SIP_NOT_NONE` and :c:macro:`SIP_NO_CONVERTORS` flags. :param state: the state of the returned C/C++ instance is returned via this pointer. :param iserr: the error flag is passed and updated via this pointer. :return: the C/C++ instance. If *transferObj* is ``NULL`` then the ownership is unchanged. If it is ``Py_None`` then ownership is transferred to Python via a call to :c:func:`sipTransferBack()`. Otherwise ownership is transferred to C/C++ and *obj* associated with *transferObj* via a call to :c:func:`sipTransferTo()`. Note that *obj* can also be managed by the C/C++ instance itself, but this can only be achieved by using :c:func:`sipTransferTo()`. If *state* is not ``NULL`` then the location it points to is set to describe the state of the returned C/C++ instance and is the value returned by any :directive:`%ConvertToTypeCode`. The calling code must then release the value at some point to prevent a memory leak by calling :c:func:`sipReleaseType()`. If there is an error then the location *iserr* points to is set to a non-zero value. If it was initially a non-zero value then the conversion isn't attempted in the first place. (This allows several calls to be made that share the same error flag so that it only needs to be tested once rather than after each call.) .. c:function:: PyObject *sipConvertToTypedArray(void *data, const sipTypeDef *td, const char *format, size_t stride, Py_ssize_t len, int flags) This converts a one dimensional array of instances of a C structure, C++ class or mapped type to a :class:`sip.array` object. An array is very like a Python :class:`memoryview` object but it's elements correspond to C structures or C++ classes. The underlying memory is not copied and may be modified in situ. Arrays support the buffer protocol and so can be passed to other modules, again without the underlying memory being copied. :param data: the address of the start of the C/C++ array. :param td: an element's type's :ref:`generated type structure `. :param format: the format, as defined by the :mod:`struct` module, of an array element. :param stride: the size of an array element, including any padding. :param len: the number of elements in the array. :param flags: the optional :c:macro:`SIP_READ_ONLY` flag. :return: the :class:`sip.array` object. .. c:function:: void *sipConvertToVoidPtr(PyObject *obj) This converts a Python object to a memory address. :c:func:`PyErr_Occurred()` must be used to determine if the conversion was successful. :param obj: the Python object which may be ``Py_None``, a :class:`sip.voidptr` or a :c:type:`PyCObject`. :return: the memory address. .. c:type:: sipDateDef This C structure is used with :c:func:`sipGetDate()`, :c:func:`sipFromDate()`, :c:func:`sipGetDateTime()` and :c:func:`sipFromDateTime()` and encapsulates the components parts of a Python date. The structure elements are as follows. .. c:member:: int pd_year The year. .. c:member:: int pd_month The month (1-12). .. c:member:: int pd_day The day (1-31). .. c:function:: int sipEnableAutoconversion(const sipTypeDef *td, int enable) Instances of some classes may be automatically converted to other Python objects even though the class has been wrapped. This allows that behaviour to be suppressed so that an instances of the wrapped class is returned instead. :param td: the type's :ref:`generated type structure `. This must refer to a class. :param enable: is non-zero if auto-conversion should be enabled for the type. This is the default behaviour. :return: ``1`` or ``0`` depending on whether or not auto-conversion was previously enabled for the type. This allows the previous state to be restored later on. ``-1`` is returned, and a Python exception raised, if there was an error. .. c:function:: int sipEnableGC(int enable) This enables or disables the Python garbarge collector. :param enable: is greater than ``0`` if the garbage collector should be enabled. :return: ``1`` or ``0`` depending on whether or not the garbage collector was previously enabled. This allows the previous state to be restored later on. ``-1`` is returned if there was an error. .. c:function:: int sipEnableOverflowChecking(int enable) This enables or disables the checking for overflows when converting Python integer objects to C/C++ integer types. When it is enabled an exception is raised when the value of a Python integer object is too large to fit in the corresponding C/C++ type. By default it is disabled. :param enable: is greater than ``0`` if overflow checking should be enabled. :return: ``1`` or ``0`` depending on whether or not overflow chacking was previously enabled. This allows the previous state to be restored later on. .. cpp:enum:: sipEventType This is the enum that defines the different event types. .. cpp:enumerator:: sipEventWrappedInstance This event is triggered whenever a C/C++ instance that is created by C/C++ (and not by Python) is wrapped. The handler is passed a ``void *`` which is the address of the C/C++ instance. .. cpp:enumerator:: sipEventCollectingWrapper This event is triggered whenever a Python wrapper object is being garbage collected. The handler is passed a pointer to the :c:type:`sipSimpleWrapper` object that is the Python wrapper object being garbage collected. .. c:function:: int sipExportSymbol(const char *name, void *sym) Python does not allow extension modules to directly access symbols in another extension module. This exports a symbol, referenced by a name, that can subsequently be imported, using :c:func:`sipImportSymbol()`, by another module. :param name: the name of the symbol. :param sym: the value of the symbol. :return: 0 if there was no error. A negative value is returned if *name* is already associated with a symbol or there was some other error. .. c:function:: const sipTypeDef *sipFindType(const char *type) This returns a pointer to the :ref:`generated type structure ` corresponding to a C/C++ type. :param type: the C/C++ declaration of the type. :return: the generated type structure. This will not change and may be saved in a static cache. ``NULL`` is returned if the C/C++ type doesn't exist. .. c:function:: void *sipForceConvertToType(PyObject *obj, const sipTypeDef *td, PyObject *transferObj, int flags, int *state, int *iserr) This converts a Python object to an instance of a C structure, C++ class or mapped type by calling :c:func:`sipCanConvertToType()` and, if it is successfull, calling :c:func:`sipConvertToType()`. See :c:func:`sipConvertToType()` for a full description of the arguments. .. c:function:: void sipFree(void *mem) This returns an area of memory allocated by :c:func:`sipMalloc()` to the heap. :param mem: the memory address. .. c:function:: PyObject *sipFromDate(const sipDateDef *date) This creates a Python date object from its component parts. :param date: the component parts of the date. :return: the Python date object. .. c:function:: PyObject *sipFromDateTime(const sipDateDef *date, const sipTimeDef *time) This creates a Python datetime object from its component parts. :param date: the date related component parts of the datetime. :param time: the time related component parts of the datetime. :return: the Python datetime object. .. c:function:: PyObject *sipFromMethod(const sipMethodDef *method) This creates a Python method object from its component parts. :param method: the component parts of the method. :return: the Python method object. .. c:function:: PyObject *sipFromTime(const sipTimeDef *time) This creates a Python time object from its component parts. :param time: the component parts of the time. :return: the Python time object. .. c:function:: void *sipGetAddress(sipSimpleWrapper *obj) This returns the address of the C structure or C++ class instance wrapped by a Python object. :param obj: the Python object. :return: the address of the C/C++ instance .. c:function:: int sipGetBufferInfo(PyObject *obj, sipBufferInfoDef *buffer_info) This checks to see if an object implements the Python buffer protocol and, if so, optionally returns the buffer information. It is similar to :c:func:`PyObject_GetBuffer` and should be used instead of that when the limited Python API is enabled. Note that, at the moment, only 1-dimensional buffers are supported. :param obj: the Python object. :param buffer_info: if this is not ``NULL``, and the object implements the buffer protocol, then the buffer information is returned in this structure. There should be a corresponding call to :c:func:`sipReleaseBuffer`. :return: > 0 if the object supports the buffer protocol and the buffer information was returned (if requested). 0 if the object does not support the buffer protocol. < 0 (and a Python exception is raised) if the object supports the buffer protocol but there was an error returning the requested buffer information. .. c:function:: int sipGetCFunction(PyObject *obj, sipCFunctionDef *c_function) This checks to see if an object is a Python C function object and, if so, optionally returns its component parts. :param obj: the Python object. :param c_function: if this is not ``NULL``, and the object is a C function object, then the component parts are returned in this structure. :return: a non-zero value if the object is a Python C function object. .. c:function:: int sipGetDate(PyObject *obj, sipDateDef *date) This checks to see if an object is a Python date object and, if so, optionally returns its component parts. :param obj: the Python object. :param date: if this is not ``NULL``, and the object is a date object, then the component parts are returned in this structure. :return: a non-zero value if the object is a Python date object. .. c:function:: int sipGetDateTime(PyObject *obj, sipDateDef *date, sipTimeDef *time) This checks to see if an object is a Python datetime object and, if so, optionally returns its component parts. :param obj: the Python object. :param date: if this is not ``NULL``, and the object is a datetime object, then the date related component parts are returned in this structure. :param time: if this is not ``NULL``, and the object is a datetime object, then the time related component parts are returned in this structure. :return: a non-zero value if the object is a Python datetime object. .. c:function:: PyFrameObject *sipGetFrame(int depth) This retrieves a frame object from the current execution stack. .. note:: On PyPy this will always return NULL. :param depth: the depth of frame to retrieve where 0 is the current frame, 1 is the previous frame etc. :return: the opaque frame or NULL if there wasn't one at the given depth. .. c:function:: PyInterpreterState *sipGetInterpreter() This returns the address of the Python interpreter. If it is ``NULL`` then calls to the Python interpreter library must not be made. :return: the address of the Python interpreter .. c:function:: int sipGetMethod(PyObject *obj, sipMethodDef *method) This checks to see if an object is a Python method object and, if so, optionally returns its component parts. :param obj: the Python object. :param method: if this is not ``NULL``, and the object is a method object, then the component parts are returned in this structure. :return: a non-zero value if the object is a Python method object. .. c:function:: void *sipGetMixinAddress(sipSimpleWrapper *obj, const sipTypeDef *td) This returns the address of the C++ class instance that implements the mixin of a wrapped Python object. :param obj: the Python object. :param td: the :ref:`generated type structure ` corresponding to the C++ type of the mixin. :return: the address of the C++ instance .. c:function:: PyObject *sipGetPyObject(void *cppptr, const sipTypeDef *td) This returns a borrowed reference to the Python object for a C structure or C++ class instance. :param cppptr: the pointer to the C/C++ instance. :param td: the :ref:`generated type structure ` corresponding to the C/C++ type. :return: the Python object or ``NULL`` (and no exception is raised) if the C/C++ instance hasn't been wrapped. .. c:function:: int sipGetState(PyObject *transferObj) The :directive:`%ConvertToTypeCode` directive requires that the provided code returns an ``int`` describing the state of the converted value. The state usually depends on any transfers of ownership that have been requested. This is a convenience function that returns the correct state when the converted value is a temporary. :param transferObj: the object that describes the requested transfer of ownership. :return: the state of the converted value. .. c:function:: int sipGetTime(PyObject *obj, sipTimeDef *time) This checks to see if an object is a Python time object and, if so, optionally returns its component parts. :param obj: the Python object. :param time: if this is not ``NULL``, and the object is a time object, then the component parts are returned in this structure. :return: a non-zero value if the object is a Python time object. .. c:function:: void *sipGetTypeUserData(const sipWrapperType *type) Each generated type corresponding to a wrapped C/C++ type, or a user sub-class of such a type, contains a pointer for the use of handwritten code. This returns the value of that pointer. :param type: the type object. :return: the type-specific pointer. .. c:function:: PyObject *sipGetUserObject(const sipSimpleWrapper *obj) Each wrapped object can contain a reference to a single Python object that can be used for any purpose by handwritten code and will automatically be garbage collected at the appropriate time. This returns that object. :param obj: the wrapped object. :return: the user object. .. c:function:: void *sipImportSymbol(const char *name) Python does not allow extension modules to directly access symbols in another extension module. This imports a symbol, referenced by a name, that has previously been exported, using :c:func:`sipExportSymbol()`, by another module. :param name: the name of the symbol. :return: the value of the symbol. ``NULL`` is returned if there is no such symbol. .. c:function:: void sipInstanceDestroyed(sipSimpleWrapper *obj) This should be called by handwritten code if it is able to detect that a wrapped C++ instance has been destroyed from C++. It should not be called if SIP is able to detect this itself, i.e. when the instance was created from Python and the class has a virtual destructor. :param obj: the Python object that wraps the destroyed instance. .. c:function:: int sipIsAPIEnabled(const char *name, int from, int to) .. deprecated:: 12.0 This will be removed in v13. This checks to see if the current version number of an API falls within a given range. :param name: the name of the API. :param from: the lower bound of the range. For the API to be enabled its version number must be greater than or equal to *from*. If *from* is 0 then this check isn't made. :param to: the upper bound of the range. For the API to be enabled its version number must be less than *to*. If *to* is 0 then this check isn't made. :return: a non-zero value if the API is enabled. .. c:function:: int sipIsOwnedByPython(sipSimpleWrapper *obj) This determines if a wrapped object is currently owned by Python. :param obj: the wrapped object. :return: a non-zero value if the object is currently owned by Python. .. c:function:: int sipIsUserType(const sipWrapperType *type) This checks if a type corresponds to a wrapped C/C++ type or a user sub-class of such a type. :param type: the type object. :return: a non-zero value if the type is a user defined type. .. c:function:: char sipLong_AsChar(PyObject *obj) This converts a Python object to a C/C++ char. If the value is too large then an exception is raised if overflow checking is enabled. :param obj: the Python object. :return: the converted C/C++ value. .. c:function:: signed char sipLong_AsSignedChar(PyObject *obj) This converts a Python object to a C/C++ signed char. If the value is too large then an exception is raised if overflow checking is enabled. :param obj: the Python object. :return: the converted C/C++ value. .. c:function:: unsigned char sipLong_AsUnsignedChar(PyObject *obj) This converts a Python object to a C/C++ unsigned char. If the value is too large then an exception is raised if overflow checking is enabled. :param obj: the Python object. :return: the converted C/C++ value. .. c:function:: short sipLong_AsShort(PyObject *obj) This converts a Python object to a C/C++ short. If the value is too large then an exception is raised if overflow checking is enabled. :param obj: the Python object. :return: the converted C/C++ value. .. c:function:: unsigned short sipLong_AsUnsignedShort(PyObject *obj) This converts a Python object to a C/C++ unsigned short. If the value is too large then an exception is raised if overflow checking is enabled. :param obj: the Python object. :return: the converted C/C++ value. .. c:function:: int sipLong_AsInt(PyObject *obj) This converts a Python object to a C/C++ int. If the value is too large then an exception is raised if overflow checking is enabled. :param obj: the Python object. :return: the converted C/C++ value. .. c:function:: unsigned int sipLong_AsUnsignedInt(PyObject *obj) This converts a Python object to a C/C++ unsigned int. If the value is too large then an exception is raised if overflow checking is enabled. :param obj: the Python object. :return: the converted C/C++ value. .. c:function:: size_t sipLong_AsSizeT(PyObject *obj) This converts a Python object to a C/C++ size_t. If the value is too large then an exception is raised if overflow checking is enabled. :param obj: the Python object. :return: the converted C/C++ value. .. c:function:: long sipLong_AsLong(PyObject *obj) This converts a Python object to a C/C++ long. If the value is too large then an exception is raised if overflow checking is enabled. :param obj: the Python object. :return: the converted C/C++ value. .. c:function:: unsigned long sipLong_AsUnsignedLong(PyObject *obj) This converts a Python object to a C/C++ unsigned long. If the value is too large then an exception is raised if overflow checking is enabled. :param obj: the Python object. :return: the converted C/C++ value. .. c:function:: long long sipLong_AsLongLong(PyObject *obj) This converts a Python object to a C/C++ long long. If the value is too large then an exception is raised if overflow checking is enabled. :param obj: the Python object. :return: the converted C/C++ value. .. c:function:: unsigned long long sipLong_AsUnsignedLongLong(PyObject *obj) This converts a Python object to a C/C++ unsigned long long. If the value is too large then an exception is raised if overflow checking is enabled. :param obj: the Python object. :return: the converted C/C++ value. .. c:function:: void *sipMalloc(size_t nbytes) This allocates an area of memory on the heap using the Python :c:func:`PyMem_RawMalloc()` function. The memory is freed by calling :c:func:`sipFree()`. :param nbytes: the number of bytes to allocate. :return: the memory address. If there was an error then ``NULL`` is returned and a Python exception raised. .. c:type:: sipMethodDef This C structure is used with :c:func:`sipGetMethod()` and :c:func:`sipFromMethod()` and encapsulates the components parts of a Python method. The structure elements are as follows. .. c:member:: PyObject *pm_function The function that implements the method. .. c:member:: PyObject *pm_self The bound object. .. c:function:: int sipParseResult(int *iserr, PyObject *method, PyObject *result, const char *format, ...) This converts a Python object (usually returned by a method) to C/C++ based on a format string and associated values in a similar way to the Python :c:func:`PyArg_ParseTuple()` function. :param iserr: if this is not ``NULL`` then the location it points to is set to a non-zero value if there was an error. :param method: the Python method that returned *result*. :param result: the Python object returned by *method*. :param format: the format string. :return: 0 if there was no error. Otherwise a negative value is returned, and an exception raised. This is normally called by handwritten code specified with the :directive:`%VirtualCatcherCode` directive with *method* being the supplied ``sipMethod`` and *result* being the value returned by :c:func:`sipCallMethod()`. If *format* begins and ends with parentheses then *result* must be a Python tuple and the rest of *format* is applied to the tuple contents. In the following description the first letter is the format character, the entry in parentheses is the Python object type that the format character will convert, and the entry in brackets are the types of the C/C++ values to be passed. ``ae`` (object) [char \*] Convert a Python string-like object of length 1 to a C/C++ ``char`` according to the encoding ``e``. ``e`` can either be ``A`` for ASCII, ``L`` for Latin-1, or ``8`` for UTF-8. The object may either be a ``bytes`` object or a ``str`` object that can be encoded. An object that supports the buffer protocol may also be used. ``b`` (integer) [bool \*] Convert a Python integer to a C/C++ ``bool``. ``c`` (bytes) [char \*] Convert a Python ``bytes`` object of length 1 to a C/C++ ``char``. ``d`` (float) [double \*] Convert a Python floating point number to a C/C++ ``double``. ``e`` (integer) [enum \*] Convert a Python integer to an anonymous C/C++ ``enum``. ``f`` (float) [float \*] Convert a Python floating point number to a C/C++ ``float``. ``g`` (bytes) [const char \*\*, :c:macro:`Py_ssize_t` \*] Convert a Python ``bytes`` object to a C/C++ character array and its length. If the Python object is ``Py_None`` then the array and length are ``NULL`` and zero respectively. ``h`` (integer) [short \*] Convert a Python integer to a C/C++ ``short``. ``i`` (integer) [int \*] Convert a Python integer to a C/C++ ``int``. ``l`` (long) [long \*] Convert a Python long to a C/C++ ``long``. ``m`` (long) [unsigned long \*] Convert a Python long to a C/C++ ``unsigned long``. ``n`` (long) [long long \*] Convert a Python long to a C/C++ ``long long``. ``o`` (long) [unsigned long long \*] Convert a Python long to a C/C++ ``unsigned long long``. ``t`` (long) [unsigned short \*] Convert a Python long to a C/C++ ``unsigned short``. ``u`` (long) [unsigned int \*] Convert a Python long to a C/C++ ``unsigned int``. ``w`` (string) [wchar_t \*] Convert a Python ``str`` object of length 1 to a C/C++ wide character. ``x`` (string) [wchar_t \*\*] Convert a Python ``str`` object to a C/C++ ``L'\0'`` terminated wide character string. If the Python object is ``Py_None`` then the string is ``NULL``. ``Ae`` (object) [int, const char \*\*] Convert a Python string-like object to a C/C++ ``'\0'`` terminated string according to the encoding ``e``. ``e`` can either be ``A`` for ASCII, ``L`` for Latin-1, or ``8`` for UTF-8. If the Python object is ``Py_None`` then the string is ``NULL``. The integer uniquely identifies the object in the context defined by the ``S`` format character and allows an extra reference to the object to be kept to ensure that the string remains valid. The object may either be a ``bytes`` object or a ``str`` object that can be encoded. An object that supports the buffer protocol may also be used. ``B`` (bytes) [int, const char \*\*] Convert a Python ``bytes`` object to a C/C++ ``'\0'`` terminated string. If the Python object is ``Py_None`` then the string is ``NULL``. The integer uniquely identifies the object in the context defined by the ``S`` format character and allows an extra reference to the object to be kept to ensure that the string remains valid. ``F`` (wrapped enum) [:c:type:`sipTypeDef` \*, enum \*] Convert a Python named enum type to the corresponding C/C++ ``enum``. ``G`` (string) [wchar_t \*\*, :c:macro:`Py_ssize_t` \*] Convert a Python ``str`` object to a C/C++ wide character array and its length. If the Python object is ``Py_None`` then the array and length are ``NULL`` and zero respectively. ``Hf`` (wrapped instance) [const :c:type:`sipTypeDef` \*, int \*, void \*\*] Convert a Python object to a C structure, C++ class or mapped type instance as described in :c:func:`sipConvertToType()`. ``f`` is a combination of the following flags encoded as an ASCII character by adding ``0`` to the combined value: 0x01 disallows the conversion of ``Py_None`` to ``NULL`` 0x02 implements the :fanno:`Factory` and :fanno:`TransferBack` annotations 0x04 returns a copy of the C/C++ instance. ``L`` (integer) [signed char \*] Convert a Python integer to a C/C++ ``signed char``. ``M`` (long) [unsigned char \*] Convert a Python long to a C/C++ ``unsigned char``. ``N`` (object) [PyTypeObject \*, PyObject \*\*] A Python object is checked to see if it is a certain type and then returned without any conversions. The reference count is incremented. The Python object may be ``Py_None``. ``O`` (object) [PyObject \*\*] A Python object is returned without any conversions. The reference count is incremented. ``S`` [:c:type:`sipSimpleWrapper` \*] This format character, if used, must be the first. It is used with other format characters to define a context and doesn't itself convert an argument. ``T`` (object) [PyTypeObject \*, PyObject \*\*] A Python object is checked to see if it is a certain type and then returned without any conversions. The reference count is incremented. The Python object may not be ``Py_None``. ``V`` (:class:`sip.voidptr`) [void \*\*] Convert a Python :class:`sip.voidptr` object to a C/C++ ``void *``. ``z`` (object) [const char \*, void \*\*] Convert a Python named capsule object to a C/C++ ``void *``. ``Z`` (object) [] Check that a Python object is ``Py_None``. No value is returned. ``!`` (object) [PyObject \*\*] A Python object is checked to see if it implements the buffer protocol and then returned without any conversions. The reference count is incremented. The Python object may not be ``Py_None``. ``$`` (object) [PyObject \*\*] A Python object is checked to see if it implements the buffer protocol and then returned without any conversions. The reference count is incremented. The Python object may be ``Py_None``. ``=`` (long) [size_t \*] Convert a Python long to a C/C++ ``size_t``. .. c:function:: PyObject *sipPyTypeDict(const PyTypeObject *py_type) This provides access to a Python type object's ``tp_dict`` field and is typically used when the limited Python API is enabled. .. note:: This is deprecated in ABI v12.13 and must not be used with Python v3.12 and later. :param py_type: the type object. :return: a borrowed reference to the type object's ``tp_dict`` field. .. c:function:: PyObject *sipPyTypeDictRef(PyTypeObject *py_type) This provides access to a Python type object's type dictionary and is typically used when the limited Python API is enabled. :param py_type: the type object. :return: a new reference to type object's type dictionary. .. c:function:: void sipPrintObject(PyObject *obj) This is a thin wrapper around :c:func:`PyObject_Print()` that is typically used when debugging when the limited Python API is enabled. :param obj: the Python object. .. c:function:: const char *sipPyTypeName(const PyTypeObject *py_type) This provides access to a Python type object's ``tp_name`` field and is typically used when the limited Python API is enabled. :param py_type: the type object. :return: the value of the type object's ``tp_name`` field. .. c:function:: int sipRegisterAttributeGetter(const sipTypeDef *td, sipAttrGetterFunc getter) This registers a getter that will be called just before SIP needs to get an attribute from a wrapped type's dictionary for the first time. The getter must then populate the type's dictionary with any lazy attributes. :param td: the optional :ref:`generated type structure ` that determines which types the getter will be called for. :param getter: the getter function. :return: 0 if there was no error, otherwise -1 is returned. If *td* is not ``NULL`` then the getter will only be called for types with that type or that are sub-classed from it. Otherwise the getter will be called for all types. A getter has the following signature. int getter(const :c:type:`sipTypeDef` \*td, PyObject \*dict) *td* is the generated type definition of the type whose dictionary is to be populated. *dict* is the dictionary to be populated. 0 is returned if there was no error, otherwise -1 is returned. See the section :ref:`ref-lazy-type-attributes` for more details. .. c:function:: int sipRegisterEventHandler(sipEventType type, const sipTypeDef *td, void *handler) This registers an event handler which will be called whenever an event is triggered. :param type: the event type for which the handler is registered. :param td: the generated type structure - the handler will only be invoked for Python object corresponding to this type or a sub-type. :param handler: the handler that is called when the event is triggered. :return: 0 if there was no error, otherwise -1 is returned (and a Python exception is raised). .. c:function:: int sipRegisterExitNotifier(PyMethodDef *md) This registers a C function with Python's :py:mod:`atexit` module that will be called when the interpreter terminates. :param md: the data structure that describes the C function to be called. :return: 0 if there was no error, otherwise -1 is returned. .. c:function:: int sipRegisterProxyResolver(const sipTypeDef *td, sipProxyResolverFunc resolver) This registers a resolver that will be called just before SIP wraps a C/C++ pointer in a Python object. The resolver may choose to replace the C/C++ pointer with the address of another object. Typically this is used to replace a proxy by the object that is being proxied for. :param td: the optional :ref:`generated type structure ` that determines which type the resolver will be called for. :param resolver: the resolver function. :return: 0 if there was no error, otherwise -1 is returned. A resolver has the following signature. void \*resolver(void \*proxy) *proxy* is C/C++ pointer that is being wrapped. The C/C++ pointer that will actually be wrapped is returned. .. c:function:: int sipRegisterPyType(PyTypeObject *type) This registers a Python type object that can be used as the meta-type or super-type of a wrapped C++ type. :param type: the type object. :return: 0 if there was no error, otherwise -1 is returned. See the section :ref:`ref-types-metatypes` for more details. .. c:function:: void sipReleaseBufferInfo(sipBufferInfoDef *buffer_info) This releases the buffer information related to a Python object that implements the buffer protocol that was created with a corresponding call to :c:func:`sipGetBufferInfo`. It is similar to :c:func:`PyBuffer_Release` and should be used instead of that when the limited Python API is enabled. :param buffer_info: the buffer information to release. .. c:function:: void sipReleaseType(void *cpp, const sipTypeDef *td, int state) This destroys a wrapped C/C++ or mapped type instance if it was a temporary instance. It is called after a call to either :c:func:`sipConvertToType()` or :c:func:`sipForceConvertToType()`. :param cpp: the C/C++ instance. :param td: the type's :ref:`generated type structure `. :param state: describes the state of the C/C++ instance. .. c:function:: const char *sipResolveTypedef(const char *name) This returns the value of a C/C++ typedef. :param name: the name of the typedef. :return: the value of the typedef or ``NULL`` if there was no such typedef. .. c:function:: void sipSetDestroyOnExit(int destroy) When the Python interpreter exits it garbage collects those objects that it can. This means that any corresponding C++ instances and C structures owned by Python are destroyed. Unfortunately this happens in an unpredictable order and so can cause memory faults within the wrapped library. Calling this function with a value of zero disables the automatic destruction of C++ instances and C structures. :param destroy: non-zero if all C++ instances and C structures owned by Python should be destroyed when the interpreter exits. This is the default. .. c:function:: sipNewUserTypeFunc sipSetNewUserTypeHandler(const sipTypeDef *td, sipNewUserTypeFunc handler) The allows a function to be specified that is called whenever a user defined sub-class of a C/C++ type is created (i.e. one implemented in Python). It is normalled called from a module's :directive:`%PostInitialisationCode`. It is provided as an alternative to providing a meta-type when the limited Python API is enabled. :param td: the :ref:`generated type structure ` corresponding to the C/C++ type. :param handler: the function that is called whenever a user defined sub-class of the type is created. The function takes a single argument which is the :c:type:`sipWrapperType` of the user defined class. It returns an ``int`` which is 0 if there was no error. A Python exception is raised and -1 returned if there was an error. :return: the previously installed handler. This allows handlers to be chained. .. c:function:: void sipSetTypeUserData(sipWrapperType *type, void *data) Each generated type corresponding to a wrapped C/C++ type, or a user sub-class of such a type, contains a pointer for the use of handwritten code. This sets the value of that pointer. :param type: the type object. :param data: the type-specific pointer. .. c:function:: void sipSetUserObject(sipSimpleWrapper *obj, PyObject *user) Each wrapped object can contain a reference to a single Python object that can be used for any purpose by handwritten code and will automatically be garbage collected at the appropriate time. This sets that object. :param obj: the wrapped object. :param user: a borrowed reference to the user object. .. c:type:: sipSimpleWrapper This is a C structure that represents a Python wrapped instance whose type is :class:`sip.simplewrapper`. It is an extension of the ``PyObject`` structure and so may be safely cast to it. When the limited Python API is enabled then it is only available as an opaque (i.e. incomplete) type and the following members are not available. .. c:member:: void *data This is initialised to the address of the C/C++ instance. If an access function is subsequently provided then it may be used for any purpose by the access function. .. c:member:: sipAccessFunc access_func This is the address of an optional access function that is called, with a pointer to this structure as its first argument. If its second argument is ``UnguardedPointer`` then it returns the address of the C/C++ instance, even if it is known that its value is no longer valid. If the second argument is ``GuardedPointer`` then it returns the address of the C++ instance or ``0`` if it is known to be invalid. If the second argument is ``ReleaseGuard`` then the structure is being deallocated and any dynamic resources used by the access function should be released. If there is no access function then the :c:member:`sipSimpleWrapper.data` is used as the address of the C/C++ instance. Typically a custom meta-type is used to set an access method after the Python object has been created. .. c:member:: PyObject *user This can be used for any purpose by handwritten code and will automatically be garbage collected at the appropriate time. .. c:var:: PyTypeObject *sipSimpleWrapper_Type This is the type of a :c:type:`sipSimpleWrapper` structure and is the C implementation of :class:`sip.simplewrapper`. It may be safely cast to :c:type:`sipWrapperType`. When the limited Python API is enabled then it is only available as an opaque (i.e. incomplete) type. .. c:type:: sipTimeDef This C structure is used with :c:func:`sipGetTime()`, :c:func:`sipFromTime()`, :c:func:`sipGetDateTime()` and :c:func:`sipFromDateTime()` and encapsulates the components parts of a Python time. The structure elements are as follows. .. c:member:: int pt_hour The hour (0-23). .. c:member:: int pt_minute The minute (0-59). .. c:member:: int pt_second The second (0-59). .. c:member:: int pt_microsecond The microsecond (0-999999). .. c:function:: void sipTransferBack(PyObject *obj) This transfers ownership of a Python wrapped instance to Python (see :ref:`ref-object-ownership`). :param obj: the wrapped instance. In addition, any association of the instance with regard to the cyclic garbage collector with another instance is removed. .. c:function:: void sipTransferTo(PyObject *obj, PyObject *owner) This transfers ownership of a Python wrapped instance to C++ (see :ref:`ref-object-ownership`). :param obj: the wrapped instance. :param owner: an optional wrapped instance that *obj* becomes associated with with regard to the cyclic garbage collector. If *owner* is ``NULL`` then no such association is made. If *owner* is ``Py_None`` then *obj* is given an extra reference which is removed when the C++ instance's destructor is called. If *owner* is the same value as *obj* then any reference cycles involving *obj* can never be detected or broken by the cyclic garbage collector. Responsibility for calling the C++ instance's destructor is always transfered to C++. .. c:function:: PyTypeObject *sipTypeAsPyTypeObject(const sipTypeDef *td) This returns a pointer to the Python type object that SIP creates for a :ref:`generated type structure `. :param td: the type structure. :return: the Python type object. If the type structure refers to a mapped type then ``NULL`` will be returned. If the type structure refers to a C structure or C++ class then the Python type object may be safely cast to a :c:type:`sipWrapperType`. .. c:function:: const sipTypeDef *sipTypeFromPyTypeObject(PyTypeObject *py_type) This returns the :ref:`generated type structure ` for a Python type object. :param py_type: the Python type object. :return: the type structure or ``NULL`` if the Python type object doesn't correspond to a type structure. .. c:function:: int sipTypeIsClass(sipTypeDef *td) This checks if a :ref:`generated type structure ` refers to a C structure or C++ class. :param td: the type structure. :return: a non-zero value if the type structure refers to a structure or class. .. c:function:: int sipTypeIsEnum(sipTypeDef *td) This checks if a :ref:`generated type structure ` refers to a C-style named enum. :param td: the type structure. :return: a non-zero value if the type structure refers to a C-style named enum. .. c:function:: int sipTypeIsMapped(sipTypeDef *td) This checks if a :ref:`generated type structure ` refers to a mapped type. :param td: the type structure. :return: a non-zero value if the type structure refers to a mapped type. .. c:function:: int sipTypeIsNamespace(sipTypeDef *td) This checks if a :ref:`generated type structure ` refers to a C++ namespace. :param td: the type structure. :return: a non-zero value if the type structure refers to a namespace. .. c:function:: int sipTypeIsScopedEnum(sipTypeDef *td) This checks if a :ref:`generated type structure ` refers to a C++11 scoped enum. :param td: the type structure. :return: a non-zero value if the type structure refers to a C++11 scoped enum. .. c:function:: const char *sipTypeName(const sipTypeDef *td) This returns the C/C++ name of a wrapped type. :param td: the type's :ref:`generated type structure `. :return: the name of the C/C++ type. .. c:function:: const sipTypeDef *sipTypeScope(const sipTypeDef *td) This returns the :ref:`generated type structure ` of the enclosing scope of another generated type structure. :param td: the type structure. :return: the type structure of the scope or ``NULL`` if the type has no scope. .. c:function:: void *sipUnicodeData(PyObject *obj, int *char_size, Py_ssize_t *len) This returns information about the contents of a Python unicode object. :param obj: the unicode object. :param char_size: a pointer which will be updated with the number of bytes (either 1, 2 or 4) used to store a character. If there was an error then this will be a negative value. :param len: a pointer which will be updated with the number of characters (not bytes) in the unicode object. :return: the address of the buffer where the characters are stored. It will be undefined if the returned character size is a negative value. .. c:function:: PyObject *sipUnicodeNew(Py_ssize_t len, unsigned maxchar, int *kind, void **data) This creates a Python unicode object that will hold a set number of characters, each character being of a certain size. :param len: the number of characters. :param maxchar: the largest code point that will be placed in the object. :param kind: a pointer which will be updated with a value that represents the number of bytes (either 1, 2 or 4) used to store a character. :param data: a pointer which will be updated with the address of the buffer where the characters will be stored. :return: the unicode object or ``NULL`` if there was an error. .. c:function:: void sipUnicodeWrite(int kind, void *data, int index, unsigned value) This updates the buffer of a Python unicode object with a character at a particular position. :param kind: the value that represents the number of bytes (either 1, 2 or 4) used to store a character. :param data: the address of the buffer where the characters are stored. :param index: the character (not byte) index of the character to be updated. :param value: the value of the new character. .. c:function:: void sipVisitWrappers(sipWrapperVisitorFunc visitor, void *closure) This calls a visitor function for every wrapper object. :param visitor: the visitor function. :param closure: a pointer that is passed to the visitor. A visitor has the following signature. void visitor(sipSimpleWrapper \*obj, void \*closure) *obj* is the wrapped object being visited. *closure* is the pointer passed to :c:func:`sipVisitWrappers()`. .. c:var:: PyTypeObject *sipVoidPtr_Type This is the type of a ``PyObject`` structure that is used to wrap a ``void *``. .. c:type:: sipWrapper This is a C structure that represents a Python wrapped instance whose type is :class:`sip.wrapper`. It is an extension of the :c:type:`sipSimpleWrapper` and ``PyObject`` structures and so may be safely cast to both. When the limited Python API is enabled then it is only available as an opaque (i.e. incomplete) type. .. c:var:: PyTypeObject *sipWrapper_Type This is the type of a :c:type:`sipWrapper` structure and is the C implementation of :class:`sip.wrapper`. It may be safely cast to :c:type:`sipWrapperType`. .. c:type:: sipWrapperType This is a C structure that represents a SIP generated type object. It is an extension of the ``PyTypeObject`` structure (which is itself an extension of the ``PyObject`` structure) and so may be safely cast to ``PyTypeObject`` (and ``PyObject``). When the limited Python API is enabled then it is only available as an opaque (i.e. incomplete) type. .. c:var:: PyTypeObject *sipWrapperType_Type This is the type of a :c:type:`sipWrapperType` structure and is the C implementation of :class:`sip.wrappertype`. sip-6.8.6/docs/abi_13.rst000066400000000000000000002113471464421045000150560ustar00rootroot00000000000000ABI v13 for Handwritten Code ============================ In this section we describe the v13 of the ABI, provided by the :mod:`sip` module, that can be used by handwritten code in specification files. .. c:macro:: SIP_API_MAJOR_NR This is a C preprocessor symbol that defines the major number of the SIP API. Its value is a number. There is no direct relationship between this and the SIP version number. .. c:macro:: SIP_API_MINOR_NR This is a C preprocessor symbol that defines the minor number of the SIP API. Its value is a number. There is no direct relationship between this and the SIP version number. .. c:macro:: SIP_BLOCK_THREADS This is a C preprocessor macro that will make sure the Python Global Interpreter Lock (GIL) is acquired. Python API calls must only be made when the GIL has been acquired. There must be a corresponding :c:macro:`SIP_UNBLOCK_THREADS` at the same lexical scope. .. c:macro:: SIP_NO_CONVERTORS This is a flag used by various type convertors that suppresses the use of a type's :directive:`%ConvertToTypeCode`. .. c:macro:: SIP_NOT_NONE This is a flag used by various type convertors that causes the conversion to fail if the Python object being converted is ``Py_None``. .. c:macro:: SIP_NULLPTR This is a C preprocessor macro that should be used instead of ``NULL`` or ``nullptr``. It ensures the correct value is used depending on whether C or C++ is being generated and which language standard the compiler supports. .. c:macro:: SIP_OWNS_MEMORY This is a flag used by various array constructors that species that the array owns the memory that holds the array's contents. .. c:macro:: SIP_PROTECTED_IS_PUBLIC This is a C preprocessor symbol that is defined automatically by the build system to specify that the generated code is being compiled with ``protected`` redefined as ``public``. This allows handwritten code to determine if the generated helper functions for accessing protected C++ functions are available (see :directive:`%MethodCode`). .. c:macro:: SIP_READ_ONLY This is a flag used by various array constructors that species that the array is read-only. .. c:function:: void SIP_RELEASE_GIL(sip_gilstate_t sipGILState) This is called from the handwritten code specified with the :directive:`VirtualErrorHandler` in order to release the Python Global Interpreter Lock (GIL) prior to changing the execution path (e.g. by throwing a C++ exception). It should not be called under any other circumstances. :param sipGILState: an opaque value provided to the handwritten code by SIP. .. c:macro:: SIP_UNBLOCK_THREADS This is a C preprocessor macro that will restore the Python Global Interpreter Lock (GIL) to the state it was prior to the corresponding :c:macro:`SIP_BLOCK_THREADS`. .. c:macro:: SIP_VERSION This is a C preprocessor symbol that defines the SIP version number represented as a 3 part hexadecimal number (e.g. v5.0.0 is represented as ``0x050000``). .. c:macro:: SIP_VERSION_STR This is a C preprocessor symbol that defines the SIP version number represented as a string. For development versions it will contain ``.dev``. .. c:function:: sipErrorState sipBadCallableArg(int arg_nr, PyObject *arg) This is called from :directive:`%MethodCode` to raise a Python exception when an argument to a function, a C++ constructor or method is found to have an unexpected type. This should be used when the :directive:`%MethodCode` does additional type checking of the supplied arguments. :param arg_nr: the number of the argument. Arguments are numbered from 0 but are numbered from 1 in the detail of the exception. :param arg: the argument. :return: the value that should be assigned to ``sipError``. .. c:function:: void sipBadCatcherResult(PyObject *method) This raises a Python exception when the result of a Python reimplementation of a C++ method doesn't have the expected type. It is normally called by handwritten code specified with the :directive:`%VirtualCatcherCode` directive. :param method: the Python method and would normally be the supplied ``sipMethod``. .. c:function:: void sipBadLengthForSlice(Py_ssize_t seqlen, Py_ssize_t slicelen) This raises a Python exception when the length of a slice object is inappropriate for a sequence-like object. It is normally called by handwritten code specified for :meth:`__setitem__` methods. :param seqlen: the length of the sequence. :param slicelen: the length of the slice. .. c:type:: sipBufferInfoDef This C structure is used with :c:func:`sipGetBufferInfo()` and :c:func:`sipReleaseBufferInfo()` and encapsulates information provided by a Python object that implements the buffer protocol. The structure elements are as follows. .. c:member:: void *bi_buf The address of the buffer. .. c:member:: PyObject *bi_obj A reference to the object that implements the buffer protocol. .. c:member:: Py_ssize_t bi_len The length of the buffer in bytes. .. c:member:: int bi_readonly Non-zero if the buffer is read-only. .. c:member:: char *bi_format The format of each element of the buffer. .. c:function:: PyObject *sipBuildResult(int *iserr, const char *format, ...) This creates a Python object based on a format string and associated values in a similar way to the Python :c:func:`Py_BuildValue()` function. :param iserr: if this is not ``NULL`` then the location it points to is set to a non-zero value. :param format: the string of format characters. :return: If there was an error then ``NULL`` is returned and a Python exception is raised. If the format string begins and ends with parentheses then a tuple of objects is created. If it contains more than one format character then parentheses must be specified. In the following description the first letter is the format character, the entry in parentheses is the Python object type that the format character will create, and the entry in brackets are the types of the C/C++ values to be passed. ``a`` (string) [char] Convert a C/C++ ``char`` to a Python ``str`` object. ``b`` (boolean) [int] Convert a C/C++ ``int`` to a Python boolean. ``c`` (string/bytes) [char] Convert a C/C++ ``char`` to a Python ``bytes`` object. ``d`` (float) [double] Convert a C/C++ ``double`` to a Python floating point number. ``e`` (integer) [enum] Convert an anonymous C/C++ ``enum`` to a Python integer. ``f`` (float) [float] Convert a C/C++ ``float`` to a Python floating point number. ``g`` (string/bytes) [char \*, :c:macro:`Py_ssize_t`] Convert a C/C++ character array and its length to a Python ``bytes`` object. If the array is ``NULL`` then the length is ignored and the result is ``Py_None``. ``h`` (integer) [short] Convert a C/C++ ``short`` to a Python integer. ``i`` (integer) [int] Convert a C/C++ ``int`` to a Python integer. ``l`` (long) [long] Convert a C/C++ ``long`` to a Python integer. ``m`` (long) [unsigned long] Convert a C/C++ ``unsigned long`` to a Python long. ``n`` (long) [long long] Convert a C/C++ ``long long`` to a Python long. ``o`` (long) [unsigned long long] Convert a C/C++ ``unsigned long long`` to a Python long. ``r`` (wrapped instance) [*type* \*, :c:macro:`Py_ssize_t`, const :c:type:`sipTypeDef` \*] Convert an array of C structures, C++ classes or mapped type instances to a Python tuple. Note that copies of the array elements are made. ``s`` (string/bytes) [char \*] Convert a C/C++ ``'\0'`` terminated string to a Python ``bytes`` object. If the string pointer is ``NULL`` then the result is ``Py_None``. ``t`` (long) [unsigned short] Convert a C/C++ ``unsigned short`` to a Python long. ``u`` (long) [unsigned int] Convert a C/C++ ``unsigned int`` to a Python long. ``w`` (unicode/string) [wchar_t] Convert a C/C++ wide character to a Python ``str`` object. ``x`` (unicode/string) [wchar_t \*] Convert a C/C++ ``L'\0'`` terminated wide character string to a Python ``str`` object. If the string pointer is ``NULL`` then the result is ``Py_None``. ``A`` (string) [char \*] Convert a C/C++ ``'\0'`` terminated string to a Python ``str`` object. If the string pointer is ``NULL`` then the result is ``Py_None``. ``D`` (wrapped instance) [*type* \*, const :c:type:`sipTypeDef` \*, PyObject \*] Convert a C structure, C++ class or mapped type instance to a Python object. If the instance has already been wrapped then the result is a new reference to the existing object. Ownership of the instance is determined by the ``PyObject *`` argument. If it is ``NULL`` and the instance has already been wrapped then the ownership is unchanged. If it is ``NULL`` and the instance is newly wrapped then ownership will be with C/C++. If it is ``Py_None`` then ownership is transferred to Python via a call to :c:func:`sipTransferBack()`. Otherwise ownership is transferred to C/C++ and the instance associated with the ``PyObject *`` argument via a call to :c:func:`sipTransferTo()`. The Python class is influenced by any applicable :directive:`%ConvertToSubClassCode` code. ``F`` (wrapped enum) [enum, :c:type:`sipTypeDef` \*] Convert a named C/C++ ``enum`` to an instance of the corresponding Python named enum type. ``G`` (unicode) [wchar_t \*, :c:macro:`Py_ssize_t`] Convert a C/C++ wide character array and its length to a Python unicode object. If the array is ``NULL`` then the length is ignored and the result is ``Py_None``. ``L`` (integer) [char] Convert a C/C++ ``char`` to a Python integer. ``M`` (long) [unsigned char] Convert a C/C++ ``unsigned char`` to a Python long. ``N`` (wrapped instance) [*type* \*, :c:type:`sipTypeDef` \*, PyObject \*] Convert a new C structure, C++ class or mapped type instance to a Python object. Ownership of the instance is determined by the ``PyObject *`` argument. If it is ``NULL`` and the instance has already been wrapped then the ownership is unchanged. If it is ``NULL`` or ``Py_None`` then ownership will be with Python. Otherwise ownership will be with C/C++ and the instance associated with the ``PyObject *`` argument. The Python class is influenced by any applicable :directive:`%ConvertToSubClassCode` code. ``R`` (object) [PyObject \*] The result is value passed without any conversions. The reference count is unaffected, i.e. a reference is taken. ``S`` (object) [PyObject \*] The result is value passed without any conversions. The reference count is incremented. ``V`` (sip.voidptr) [void \*] Convert a C/C++ ``void *`` to a Python :class:`sip.voidptr` object. ``z`` (object) [const char \*, void \*] Convert a C/C++ ``void *`` to a Python named capsule object. ``=`` (long) [size_t] Convert a C/C++ ``size_t`` to a Python long. .. c:function:: PyObject *sipCallMethod(int *iserr, PyObject *method, const char *format, ...) This calls a Python method passing a tuple of arguments based on a format string and associated values in a similar way to the Python :c:func:`PyObject_CallObject()` function. :param iserr: if this is not ``NULL`` then the location it points to is set to a non-zero value if there was an error. :param method: the Python bound method to call. :param format: the string of format characters (see :c:func:`sipBuildResult()`). :return: If there was an error then ``NULL`` is returned and a Python exception is raised. It is normally called by handwritten code specified with the :directive:`%VirtualCatcherCode` directive with method being the supplied ``sipMethod``. .. c:function:: int sipCanConvertToType(PyObject *obj, const sipTypeDef *td, int flags) This checks if a Python object can be converted to an instance of a C structure, C++ class or mapped type. :param obj: the Python object. :param td: the C/C++ type's :ref:`generated type structure `. :param flags: any combination of the :c:macro:`SIP_NOT_NONE` and :c:macro:`SIP_NO_CONVERTORS` flags. :return: a non-zero value if the object can be converted. .. c:type:: sipCFunctionDef This C structure is used with :c:func:`sipGetCFunction()` and encapsulates the components parts of a Python C function. The structure elements are as follows. .. c:member:: PyMethodDef *cf_function The C function. .. c:member:: PyObject *cf_self The optional bound object. .. c:function:: PyObject *sipConvertFromConstVoidPtr(const void *cpp) This creates a :class:`sip.voidptr` object for a memory address. The object will not be writeable and has no associated size. :param cpp: the memory address. :return: the :class:`sip.voidptr` object. .. c:function:: PyObject *sipConvertFromConstVoidPtrAndSize(const void *cpp, Py_ssize_t size) This creates a :class:`sip.voidptr` object for a memory address. The object will not be writeable and can be used as an immutable buffer object. :param cpp: the memory address. :param size: the size associated with the address. :return: the :class:`sip.voidptr` object. .. c:function:: PyObject *sipConvertFromEnum(int eval, const sipTypeDef *td) This converts a named C/C++ ``enum`` to a Python object. If the enum is a C++11 scoped enum then the Python object is created using the :py:mod:`enum` module. Otherwise a SIP generated type is used that can itself be converted to an ``int``. :param eval: the enumerated value to convert. :param td: the enum's :ref:`generated type structure `. :return: the Python object. .. c:function:: PyObject *sipConvertFromNewPyType(void *cpp, PyTypeObject *py_type, sipWrapper *owner, sipSimpleWrapper **selfp, const char *format, ...) This converts a new C structure or a C++ class instance to an instance of a corresponding Python type (as opposed to the corresponding generated Python type). This is useful when the C/C++ library provides some sort of mechanism whereby handwritten code has some control over the exact type of structure or class being created. Typically it would be used to create an instance of the generated derived class which would then allow Python re-implementations of C++ virtual methods to function properly. :param cpp: the C/C++ instance. :param py_type: the Python type object. This is called to create the Python object and is passed the arguments defined by the string of format characters. :param owner: is the optional owner of the Python object. :param selfp: is an optional pointer to the ``sipPySelf`` instance variable of the C/C++ instance if that instance's type is a generated derived class. Otherwise it should be ``NULL``. :param format: the string of format characters (see :c:func:`sipBuildResult()`). :return: the Python object. If there was an error then ``NULL`` is returned and a Python exception is raised. .. c:function:: PyObject *sipConvertFromNewType(void *cpp, const sipTypeDef *td, PyObject *transferObj) This converts a new C structure or a C++ class instance to an instance of the corresponding generated Python type. :param cpp: the C/C++ instance. :param td: the type's :ref:`generated type structure `. :param transferObj: this controls the ownership of the returned value. :return: the Python object. If *transferObj* is ``NULL`` or ``Py_None`` then ownership will be with Python. Otherwise ownership will be with C/C++ and the instance associated with *transferObj*. The Python type is influenced by any applicable :directive:`%ConvertToSubClassCode` code. .. c:function:: Py_ssize_t sipConvertFromSequenceIndex(Py_ssize_t idx, Py_ssize_t len) This converts a Python sequence index (i.e. where a negative value refers to the offset from the end of the sequence) to a C/C++ array index. If the index was out of range then a negative value is returned and a Python exception raised. :param idx: the sequence index. :param len: the length of the sequence. :return: the unsigned array index. .. c:function:: int sipConvertFromSliceObject(PyObject *slice, Py_ssize_t length, Py_ssize_t *start, Py_ssize_t *stop, Py_ssize_t *step, Py_ssize_t *slicelength) This is a thin wrapper around Python's :c:func:`PySlice_Unpack()` and :c:func:`PySlice_AdjustIndices()` functions. .. c:function:: PyObject *sipConvertFromType(void *cpp, const sipTypeDef *td, PyObject *transferObj) This converts a C structure or a C++ class instance to an instance of the corresponding generated Python type. :param cpp: the C/C++ instance. :param td: the type's :ref:`generated type structure `. :param transferObj: this controls the ownership of the returned value. :return: the Python object. If the C/C++ instance has already been wrapped then the result is a new reference to the existing object. If *transferObj* is ``NULL`` and the instance has already been wrapped then the ownership is unchanged. If *transferObj* is ``NULL`` and the instance is newly wrapped then ownership will be with C/C++. If *transferObj* is ``Py_None`` then ownership is transferred to Python via a call to :c:func:`sipTransferBack()`. Otherwise ownership is transferred to C/C++ and the instance associated with *transferObj* via a call to :c:func:`sipTransferTo()`. The Python class is influenced by any applicable :directive:`%ConvertToSubClassCode` code. .. c:function:: PyObject *sipConvertFromVoidPtr(void *cpp) This creates a :class:`sip.voidptr` object for a memory address. The object will be writeable but has no associated size. :param cpp: the memory address. :return: the :class:`sip.voidptr` object. .. c:function:: PyObject *sipConvertFromVoidPtrAndSize(void *cpp, Py_ssize_t size) This creates a :class:`sip.voidptr` object for a memory address. The object will be writeable and can be used as a mutable buffer object. :param cpp: the memory address. :param size: the size associated with the address. :return: the :class:`sip.voidptr` object. .. c:function:: PyObject *sipConvertToArray(void *data, const char *format, Py_ssize_t len, int flags) This converts a one dimensional array of fundamental types to a :class:`sip.array` object. An array is very like a Python :class:`memoryview` object. The underlying memory is not copied and may be modified in situ. Arrays support the buffer protocol and so can be passed to other modules, again without the underlying memory being copied. :param data: the address of the start of the C/C++ array. :param format: the format, as defined by the :mod:`struct` module, of an array element. At the moment only ``b`` (char), ``B`` (unsigned char), ``h`` (short), ``H`` (unsigned short), ``i`` (int), ``I`` (unsigned int), ``f`` (float) and ``d`` (double) are supported. :param len: the number of elements in the array. :param readonly: is non-zero if the array is read-only. :param flags: any combination of the :c:macro:`SIP_READ_ONLY` and :c:macro:`SIP_OWNS_MEMORY` flags. :return: the :class:`sip.array` object. .. c:function:: int sipConvertToBool(PyObject *obj) This converts a Python object to an integer corresponding to a C++ ``bool``. :param obj: the Python object to convert. :return: the boolean value as an integer. ``1`` corresponds to ``true`` and ``0`` corresponds to ``false``. ``-1`` is returned, and an exception is raised, if there was an error. .. c:function:: int sipConvertToEnum(PyObject *obj, const sipTypeDef *td) This converts a Python object to the value of a named C/C++ ``enum`` member. :param obj: the Python object to convert. :param td: the enum's :ref:`generated type structure `. :return: the integer value. An exception is raised if there was an error. .. c:function:: void *sipConvertToType(PyObject *obj, const sipTypeDef *td, PyObject *transferObj, int flags, int *state, int *iserr) This converts a Python object to an instance of a C structure, C++ class or mapped type similar to :c:func:`sipConvertToTypeUS()` but without support for any user state. :param obj: the Python object. :param td: the type's :ref:`generated type structure `. :param transferObj: this controls any ownership changes to *obj*. :param flags: any combination of the :c:macro:`SIP_NOT_NONE` and :c:macro:`SIP_NO_CONVERTORS` flags. :param state: the state of the returned C/C++ instance is returned via this pointer. :param iserr: the error flag is passed and updated via this pointer. :return: the C/C++ instance. See :c:func:`sipConvertToTypeUS()` for a full description of the arguments. .. c:function:: void *sipConvertToTypeUS(PyObject *obj, const sipTypeDef *td, PyObject *transferObj, int flags, int *state, void **user_state, int *iserr) This converts a Python object to an instance of a C structure, C++ class or mapped type assuming that a previous call to :c:func:`sipCanConvertToType()` has been successful. :param obj: the Python object. :param td: the type's :ref:`generated type structure `. :param transferObj: this controls any ownership changes to *obj*. :param flags: any combination of the :c:macro:`SIP_NOT_NONE` and :c:macro:`SIP_NO_CONVERTORS` flags. :param state: the state of the returned C/C++ instance is returned via this pointer. :param user_state: any additional state of the returned C/C++ instance is returned via this pointer. :param iserr: the error flag is passed and updated via this pointer. :return: the C/C++ instance. If *transferObj* is ``NULL`` then the ownership is unchanged. If it is ``Py_None`` then ownership is transferred to Python via a call to :c:func:`sipTransferBack()`. Otherwise ownership is transferred to C/C++ and *obj* associated with *transferObj* via a call to :c:func:`sipTransferTo()`. Note that *obj* can also be managed by the C/C++ instance itself, but this can only be achieved by using :c:func:`sipTransferTo()`. If *state* is not ``NULL`` then the location it points to is set to describe the state of the returned C/C++ instance and is the value returned by any :directive:`%ConvertToTypeCode`. The calling code must then release the value at some point to prevent a memory leak by calling :c:func:`sipReleaseType()`. If *user_state* is not ``NULL`` then the location it points to may be used by the type convertor for any purpose, typically to store a pointer to additional state on the heap. Any such pointer is passed to the type's corresponding :c:func:`sipReleaseTypeUS()` function. If there is an error then the location *iserr* points to is set to a non-zero value. If it was initially a non-zero value then the conversion isn't attempted in the first place. (This allows several calls to be made that share the same error flag so that it only needs to be tested once rather than after each call.) .. c:function:: PyObject *sipConvertToTypedArray(void *data, const sipTypeDef *td, const char *format, size_t stride, Py_ssize_t len, int flags) This converts a one dimensional array of instances of a C structure, C++ class or mapped type to a :class:`sip.array` object. An array is very like a Python :class:`memoryview` object but it's elements correspond to C structures or C++ classes. The underlying memory is not copied and may be modified in situ. Arrays support the buffer protocol and so can be passed to other modules, again without the underlying memory being copied. :param data: the address of the start of the C/C++ array. :param td: an element's type's :ref:`generated type structure `. :param format: the format, as defined by the :mod:`struct` module, of an array element. :param stride: the size of an array element, including any padding. :param len: the number of elements in the array. :param flags: the optional :c:macro:`SIP_READ_ONLY` flag. :return: the :class:`sip.array` object. .. c:function:: void *sipConvertToVoidPtr(PyObject *obj) This converts a Python object to a memory address. :c:func:`PyErr_Occurred()` must be used to determine if the conversion was successful. :param obj: the Python object which may be ``Py_None``, a :class:`sip.voidptr` or a :c:type:`PyCObject`. :return: the memory address. .. c:type:: sipDateDef This C structure is used with :c:func:`sipGetDate()`, :c:func:`sipFromDate()`, :c:func:`sipGetDateTime()` and :c:func:`sipFromDateTime()` and encapsulates the components parts of a Python date. The structure elements are as follows. .. c:member:: int pd_year The year. .. c:member:: int pd_month The month (1-12). .. c:member:: int pd_day The day (1-31). .. c:function:: int sipEnableAutoconversion(const sipTypeDef *td, int enable) Instances of some classes may be automatically converted to other Python objects even though the class has been wrapped. This allows that behaviour to be suppressed so that an instances of the wrapped class is returned instead. :param td: the type's :ref:`generated type structure `. This must refer to a class. :param enable: is non-zero if auto-conversion should be enabled for the type. This is the default behaviour. :return: ``1`` or ``0`` depending on whether or not auto-conversion was previously enabled for the type. This allows the previous state to be restored later on. ``-1`` is returned, and a Python exception raised, if there was an error. .. c:function:: int sipEnableGC(int enable) This enables or disables the Python garbarge collector. :param enable: is greater than ``0`` if the garbage collector should be enabled. :return: ``1`` or ``0`` depending on whether or not the garbage collector was previously enabled. This allows the previous state to be restored later on. ``-1`` is returned if there was an error. .. cpp:enum:: sipEventType This is the enum that defines the different event types. .. cpp:enumerator:: sipEventWrappedInstance This event is triggered whenever a C/C++ instance that is created by C/C++ (and not by Python) is wrapped. The handler is passed a ``void *`` which is the address of the C/C++ instance. .. cpp:enumerator:: sipEventCollectingWrapper This event is triggered whenever a Python wrapper object is being garbage collected. The handler is passed a pointer to the :c:type:`sipSimpleWrapper` object that is the Python wrapper object being garbage collected. .. c:function:: int sipExportSymbol(const char *name, void *sym) Python does not allow extension modules to directly access symbols in another extension module. This exports a symbol, referenced by a name, that can subsequently be imported, using :c:func:`sipImportSymbol()`, by another module. :param name: the name of the symbol. :param sym: the value of the symbol. :return: 0 if there was no error. A negative value is returned if *name* is already associated with a symbol or there was some other error. .. c:function:: const sipTypeDef *sipFindType(const char *type) This returns a pointer to the :ref:`generated type structure ` corresponding to a C/C++ type. :param type: the C/C++ declaration of the type. :return: the generated type structure. This will not change and may be saved in a static cache. ``NULL`` is returned if the C/C++ type doesn't exist. .. c:function:: void *sipForceConvertToType(PyObject *obj, const sipTypeDef *td, PyObject *transferObj, int flags, int *state, int *iserr) This converts a Python object to an instance of a C structure, C++ class or mapped type similar to :c:func:`sipForceConvertToTypeUS()` but without support for any user state. See :c:func:`sipForceConvertToType()` for a full description of the arguments. .. c:function:: void *sipForceConvertToTypeUS(PyObject *obj, const sipTypeDef *td, PyObject *transferObj, int flags, int *state, void **user_state, int *iserr) This converts a Python object to an instance of a C structure, C++ class or mapped type by calling :c:func:`sipCanConvertToType()` and, if it is successfull, calling :c:func:`sipConvertToTypeUS()`. See :c:func:`sipConvertToTypeUS()` for a full description of the arguments. .. c:function:: void sipFree(void *mem) This returns an area of memory allocated by :c:func:`sipMalloc()` to the heap. :param mem: the memory address. .. c:function:: PyObject *sipFromDate(const sipDateDef *date) This creates a Python date object from its component parts. :param date: the component parts of the date. :return: the Python date object. .. c:function:: PyObject *sipFromDateTime(const sipDateDef *date, const sipTimeDef *time) This creates a Python datetime object from its component parts. :param date: the date related component parts of the datetime. :param time: the time related component parts of the datetime. :return: the Python datetime object. .. c:function:: PyObject *sipFromMethod(const sipMethodDef *method) This creates a Python method object from its component parts. :param method: the component parts of the method. :return: the Python method object. .. c:function:: PyObject *sipFromTime(const sipTimeDef *time) This creates a Python time object from its component parts. :param time: the component parts of the time. :return: the Python time object. .. c:function:: void *sipGetAddress(sipSimpleWrapper *obj) This returns the address of the C structure or C++ class instance wrapped by a Python object. :param obj: the Python object. :return: the address of the C/C++ instance .. c:function:: int sipGetBufferInfo(PyObject *obj, sipBufferInfoDef *buffer_info) This checks to see if an object implements the Python buffer protocol and, if so, optionally returns the buffer information. It is similar to :c:func:`PyObject_GetBuffer` and should be used instead of that when the limited Python API is enabled. Note that, at the moment, only 1-dimensional buffers are supported. :param obj: the Python object. :param buffer_info: if this is not ``NULL``, and the object implements the buffer protocol, then the buffer information is returned in this structure. There should be a corresponding call to :c:func:`sipReleaseBuffer`. :return: > 0 if the object supports the buffer protocol and the buffer information was returned (if requested). 0 if the object does not support the buffer protocol. < 0 (and a Python exception is raised) if the object supports the buffer protocol but there was an error returning the requested buffer information. .. c:function:: int sipGetCFunction(PyObject *obj, sipCFunctionDef *c_function) This checks to see if an object is a Python C function object and, if so, optionally returns its component parts. :param obj: the Python object. :param c_function: if this is not ``NULL``, and the object is a C function object, then the component parts are returned in this structure. :return: a non-zero value if the object is a Python C function object. .. c:function:: int sipGetDate(PyObject *obj, sipDateDef *date) This checks to see if an object is a Python date object and, if so, optionally returns its component parts. :param obj: the Python object. :param date: if this is not ``NULL``, and the object is a date object, then the component parts are returned in this structure. :return: a non-zero value if the object is a Python date object. .. c:function:: int sipGetDateTime(PyObject *obj, sipDateDef *date, sipTimeDef *time) This checks to see if an object is a Python datetime object and, if so, optionally returns its component parts. :param obj: the Python object. :param date: if this is not ``NULL``, and the object is a datetime object, then the date related component parts are returned in this structure. :param time: if this is not ``NULL``, and the object is a datetime object, then the time related component parts are returned in this structure. :return: a non-zero value if the object is a Python datetime object. .. c:function:: PyInterpreterState *sipGetInterpreter() This returns the address of the Python interpreter. If it is ``NULL`` then calls to the Python interpreter library must not be made. :return: the address of the Python interpreter .. c:function:: int sipGetMethod(PyObject *obj, sipMethodDef *method) This checks to see if an object is a Python method object and, if so, optionally returns its component parts. :param obj: the Python object. :param method: if this is not ``NULL``, and the object is a method object, then the component parts are returned in this structure. :return: a non-zero value if the object is a Python method object. .. c:function:: void *sipGetMixinAddress(sipSimpleWrapper *obj, const sipTypeDef *td) This returns the address of the C++ class instance that implements the mixin of a wrapped Python object. :param obj: the Python object. :param td: the :ref:`generated type structure ` corresponding to the C++ type of the mixin. :return: the address of the C++ instance .. c:function:: PyObject *sipGetPyObject(void *cppptr, const sipTypeDef *td) This returns a borrowed reference to the Python object for a C structure or C++ class instance. :param cppptr: the pointer to the C/C++ instance. :param td: the :ref:`generated type structure ` corresponding to the C/C++ type. :return: the Python object or ``NULL`` (and no exception is raised) if the C/C++ instance hasn't been wrapped. .. c:function:: int sipGetState(PyObject *transferObj) The :directive:`%ConvertToTypeCode` directive requires that the provided code returns an ``int`` describing the state of the converted value. The state usually depends on any transfers of ownership that have been requested. This is a convenience function that returns the correct state when the converted value is a temporary. :param transferObj: the object that describes the requested transfer of ownership. :return: the state of the converted value. .. c:function:: int sipGetTime(PyObject *obj, sipTimeDef *time) This checks to see if an object is a Python time object and, if so, optionally returns its component parts. :param obj: the Python object. :param time: if this is not ``NULL``, and the object is a time object, then the component parts are returned in this structure. :return: a non-zero value if the object is a Python time object. .. c:function:: void *sipGetTypeUserData(const sipWrapperType *type) Each generated type corresponding to a wrapped C/C++ type, or a user sub-class of such a type, contains a pointer for the use of handwritten code. This returns the value of that pointer. :param type: the type object. :return: the type-specific pointer. .. c:function:: PyObject *sipGetUserObject(const sipSimpleWrapper *obj) Each wrapped object can contain a reference to a single Python object that can be used for any purpose by handwritten code and will automatically be garbage collected at the appropriate time. This returns that object. :param obj: the wrapped object. :return: the user object. .. c:function:: void *sipImportSymbol(const char *name) Python does not allow extension modules to directly access symbols in another extension module. This imports a symbol, referenced by a name, that has previously been exported, using :c:func:`sipExportSymbol()`, by another module. :param name: the name of the symbol. :return: the value of the symbol. ``NULL`` is returned if there is no such symbol. .. c:function:: void sipInstanceDestroyed(sipSimpleWrapper *obj) This should be called by handwritten code if it is able to detect that a wrapped C++ instance has been destroyed from C++. It should not be called if SIP is able to detect this itself, i.e. when the instance was created from Python and the class has a virtual destructor. :param obj: the Python object that wraps the destroyed instance. .. c:function:: int sipIsEnumFlag(PyObject *obj) This determines if an object is a sub-class of :py:class:`enum.Flag`. :param obj: the object. :return: a non-zero value if the object is a :py:class:`enum.Flag` sub-class. .. c:function:: int sipIsOwnedByPython(sipSimpleWrapper *obj) This determines if a wrapped object is currently owned by Python. :param obj: the wrapped object. :return: a non-zero value if the object is currently owned by Python. .. c:function:: int sipIsUserType(const sipWrapperType *type) This checks if a type corresponds to a wrapped C/C++ type or a user sub-class of such a type. :param type: the type object. :return: a non-zero value if the type is a user defined type. .. c:function:: char sipLong_AsChar(PyObject *obj) This converts a Python object to a C/C++ char. If the value is too large then an exception is raised. :param obj: the Python object. :return: the converted C/C++ value. .. c:function:: signed char sipLong_AsSignedChar(PyObject *obj) This converts a Python object to a C/C++ signed char. If the value is too large then an exception is raised. :param obj: the Python object. :return: the converted C/C++ value. .. c:function:: unsigned char sipLong_AsUnsignedChar(PyObject *obj) This converts a Python object to a C/C++ unsigned char. If the value is too large then an exception is raised. :param obj: the Python object. :return: the converted C/C++ value. .. c:function:: short sipLong_AsShort(PyObject *obj) This converts a Python object to a C/C++ short. If the value is too large then an exception is raised. :param obj: the Python object. :return: the converted C/C++ value. .. c:function:: unsigned short sipLong_AsUnsignedShort(PyObject *obj) This converts a Python object to a C/C++ unsigned short. If the value is too large then an exception is raised. :param obj: the Python object. :return: the converted C/C++ value. .. c:function:: int sipLong_AsInt(PyObject *obj) This converts a Python object to a C/C++ int. If the value is too large then an exception is raised. :param obj: the Python object. :return: the converted C/C++ value. .. c:function:: unsigned int sipLong_AsUnsignedInt(PyObject *obj) This converts a Python object to a C/C++ unsigned int. If the value is too large then an exception is raised. :param obj: the Python object. :return: the converted C/C++ value. .. c:function:: size_t sipLong_AsSizeT(PyObject *obj) This converts a Python object to a C/C++ size_t. If the value is too large then an exception is raised. :param obj: the Python object. :return: the converted C/C++ value. .. c:function:: long sipLong_AsLong(PyObject *obj) This converts a Python object to a C/C++ long. If the value is too large then an exception is raised. :param obj: the Python object. :return: the converted C/C++ value. .. c:function:: unsigned long sipLong_AsUnsignedLong(PyObject *obj) This converts a Python object to a C/C++ unsigned long. If the value is too large then an exception is raised. :param obj: the Python object. :return: the converted C/C++ value. .. c:function:: long long sipLong_AsLongLong(PyObject *obj) This converts a Python object to a C/C++ long long. If the value is too large then an exception is raised. :param obj: the Python object. :return: the converted C/C++ value. .. c:function:: unsigned long long sipLong_AsUnsignedLongLong(PyObject *obj) This converts a Python object to a C/C++ unsigned long long. If the value is too large then an exception is raised. :param obj: the Python object. :return: the converted C/C++ value. .. c:function:: void *sipMalloc(size_t nbytes) This allocates an area of memory on the heap using the Python :c:func:`PyMem_RawMalloc()` function. The memory is freed by calling :c:func:`sipFree()`. :param nbytes: the number of bytes to allocate. :return: the memory address. If there was an error then ``NULL`` is returned and a Python exception raised. .. c:type:: sipMethodDef This C structure is used with :c:func:`sipGetMethod()` and :c:func:`sipFromMethod()` and encapsulates the components parts of a Python method. The structure elements are as follows. .. c:member:: PyObject *pm_function The function that implements the method. .. c:member:: PyObject *pm_self The bound object. .. c:function:: int sipParseResult(int *iserr, PyObject *method, PyObject *result, const char *format, ...) This converts a Python object (usually returned by a method) to C/C++ based on a format string and associated values in a similar way to the Python :c:func:`PyArg_ParseTuple()` function. :param iserr: if this is not ``NULL`` then the location it points to is set to a non-zero value if there was an error. :param method: the Python method that returned *result*. :param result: the Python object returned by *method*. :param format: the format string. :return: 0 if there was no error. Otherwise a negative value is returned, and an exception raised. This is normally called by handwritten code specified with the :directive:`%VirtualCatcherCode` directive with *method* being the supplied ``sipMethod`` and *result* being the value returned by :c:func:`sipCallMethod()`. If *format* begins and ends with parentheses then *result* must be a Python tuple and the rest of *format* is applied to the tuple contents. In the following description the first letter is the format character, the entry in parentheses is the Python object type that the format character will convert, and the entry in brackets are the types of the C/C++ values to be passed. ``ae`` (object) [char \*] Convert a Python string-like object of length 1 to a C/C++ ``char`` according to the encoding ``e``. ``e`` can either be ``A`` for ASCII, ``L`` for Latin-1, or ``8`` for UTF-8. The object may either be a ``bytes`` object or a ``str`` object that can be encoded. An object that supports the buffer protocol may also be used. ``b`` (integer) [bool \*] Convert a Python integer to a C/C++ ``bool``. ``c`` (bytes) [char \*] Convert a Python ``bytes`` object of length 1 to a C/C++ ``char``. ``d`` (float) [double \*] Convert a Python floating point number to a C/C++ ``double``. ``e`` (integer) [enum \*] Convert a Python integer to an anonymous C/C++ ``enum``. ``f`` (float) [float \*] Convert a Python floating point number to a C/C++ ``float``. ``g`` (bytes) [const char \*\*, :c:macro:`Py_ssize_t` \*] Convert a Python ``bytes`` object to a C/C++ character array and its length. If the Python object is ``Py_None`` then the array and length are ``NULL`` and zero respectively. ``h`` (integer) [short \*] Convert a Python integer to a C/C++ ``short``. ``i`` (integer) [int \*] Convert a Python integer to a C/C++ ``int``. ``l`` (long) [long \*] Convert a Python long to a C/C++ ``long``. ``m`` (long) [unsigned long \*] Convert a Python long to a C/C++ ``unsigned long``. ``n`` (long) [long long \*] Convert a Python long to a C/C++ ``long long``. ``o`` (long) [unsigned long long \*] Convert a Python long to a C/C++ ``unsigned long long``. ``t`` (long) [unsigned short \*] Convert a Python long to a C/C++ ``unsigned short``. ``u`` (long) [unsigned int \*] Convert a Python long to a C/C++ ``unsigned int``. ``w`` (string) [wchar_t \*] Convert a Python ``str`` object of length 1 to a C/C++ wide character. ``x`` (string) [wchar_t \*\*] Convert a Python ``str`` object to a C/C++ ``L'\0'`` terminated wide character string. If the Python object is ``Py_None`` then the string is ``NULL``. ``Ae`` (object) [int, const char \*\*] Convert a Python string-like object to a C/C++ ``'\0'`` terminated string according to the encoding ``e``. ``e`` can either be ``A`` for ASCII, ``L`` for Latin-1, or ``8`` for UTF-8. If the Python object is ``Py_None`` then the string is ``NULL``. The integer uniquely identifies the object in the context defined by the ``S`` format character and allows an extra reference to the object to be kept to ensure that the string remains valid. The object may either be a ``bytes`` object or a ``str`` object that can be encoded. An object that supports the buffer protocol may also be used. ``B`` (bytes) [int, const char \*\*] Convert a Python ``bytes`` object to a C/C++ ``'\0'`` terminated string. If the Python object is ``Py_None`` then the string is ``NULL``. The integer uniquely identifies the object in the context defined by the ``S`` format character and allows an extra reference to the object to be kept to ensure that the string remains valid. ``F`` (wrapped enum) [:c:type:`sipTypeDef` \*, enum \*] Convert a Python named enum type to the corresponding C/C++ ``enum``. ``G`` (string) [wchar_t \*\*, :c:macro:`Py_ssize_t` \*] Convert a Python ``str`` object to a C/C++ wide character array and its length. If the Python object is ``Py_None`` then the array and length are ``NULL`` and zero respectively. ``Hf`` (wrapped instance) [const :c:type:`sipTypeDef` \*, int \*, void \*\*] Convert a Python object to a C structure, C++ class or mapped type instance as described in :c:func:`sipConvertToType()`. ``f`` is a combination of the following flags encoded as an ASCII character by adding ``0`` to the combined value: 0x01 disallows the conversion of ``Py_None`` to ``NULL`` 0x02 implements the :fanno:`Factory` and :fanno:`TransferBack` annotations 0x04 returns a copy of the C/C++ instance. ``L`` (integer) [signed char \*] Convert a Python integer to a C/C++ ``signed char``. ``M`` (long) [unsigned char \*] Convert a Python long to a C/C++ ``unsigned char``. ``N`` (object) [PyTypeObject \*, PyObject \*\*] A Python object is checked to see if it is a certain type and then returned without any conversions. The reference count is incremented. The Python object may be ``Py_None``. ``O`` (object) [PyObject \*\*] A Python object is returned without any conversions. The reference count is incremented. ``S`` [:c:type:`sipSimpleWrapper` \*] This format character, if used, must be the first. It is used with other format characters to define a context and doesn't itself convert an argument. ``T`` (object) [PyTypeObject \*, PyObject \*\*] A Python object is checked to see if it is a certain type and then returned without any conversions. The reference count is incremented. The Python object may not be ``Py_None``. ``V`` (:class:`sip.voidptr`) [void \*\*] Convert a Python :class:`sip.voidptr` object to a C/C++ ``void *``. ``z`` (object) [const char \*, void \*\*] Convert a Python named capsule object to a C/C++ ``void *``. ``Z`` (object) [] Check that a Python object is ``Py_None``. No value is returned. ``!`` (object) [PyObject \*\*] A Python object is checked to see if it implements the buffer protocol and then returned without any conversions. The reference count is incremented. The Python object may not be ``Py_None``. ``$`` (object) [PyObject \*\*] A Python object is checked to see if it implements the buffer protocol and then returned without any conversions. The reference count is incremented. The Python object may be ``Py_None``. ``=`` (long) [size_t \*] Convert a Python long to a C/C++ ``size_t``. .. c:function:: PyObject *sipPyTypeDict(const PyTypeObject *py_type) This provides access to a Python type object's ``tp_dict`` field and is typically used when the limited Python API is enabled. .. note:: This is deprecated in ABI v13.6 and must not be used with Python v3.12 and later. :param py_type: the type object. :return: the value of the type object's ``tp_dict`` field. .. c:function:: PyObject *sipPyTypeDictRef(PyTypeObject *py_type) This provides access to a Python type object's type dictionary and is typically used when the limited Python API is enabled. :param py_type: the type object. :return: a new reference to type object's type dictionary. .. c:function:: void sipPrintObject(PyObject *obj) This is a thin wrapper around :c:func:`PyObject_Print()` that is typically used when debugging when the limited Python API is enabled. :param obj: the Python object. .. c:function:: const char *sipPyTypeName(const PyTypeObject *py_type) This provides access to a Python type object's ``tp_name`` field and is typically used when the limited Python API is enabled. :param py_type: the type object. :return: the value of the type object's ``tp_name`` field. .. c:function:: int sipRegisterAttributeGetter(const sipTypeDef *td, sipAttrGetterFunc getter) This registers a getter that will be called just before SIP needs to get an attribute from a wrapped type's dictionary for the first time. The getter must then populate the type's dictionary with any lazy attributes. :param td: the optional :ref:`generated type structure ` that determines which types the getter will be called for. :param getter: the getter function. :return: 0 if there was no error, otherwise -1 is returned. If *td* is not ``NULL`` then the getter will only be called for types with that type or that are sub-classed from it. Otherwise the getter will be called for all types. A getter has the following signature. int getter(const :c:type:`sipTypeDef` \*td, PyObject \*dict) *td* is the generated type definition of the type whose dictionary is to be populated. *dict* is the dictionary to be populated. 0 is returned if there was no error, otherwise -1 is returned. See the section :ref:`ref-lazy-type-attributes` for more details. .. c:function:: int sipRegisterEventHandler(sipEventType type, const sipTypeDef *td, void *handler) This registers an event handler which will be called whenever an event is triggered. :param type: the event type for which the handler is registered. :param td: the generated type structure - the handler will only be invoked for Python object corresponding to this type or a sub-type. :param handler: the handler that is called when the event is triggered. :return: 0 if there was no error, otherwise -1 is returned (and a Python exception is raised). .. c:function:: int sipRegisterExitNotifier(PyMethodDef *md) This registers a C function with Python's :py:mod:`atexit` module that will be called when the interpreter terminates. :param md: the data structure that describes the C function to be called. :return: 0 if there was no error, otherwise -1 is returned. .. c:function:: int sipRegisterProxyResolver(const sipTypeDef *td, sipProxyResolverFunc resolver) This registers a resolver that will be called just before SIP wraps a C/C++ pointer in a Python object. The resolver may choose to replace the C/C++ pointer with the address of another object. Typically this is used to replace a proxy by the object that is being proxied for. :param td: the optional :ref:`generated type structure ` that determines which type the resolver will be called for. :param resolver: the resolver function. :return: 0 if there was no error, otherwise -1 is returned. A resolver has the following signature. void \*resolver(void \*proxy) *proxy* is C/C++ pointer that is being wrapped. The C/C++ pointer that will actually be wrapped is returned. .. c:function:: int sipRegisterPyType(PyTypeObject *type) This registers a Python type object that can be used as the meta-type or super-type of a wrapped C++ type. :param type: the type object. :return: 0 if there was no error, otherwise -1 is returned. See the section :ref:`ref-types-metatypes` for more details. .. c:function:: void sipReleaseBufferInfo(sipBufferInfoDef *buffer_info) This releases the buffer information related to a Python object that implements the buffer protocol that was created with a corresponding call to :c:func:`sipGetBufferInfo`. It is similar to :c:func:`PyBuffer_Release` and should be used instead of that when the limited Python API is enabled. :param buffer_info: the buffer information to release. .. c:function:: void sipReleaseType(void *cpp, const sipTypeDef *td, int state) This releases a wrapped C/C++ or mapped type instance to the heap if it was a temporary instance similar to :c:func:`sipReleaseTypeUS()` but without support for any user state. :param cpp: the C/C++ instance. :param td: the type's :ref:`generated type structure `. :param state: describes the state of the C/C++ instance. See :c:func:`sipReleaseTypeUS()` for a full description of the arguments. .. c:function:: void sipReleaseTypeUS(void *cpp, const sipTypeDef *td, int state, void *user_state) This releases a wrapped C/C++ or mapped type instance to the heap if it was a temporary instance. It is called after a call to either :c:func:`sipConvertToTypeUS()` or :c:func:`sipForceConvertToTypeUS()`. :param cpp: the C/C++ instance. :param td: the type's :ref:`generated type structure `. :param state: describes the state of the C/C++ instance. :param user_state: the value set by the corresponding call to :c:func:`sipConvertToTypeUS()` or :c:func:`sipForceConvertToTypeUS()`. .. c:function:: const char *sipResolveTypedef(const char *name) This returns the value of a C/C++ typedef. :param name: the name of the typedef. :return: the value of the typedef or ``NULL`` if there was no such typedef. .. c:function:: void sipSetDestroyOnExit(int destroy) When the Python interpreter exits it garbage collects those objects that it can. This means that any corresponding C++ instances and C structures owned by Python are destroyed. Unfortunately this happens in an unpredictable order and so can cause memory faults within the wrapped library. Calling this function with a value of zero disables the automatic destruction of C++ instances and C structures. :param destroy: non-zero if all C++ instances and C structures owned by Python should be destroyed when the interpreter exits. This is the default. .. c:function:: void sipSetTypeUserData(sipWrapperType *type, void *data) Each generated type corresponding to a wrapped C/C++ type, or a user sub-class of such a type, contains a pointer for the use of handwritten code. This sets the value of that pointer. :param type: the type object. :param data: the type-specific pointer. .. c:function:: void sipSetUserObject(sipSimpleWrapper *obj, PyObject *user) Each wrapped object can contain a reference to a single Python object that can be used for any purpose by handwritten code and will automatically be garbage collected at the appropriate time. This sets that object. :param obj: the wrapped object. :param user: a borrowed reference to the user object. .. c:type:: sipSimpleWrapper This is a C structure that represents a Python wrapped instance whose type is :class:`sip.simplewrapper`. It is an extension of the ``PyObject`` structure and so may be safely cast to it. When the limited Python API is enabled then it is only available as an opaque (i.e. incomplete) type and the following members are not available. .. c:member:: void *data This is initialised to the address of the C/C++ instance. If an access function is subsequently provided then it may be used for any purpose by the access function. .. c:member:: sipAccessFunc access_func This is the address of an optional access function that is called, with a pointer to this structure as its first argument. If its second argument is ``UnguardedPointer`` then it returns the address of the C/C++ instance, even if it is known that its value is no longer valid. If the second argument is ``GuardedPointer`` then it returns the address of the C++ instance or ``0`` if it is known to be invalid. If the second argument is ``ReleaseGuard`` then the structure is being deallocated and any dynamic resources used by the access function should be released. If there is no access function then the :c:member:`sipSimpleWrapper.data` is used as the address of the C/C++ instance. Typically a custom meta-type is used to set an access method after the Python object has been created. .. c:member:: PyObject *user This can be used for any purpose by handwritten code and will automatically be garbage collected at the appropriate time. .. c:var:: PyTypeObject *sipSimpleWrapper_Type This is the type of a :c:type:`sipSimpleWrapper` structure and is the C implementation of :class:`sip.simplewrapper`. It may be safely cast to :c:type:`sipWrapperType`. When the limited Python API is enabled then it is only available as an opaque (i.e. incomplete) type. .. c:type:: sipTimeDef This C structure is used with :c:func:`sipGetTime()`, :c:func:`sipFromTime()`, :c:func:`sipGetDateTime()` and :c:func:`sipFromDateTime()` and encapsulates the components parts of a Python time. The structure elements are as follows. .. c:member:: int pt_hour The hour (0-23). .. c:member:: int pt_minute The minute (0-59). .. c:member:: int pt_second The second (0-59). .. c:member:: int pt_microsecond The microsecond (0-999999). .. c:function:: void sipTransferBack(PyObject *obj) This transfers ownership of a Python wrapped instance to Python (see :ref:`ref-object-ownership`). :param obj: the wrapped instance. In addition, any association of the instance with regard to the cyclic garbage collector with another instance is removed. .. c:function:: void sipTransferTo(PyObject *obj, PyObject *owner) This transfers ownership of a Python wrapped instance to C++ (see :ref:`ref-object-ownership`). :param obj: the wrapped instance. :param owner: an optional wrapped instance that *obj* becomes associated with with regard to the cyclic garbage collector. If *owner* is ``NULL`` then no such association is made. If *owner* is ``Py_None`` then *obj* is given an extra reference which is removed when the C++ instance's destructor is called. If *owner* is the same value as *obj* then any reference cycles involving *obj* can never be detected or broken by the cyclic garbage collector. Responsibility for calling the C++ instance's destructor is always transfered to C++. .. c:function:: PyTypeObject *sipTypeAsPyTypeObject(const sipTypeDef *td) This returns a pointer to the Python type object that SIP creates for a :ref:`generated type structure `. :param td: the type structure. :return: the Python type object. If the type structure refers to a mapped type then ``NULL`` will be returned. If the type structure refers to a C structure or C++ class then the Python type object may be safely cast to a :c:type:`sipWrapperType`. .. c:function:: const sipTypeDef *sipTypeFromPyTypeObject(PyTypeObject *py_type) This returns the :ref:`generated type structure ` for a Python type object. :param py_type: the Python type object. :return: the type structure or ``NULL`` if the Python type object doesn't correspond to a type structure. .. c:function:: int sipTypeIsClass(sipTypeDef *td) This checks if a :ref:`generated type structure ` refers to a C structure or C++ class. :param td: the type structure. :return: a non-zero value if the type structure refers to a structure or class. .. c:function:: int sipTypeIsEnum(sipTypeDef *td) This checks if a :ref:`generated type structure ` refers to a C-style named enum. :param td: the type structure. :return: a non-zero value if the type structure refers to a C-style named enum. .. c:function:: int sipTypeIsMapped(sipTypeDef *td) This checks if a :ref:`generated type structure ` refers to a mapped type. :param td: the type structure. :return: a non-zero value if the type structure refers to a mapped type. .. c:function:: int sipTypeIsNamespace(sipTypeDef *td) This checks if a :ref:`generated type structure ` refers to a C++ namespace. :param td: the type structure. :return: a non-zero value if the type structure refers to a namespace. .. c:function:: int sipTypeIsScopedEnum(sipTypeDef *td) This checks if a :ref:`generated type structure ` refers to a C++11 scoped enum. :param td: the type structure. :return: a non-zero value if the type structure refers to a C++11 scoped enum. .. c:function:: const char *sipTypeName(const sipTypeDef *td) This returns the C/C++ name of a wrapped type. :param td: the type's :ref:`generated type structure `. :return: the name of the C/C++ type. .. c:function:: const sipTypeDef *sipTypeScope(const sipTypeDef *td) This returns the :ref:`generated type structure ` of the enclosing scope of another generated type structure. :param td: the type structure. :return: the type structure of the scope or ``NULL`` if the type has no scope. .. c:function:: void *sipUnicodeData(PyObject *obj, int *char_size, Py_ssize_t *len) This returns information about the contents of a Python unicode object. :param obj: the unicode object. :param char_size: a pointer which will be updated with the number of bytes (either 1, 2 or 4) used to store a character. If there was an error then this will be a negative value. :param len: a pointer which will be updated with the number of characters (not bytes) in the unicode object. :return: the address of the buffer where the characters are stored. It will be undefined if the returned character size is a negative value. .. c:function:: PyObject *sipUnicodeNew(Py_ssize_t len, unsigned maxchar, int *kind, void **data) This creates a Python unicode object that will hold a set number of characters, each character being of a certain size. :param len: the number of characters. :param maxchar: the largest code point that will be placed in the object. :param kind: a pointer which will be updated with a value that represents the number of bytes (either 1, 2 or 4) used to store a character. :param data: a pointer which will be updated with the address of the buffer where the characters will be stored. :return: the unicode object or ``NULL`` if there was an error. .. c:function:: void sipUnicodeWrite(int kind, void *data, int index, unsigned value) This updates the buffer of a Python unicode object with a character at a particular position. :param kind: the value that represents the number of bytes (either 1, 2 or 4) used to store a character. :param data: the address of the buffer where the characters are stored. :param index: the character (not byte) index of the character to be updated. :param value: the value of the new character. .. c:function:: void sipVisitWrappers(sipWrapperVisitorFunc visitor, void *closure) This calls a visitor function for every wrapper object. :param visitor: the visitor function. :param closure: a pointer that is passed to the visitor. A visitor has the following signature. void visitor(sipSimpleWrapper \*obj, void \*closure) *obj* is the wrapped object being visited. *closure* is the pointer passed to :c:func:`sipVisitWrappers()`. .. c:var:: PyTypeObject *sipVoidPtr_Type This is the type of a ``PyObject`` structure that is used to wrap a ``void *``. .. c:type:: sipWrapper This is a C structure that represents a Python wrapped instance whose type is :class:`sip.wrapper`. It is an extension of the :c:type:`sipSimpleWrapper` and ``PyObject`` structures and so may be safely cast to both. When the limited Python API is enabled then it is only available as an opaque (i.e. incomplete) type. .. c:var:: PyTypeObject *sipWrapper_Type This is the type of a :c:type:`sipWrapper` structure and is the C implementation of :class:`sip.wrapper`. It may be safely cast to :c:type:`sipWrapperType`. .. c:type:: sipWrapperType This is a C structure that represents a SIP generated type object. It is an extension of the ``PyTypeObject`` structure (which is itself an extension of the ``PyObject`` structure) and so may be safely cast to ``PyTypeObject`` (and ``PyObject``). When the limited Python API is enabled then it is only available as an opaque (i.e. incomplete) type. .. c:var:: PyTypeObject *sipWrapperType_Type This is the type of a :c:type:`sipWrapperType` structure and is the C implementation of :class:`sip.wrappertype`. sip-6.8.6/docs/annotations.rst000066400000000000000000001225521464421045000163540ustar00rootroot00000000000000.. _ref-annotations: Annotations =========== In this section we describe each of the annotations that can be used in specification files. Annotations can either be :ref:`argument annotations `, :ref:`class annotations `, :ref:`mapped type annotations `, :ref:`enum annotations `, :ref:`exception annotations `, :ref:`function annotations `, :ref:`typedef annotations ` or :ref:`variable annotations ` depending on the context in which they can be used. Annotations are placed between forward slashes (``/``). Multiple annotations are comma separated within the slashes. Annotations have a type and, possibly, a value. The type determines the format of the value. The name of an annotation and its value are separated by ``=``. Annotations can have one of the following types: *boolean* This type of annotation has no value and is implicitly true. *integer* This type of annotation is an integer. In some cases the value is optional. *name* The value is a name that is compatible with a C/C++ identifier. In some cases the value is optional. *dotted name* The value is a name that is compatible with an identifier preceded by a Python scope. *string* The value is a double quoted string. The following example shows argument and function annotations:: void exec(QWidget * /Transfer/) /ReleaseGIL/; .. _ref-arg-annos: Argument Annotations -------------------- .. argument-annotation:: AllowNone This boolean annotation specifies that the value of the corresponding argument (which should be either :stype:`SIP_PYBUFFER`, :stype:`SIP_PYCALLABLE`, :stype:`SIP_PYDICT`, :stype:`SIP_PYLIST`, :stype:`SIP_PYSLICE`, :stype:`SIP_PYTUPLE` or :stype:`SIP_PYTYPE`) may be ``None``. .. argument-annotation:: Array This boolean annotation specifies that the corresponding argument refers to an array. The argument should be either a pointer to a wrapped type, a ``char *`` or a ``unsigned char *``. If the argument is a character array then the annotation also implies the :aanno:`Encoding` annotation with an encoding of ``"None"``. There must be a corresponding argument with the :aanno:`ArraySize` annotation specified. The annotation may only be specified once in a list of arguments. .. argument-annotation:: ArraySize This boolean annotation specifies that the corresponding argument (which should be either ``short``, ``unsigned short``, ``int``, ``unsigned``, ``long`` or ``unsigned long``) refers to the size of an array. There must be a corresponding argument with the :aanno:`Array` annotation specified. The annotation may only be specified once in a list of arguments. .. argument-annotation:: Constrained Python will automatically convert between certain compatible types. For example, if a floating pointer number is expected and an integer supplied, then the integer will be converted appropriately. This can cause problems when wrapping C or C++ functions with similar signatures. For example:: // The wrapper for this function will also accept an integer argument // which Python will automatically convert to a floating point number. void foo(double); // The wrapper for this function will never get used. void foo(int); This boolean annotation specifies that the corresponding argument (which should be either ``bool``, ``int``, ``float``, ``double``, ``enum`` or a wrapped class) must match the type without any automatic conversions. In the context of a wrapped class the invocation of any :directive:`%ConvertToTypeCode` is suppressed. The following example gets around the above problem:: // The wrapper for this function will only accept floating point // numbers. void foo(double /Constrained/); // The wrapper for this function will be used for anything that Python // can convert to an integer, except for floating point numbers. void foo(int); Any type hint for the argument will be ignored. .. argument-annotation:: DisallowNone This boolean annotation specifies that the value of the corresponding argument (which should be a pointer to either a C++ class or a mapped type) must not be ``None``. .. argument-annotation:: Encoding This string annotation specifies that the corresponding argument (which should be either ``char``, ``const char``, ``char *`` or ``const char *``) refers to an encoded character or ``'\0'`` terminated encoded string with the specified encoding. The encoding can be either ``"ASCII"``, ``"Latin-1"``, ``"UTF-8"`` or ``"None"``. An encoding of ``"None"`` means that the corresponding argument refers to an unencoded character or string. The default encoding is specified by the :directive:`%DefaultEncoding` directive. If the directive is not specified then ``None`` is used. The ``bytes`` type is used to represent the argument if the encoding is ``"None"`` and the ``str`` type otherwise. .. argument-annotation:: GetWrapper This boolean annotation is only ever used in conjunction with handwritten code specified with the :directive:`%MethodCode` directive. It causes an extra variable to be generated for the corresponding argument which is a pointer to the Python object that wraps the argument. See the :directive:`%MethodCode` directive for more detail. .. argument-annotation:: In This boolean annotation is used to specify that the corresponding argument (which should be a pointer type) is used to pass a value to the function. For pointers to wrapped C structures or C++ class instances, ``char *`` and ``unsigned char *`` then this annotation is assumed unless the :aanno:`Out` annotation is specified. For pointers to other types then this annotation must be explicitly specified if required. The argument will be dereferenced to obtain the actual value. Both :aanno:`In` and :aanno:`Out` may be specified for the same argument. .. argument-annotation:: KeepReference This optional integer annotation is used to specify that a reference to the corresponding argument should be kept to ensure that the object is not garbage collected. If the method is called again with a new argument then the reference to the previous argument is discarded. Note that ownership of the argument is not changed. If the function is a method then the reference is kept by the instance, i.e. ``self``. Therefore the extra reference is released when the instance is garbage collected. If the function is a class method or an ordinary function and it is annotated using the :fanno:`Factory` annotation, then the reference is kept by the object created by the function. Therefore the extra reference is released when that object is garbage collected. Otherwise the reference is not kept by any specific object and will never be released. If a value is specified then it defines the argument's key. Arguments of different constructors or methods that have the same key are assumed to refer to the same value. .. argument-annotation:: NoCopy This boolean annotation is used with arguments of virtual methods that are a ``const`` reference to a class. Normally, if the class defines a copy constructor then a copy of the returned reference is automatically created and wrapped before being passed to a Python reimplementation of the method. The copy will be owned by Python. This means that the reimplementation may take a reference to the argument without having to make an explicit copy. If the annotation is specified then the copy is not made and the original reference is wrapped instead and will be owned by C++. .. argument-annotation:: Out This boolean annotation is used to specify that the corresponding argument (which should be a pointer type) is used by the function to return a value as an element of a tuple. For pointers to wrapped C structures or C++ class instances, ``char *`` and ``unsigned char *`` then this annotation must be explicitly specified if required. For pointers to other types then this annotation is assumed unless the :aanno:`In` annotation is specified. Both :aanno:`In` and :aanno:`Out` may be specified for the same argument. .. argument-annotation:: PyInt This boolean annotation is used with ``char``, ``signed char`` and ``unsigned char`` arguments to specify that they should be interpreted as integers rather than strings of one character. .. argument-annotation:: ResultSize This boolean annotation is used with functions or methods that return a ``void *`` or ``const void *``. It identifies an argument that defines the size of the block of memory whose address is being returned. This allows the :class:`sip.voidptr` object that wraps the address to support the Python buffer protocol. .. argument-annotation:: ScopesStripped This integer annotation is only used with Qt signal arguments. Normally the fully scoped type of the argument is used but this annotation specifies that the given number of scopes should be removed. .. argument-annotation:: Transfer This boolean annotation is used to specify that ownership of the corresponding argument (which should be a wrapped C structure or C++ class instance) is transferred from Python to C++. In addition, if the argument is of a class method, then it is associated with the class instance with regard to the cyclic garbage collector. If the annotation is used with the :aanno:`Array` annotation then the array of pointers to the sequence of C structures or C++ class instances that is created on the heap is not automatically freed. See :ref:`ref-object-ownership` for more detail. .. argument-annotation:: TransferBack This boolean annotation is used to specify that ownership of the corresponding argument (which should be a wrapped C structure or C++ class instance) is transferred back to Python from C++. In addition, any association of the argument with regard to the cyclic garbage collector with another instance is removed. See :ref:`ref-object-ownership` for more detail. .. argument-annotation:: TransferThis This boolean annotation is only used in C++ constructors or methods. In the context of a constructor or factory method it specifies that ownership of the instance being created is transferred from Python to C++ if the corresponding argument (which should be a wrapped C structure or C++ class instance) is not ``None``. In addition, the newly created instance is associated with the argument with regard to the cyclic garbage collector. In the context of a non-factory method it specifies that ownership of ``this`` is transferred from Python to C++ if the corresponding argument is not ``None``. If it is ``None`` then ownership is transferred to Python. The annotation may be used more that once, in which case ownership is transferred to last instance that is not ``None``. See :ref:`ref-object-ownership` for more detail. .. argument-annotation:: TypeHint This string annotation specifies the type of the argument as it will appear in any generated docstrings and PEP 484 type hints. It is the equivalent of specifying :aanno:`TypeHintIn` and :aanno:`TypeHintOut` with the same value. It is usually used with arguments of type :stype:`SIP_PYOBJECT` to provide a more specific type. .. argument-annotation:: TypeHintIn This string annotation specifies the type of the argument as it will appear in any generated docstrings and PEP 484 type hints when the argument is used to pass a value to a function (rather than being used to return a value from a function). It is usually used with arguments of type :stype:`SIP_PYOBJECT` to provide a more specific type. .. argument-annotation:: TypeHintOut This string annotation specifies the type of the argument as it will appear in any generated docstrings and PEP 484 type hints when the argument is used to return a value from a function (rather than being used to pass a value to a function). It is usually used with arguments of type :stype:`SIP_PYOBJECT` to provide a more specific type. .. argument-annotation:: TypeHintValue This string annotation specifies the default value of the argument as it will appear in any generated docstrings. .. _ref-class-annos: Class Annotations ----------------- .. class-annotation:: Abstract This boolean annotation is used to specify that the class has additional pure virtual methods that have not been specified and so it cannot be instantiated or sub-classed from Python. It should not be specified if all pure virtual methods have been specified. .. class-annotation:: AllowNone Normally when a Python object is converted to a C/C++ instance ``None`` is handled automatically before the class's :directive:`%ConvertToTypeCode` is called. This boolean annotation specifies that the handling of ``None`` will be left to the :directive:`%ConvertToTypeCode`. The annotation is ignored if the class does not have any :directive:`%ConvertToTypeCode`. .. class-annotation:: DelayDtor This boolean annotation is used to specify that the class's destructor should not be called until the Python interpreter exits. It would normally only be applied to singleton classes. When the Python interpreter exits the order in which any wrapped instances are garbage collected is unpredictable. However, the underlying C or C++ instances may need to be destroyed in a certain order. If this annotation is specified then when the wrapped instance is garbage collected the C or C++ instance is not destroyed but instead added to a list of delayed instances. When the interpreter exits then the function :c:func:`sipDelayedDtors()` is called with the list of delayed instances. :c:func:`sipDelayedDtors()` can then choose to call (or ignore) the destructors in any desired order. The :c:func:`sipDelayedDtors()` function must be specified using the :directive:`%ModuleCode` directive. .. c:function:: void sipDelayedDtors(const sipDelayedDtor *dd_list) :param dd_list: the linked list of delayed instances. .. c:type:: sipDelayedDtor This structure describes a particular delayed destructor. .. c:member:: const char* dd_name This is the name of the class excluding any package or module name. .. c:member:: void* dd_ptr This is the address of the C or C++ instance to be destroyed. It's exact type depends on the value of :c:member:`dd_isderived`. .. c:member:: int dd_isderived This is non-zero if the type of :c:member:`dd_ptr` is actually the generated derived class. This allows the correct destructor to be called. See :ref:`ref-derived-classes`. .. c:member:: sipDelayedDtor* dd_next This is the address of the next entry in the list or zero if this is the last one. Note that the above applies only to C and C++ instances that are owned by Python. .. class-annotation:: Deprecated This boolean annotation is used to specify that the class is deprecated. It is the equivalent of annotating all the class's constructors, function and methods as being deprecated. .. class-annotation:: FileExtension This string annotation is used to specify the filename extension to be used for the file containing the generated code for this class. .. class-annotation:: ExportDerived In many cases SIP generates a derived class for each class being wrapped (see :ref:`ref-derived-classes`). Normally this is used internally. This boolean annotation specifies that the declaration of the class is exported and able to be used by handwritten code. .. class-annotation:: External This boolean annotation is used to specify that the class is defined in another module. Declarations of external classes are private to the module in which they appear. .. class-annotation:: Metatype This dotted name annotation specifies the name of the Python type object (i.e. the value of the ``tp_name`` field) used as the meta-type used when creating the type object for this C structure or C++ type. See the section :ref:`ref-types-metatypes` for more details. .. class-annotation:: Mixin This boolean annotation specifies that the class can be used as a mixin with other wrapped classes. Normally a Python application cannot define a new class that is derived from more than one wrapped class. In C++ this would create a new C++ class. This cannot be done from Python. At best a C++ instance of each of the wrapped classes can be created and wrapped as separate Python objects. However some C++ classes may function perfectly well with this restriction. Such classes are often intended to be used as mixins. If this annotation is specified then a separate instance of the class is created. The main instance automatically delegates to the instance of the mixin when required. A mixin class should have the following characteristics: - Any constructor arguments should be able to be specified using keyword arguments. - The class should not have any virtual methods. .. class-annotation:: NoDefaultCtors This boolean annotation is used to suppress the automatic generation of default constructors for the class. .. class-annotation:: NoTypeHint This boolean annotation is used to suppress the generation of the PEP 484 type hint for the class and its contents. .. class-annotation:: PyName This name annotation specifies an alternative name for the class being wrapped which is used when it is referred to from Python. .. seealso:: :directive:`%AutoPyName` .. class-annotation:: Supertype This dotted name annotation specifies the name of the Python type object (i.e. the value of the ``tp_name`` field) used as the super-type used when creating the type object for this C structure or C++ type. See the section :ref:`ref-types-metatypes` for more details. .. class-annotation:: TypeHint This string annotation specifies the type of the class as it will appear in any generated docstrings and PEP 484 type hints. It is the equivalent of specifying :canno:`TypeHintIn` and :canno:`TypeHintOut` with the same value. .. class-annotation:: TypeHintIn This string annotation specifies the type of the class as it will appear in any generated docstrings and PEP 484 type hints when an instance of the class is passed as an argument to a function (rather than being returned from a function). It is usually used with classes that implement :directive:`%ConvertToTypeCode` to allow additional types to be used whenever an instance of the class is expected. .. class-annotation:: TypeHintOut This string annotation specifies the type of the class as it will appear in any generated docstrings and PEP 484 type hints when an instance of the class is returned from a function (rather than being used to pass a value to a function). .. class-annotation:: TypeHintValue This string annotation specifies the default value of the class as it will appear in any generated docstrings. .. class-annotation:: VirtualErrorHandler This name annotation specifies the handler (defined by the :directive:`%VirtualErrorHandler` directive) that is called when a Python re-implementation of any of the class's virtual C++ functions raises a Python exception. If not specified then the handler specified by the ``default_VirtualErrorHandler`` argument of the :directive:`%Module` directive is used. .. seealso:: :fanno:`NoVirtualErrorHandler`, :fanno:`VirtualErrorHandler`, :directive:`%VirtualErrorHandler` .. _ref-mapped-type-annos: Mapped Type Annotations ----------------------- .. mapped-type-annotation:: AllowNone Normally when a Python object is converted to a C/C++ instance ``None`` is handled automatically before the mapped type's :directive:`%ConvertToTypeCode` is called. This boolean annotation specifies that the handling of ``None`` will be left to the :directive:`%ConvertToTypeCode`. .. mapped-type-annotation:: NoAssignmentOperator This boolean annotation is used to specify that the C++ type does not have a public assignment operator. .. mapped-type-annotation:: NoCopyCtor This boolean annotation is used to specify that the C++ type does not have a public copy constructor. .. mapped-type-annotation:: NoDefaultCtor This boolean annotation is used to specify that the C++ type does not have a public default constructor. .. mapped-type-annotation:: NoRelease This boolean annotation is used to specify that the mapped type does not support the :c:func:`sipReleaseType()` function. Any :directive:`%ConvertToTypeCode` should not create temporary instances of the mapped type, i.e. it should not return :c:macro:`SIP_TEMPORARY`. .. mapped-type-annotation:: PyName This name annotation specifies an alternative name for the mapped type being wrapped which is used when it is referred to from Python. The only time a Python type is created for a mapped type is when it is used as a scope for static methods or enums. It should not be used with mapped type templates. .. seealso:: :directive:`%AutoPyName` .. mapped-type-annotation:: TypeHint This string annotation specifies the type of the mapped type as it will appear in any generated docstrings and PEP 484 type hints. It is the equivalent of specifying :manno:`TypeHintIn` and :manno:`TypeHintOut` with the same value. .. mapped-type-annotation:: TypeHintIn This string annotation specifies the type of the mapped type as it will appear in any generated docstrings and PEP 484 type hints when it is passed to a function (rather than being returned from a function). .. mapped-type-annotation:: TypeHintOut This string annotation specifies the type of the mapped type as it will appear in any generated docstrings and PEP 484 type hints when it is returned from a function (rather than being passed to a function). .. mapped-type-annotation:: TypeHintValue This string annotation specifies the default value of the mapped type as it will appear in any generated docstrings. .. _ref-enum-annos: Enum Annotations ---------------- .. enum-annotation:: BaseType This name annotation specifies the type from the :mod:`enum` module that will be used as the base type of the enum. The possible values are ``Enum`` (corresponding to :class:`~enum.Enum`), ``Flag`` (corresponding to :class:`~enum.Flag`), ``IntEnum`` (corresponding to :class:`~enum.IntEnum`), ``UIntEnum`` (also corresponding to :class:`~enum.IntEnum` but with unsigned members) and ``IntFlag`` (corresponding to :class:`~enum.IntFlag`). The default value is ``Enum``. The members of ``Flag`` and ``IntFlag`` enums are implicitly unsigned. This annotation is only available when ABI v13 or later is specified. .. enum-annotation:: NoScope This boolean annotation specifies the that scope of an enum's members should be omitted in the generated code. Normally this would mean that the generated code will not compile. However it is useful when defining pseudo-enums, for example, to wrap global values so that they are defined (in Python) within the scope of a class. .. enum-annotation:: NoTypeHint This boolean annotation is used to suppress the generation of the PEP 484 type hint for the enum or enum member. .. enum-annotation:: PyName This name annotation specifies an alternative name for the enum or enum member being wrapped which is used when it is referred to from Python. .. seealso:: :directive:`%AutoPyName` .. _ref-exception-annos: Exception Annotations --------------------- .. exception-annotation:: Default This boolean annotation specifies that the exception being defined will be used as the default exception to be caught if a function or constructor does not have a ``throw`` clause. This annotaion is ignored when using ABI v13.1 or later and v12.9 or later. .. exception-annotation:: PyName This name annotation specifies an alternative name for the exception being defined which is used when it is referred to from Python. .. seealso:: :directive:`%AutoPyName` .. _ref-function-annos: Function Annotations -------------------- .. function-annotation:: AbortOnException This boolean annotation specifies that when a Python re-implementation of a virtual C++ function raises a Python exception then ``abort()`` is called after the error handler returns. .. function-annotation:: AllowNone This boolean annotation is used to specify that the value returned by the function (which should be either :stype:`SIP_PYBUFFER`, :stype:`SIP_PYCALLABLE`, :stype:`SIP_PYDICT`, :stype:`SIP_PYLIST`, :stype:`SIP_PYSLICE`, :stype:`SIP_PYTUPLE` or :stype:`SIP_PYTYPE`) may be ``None``. .. function-annotation:: AutoGen This optional name annotation is used with class methods to specify that the method be automatically included in all sub-classes. The value is the name of a feature (specified using the :directive:`%Feature` directive) which must be enabled for the method to be generated. .. function-annotation:: Default This boolean annotation is only used with C++ constructors. Sometimes SIP needs to create a class instance. By default it uses a constructor with no compulsory arguments if one is specified. (SIP will automatically generate a constructor with no arguments if no constructors are specified.) This annotation is used to explicitly specify which constructor to use. Zero is passed as the value of any arguments to the constructor. This annotation is ignored if the class defines :directive:`%InstanceCode`. .. function-annotation:: Deprecated This boolean annotation is used to specify that the constructor or function is deprecated. A deprecation warning is issued whenever the constructor or function is called. .. function-annotation:: DisallowNone This boolean annotation is used to specify that the value returned by the function (which should be a pointer to either a C++ class or a mapped type) must not be ``None``. .. function-annotation:: Encoding This string annotation serves the same purpose as the :aanno:`Encoding` argument annotation when applied to the type of the value returned by the function. .. function-annotation:: Factory This boolean annotation specifies that the value returned by the function (which should be a wrapped C structure or C++ class instance) is a newly created instance and is owned by Python. See :ref:`ref-object-ownership` for more detail. .. function-annotation:: HoldGIL This boolean annotation specifies that the Python Global Interpreter Lock (GIL) is not released before the call to the underlying C or C++ function. See :ref:`ref-gil` and the :fanno:`ReleaseGIL` annotation. .. function-annotation:: __imatmul__ This boolean annotation specifies that a ``__imatmul__()`` method should be automatically generated that will use the method being annotated to compute the value that the ``__imatmul__()`` method will return. .. function-annotation:: KeepReference This optional integer annotation serves the same purpose as the :aanno:`KeepReference` argument annotation when applied to the type of the value returned by the function. If the function is a class method or an ordinary function then the reference is not kept by any other object and so the returned value will never be garbage collected. .. function-annotation:: KeywordArgs This string annotation specifies the level of support the argument parser generated for this function will provide for passing the parameters using Python's keyword argument syntax. The value of the annotation can be either ``"None"`` meaning that keyword arguments are not supported, ``"All"`` meaning that all named arguments can be passed as keyword arguments, or ``"Optional"`` meaning that all named optional arguments (i.e. those with a default value) can be passed as keyword arguments. If the annotation is not used then the value specified by the ``keyword_arguments`` argument of the :directive:`%Module` directive is used. Keyword arguments cannot be used for functions that use an ellipsis to designate that the function has a variable number of arguments. .. function-annotation:: __len__ This boolean annotation specifies that a ``__len__()`` method should be automatically generated that will use the method being annotated to compute the value that the ``__len__()`` method will return. If the class has a ``__getitem__()`` method or an ``operator[]`` operator with an integer argument then those will raise an ``IndexError`` exception if the argument is out of range. This means that the class will automatically support being iterated over. .. function-annotation:: __matmul__ This boolean annotation specifies that a ``__matmul__()`` method should be automatically generated that will use the method being annotated to compute the value that the ``__matmul__()`` method will return. .. function-annotation:: NewThread This boolean annotation specifies that the function (which must be a virtual) will be executed in a new thread. .. function-annotation:: NoArgParser This boolean annotation is used with methods and global functions to specify that the supplied :directive:`%MethodCode` will handle the parsing of the arguments. .. function-annotation:: NoCopy This boolean annotation is used with methods and global functions that return a ``const`` reference to a class. Normally, if the class defines a copy constructor then a copy of the returned reference is automatically created and wrapped. The copy will be owned by Python. If the annotation is specified then the copy is not made and the original reference is wrapped instead and will be owned by C++. .. function-annotation:: NoDerived This boolean annotation is only used with C++ constructors. In many cases SIP generates a derived class for each class being wrapped (see :ref:`ref-derived-classes`). This derived class contains constructors with the same C++ signatures as the class being wrapped. Sometimes you may want to define a Python constructor that has no corresponding C++ constructor. This annotation is used to suppress the generation of the constructor in the derived class. .. function-annotation:: NoRaisesPyException This boolean annotation specifies that the function or constructor does not raise a Python exception to indicate that an error occurred. .. seealso:: :fanno:`RaisesPyException` .. function-annotation:: NoTypeHint This boolean annotation is used to suppress the generation of the PEP 484 type hint for the function or constructor. .. function-annotation:: NoVirtualErrorHandler This boolean annotation specifies that when a Python re-implementation of a virtual C++ function raises a Python exception then ``PyErr_Print()`` is always called. Any error handler specified by either the :fanno:`VirtualErrorHandler` function annotation, the :canno:`VirtualErrorHandler` class annotation or the ``default_VirtualErrorHandler`` argument of the :directive:`%Module` directive is ignored. .. seealso:: :fanno:`VirtualErrorHandler`, :canno:`VirtualErrorHandler`, :directive:`%VirtualErrorHandler` .. function-annotation:: Numeric This boolean annotation specifies that the operator should be interpreted as a numeric operator rather than a sequence operator. Python uses the ``+`` operator for adding numbers and concatanating sequences, and the ``*`` operator for multiplying numbers and repeating sequences. Unless this or the :fanno:`Sequence` annotation is specified, SIP tries to work out which is meant by looking at other operators that have been defined for the type. If it finds either ``-``, ``-=``, ``/``, ``/=``, ``%`` or ``%=`` defined then it assumes that ``+``, ``+=``, ``*`` and ``*=`` should be numeric operators. Otherwise, if it finds either ``[]``, :meth:`__getitem__`, :meth:`__setitem__` or :meth:`__delitem__` defined then it assumes that they should be sequence operators. .. function-annotation:: PostHook This name annotation is used to specify the name of a Python builtin that is called immediately after the call to the underlying C or C++ function or any handwritten code. The builtin is not called if an error occurred. It is primarily used to integrate with debuggers. .. function-annotation:: PreHook This name annotation is used to specify the name of a Python builtin that is called immediately after the function's arguments have been successfully parsed and before the call to the underlying C or C++ function or any handwritten code. It is primarily used to integrate with debuggers. .. function-annotation:: PyName This name annotation specifies an alternative name for the function being wrapped which is used when it is referred to from Python. .. seealso:: :directive:`%AutoPyName` .. function-annotation:: PyInt This boolean annotation serves the same purpose as the :aanno:`PyInt` argument annotation when applied to the type of the value returned by the function. .. function-annotation:: RaisesPyException This boolean annotation specifies that the function or constructor raises a Python exception to indicate that an error occurred. Any current exception is cleared before the function or constructor is called. It is ignored if the :directive:`%MethodCode` directive is used. .. seealso:: :fanno:`NoRaisesPyException` .. function-annotation:: ReleaseGIL This boolean annotation specifies that the Python Global Interpreter Lock (GIL) is released before the call to the underlying C or C++ function and reacquired afterwards. It should be used for functions that might block or take a significant amount of time to execute. See :ref:`ref-gil` and the :fanno:`HoldGIL` annotation. .. function-annotation:: Sequence This boolean annotation specifies that the operator should be interpreted as a sequence operator rather than a numeric operator. Python uses the ``+`` operator for adding numbers and concatanating sequences, and the ``*`` operator for multiplying numbers and repeating sequences. Unless this or the :fanno:`Numeric` annotation is specified, SIP tries to work out which is meant by looking at other operators that have been defined for the type. If it finds either ``-``, ``-=``, ``/``, ``/=``, ``%`` or ``%=`` defined then it assumes that ``+``, ``+=``, ``*`` and ``*=`` should be numeric operators. Otherwise, if it finds either ``[]``, :meth:`__getitem__`, :meth:`__setitem__` or :meth:`__delitem__` defined then it assumes that they should be sequence operators. .. function-annotation:: Transfer This boolean annotation specifies that ownership of the value returned by the function (which should be a wrapped C structure or C++ class instance) is transferred to C++. It is only used in the context of a class constructor or a method. In the case of methods returned values (unless they are new references to already wrapped values) are normally owned by C++ anyway. However, in addition, an association between the returned value and the instance containing the method is created with regard to the cyclic garbage collector. See :ref:`ref-object-ownership` for more detail. .. function-annotation:: TransferBack This boolean annotation specifies that ownership of the value returned by the function (which should be a wrapped C structure or C++ class instance) is transferred back to Python from C++. Normally returned values (unless they are new references to already wrapped values) are owned by C++. In addition, any association of the returned value with regard to the cyclic garbage collector with another instance is removed. See :ref:`ref-object-ownership` for more detail. .. function-annotation:: TransferThis This boolean annotation specifies that ownership of ``this`` is transferred from Python to C++. See :ref:`ref-object-ownership` for more detail. .. function-annotation:: TypeHint This string annotation specifies the type of the value returned by the function as it will appear in any generated docstrings and PEP 484 type hints. It is usually used with results of type :stype:`SIP_PYOBJECT` to provide a more specific type. .. function-annotation:: VirtualErrorHandler This name annotation specifies the handler (defined by the :directive:`%VirtualErrorHandler` directive) that is called when a Python re-implementation of the virtual C++ function raises a Python exception. If not specified then the handler specified by the class's :canno:`VirtualErrorHandler` is used. .. seealso:: :fanno:`NoVirtualErrorHandler`, :canno:`VirtualErrorHandler`, :directive:`%VirtualErrorHandler` .. _ref-typedef-annos: Typedef Annotations ------------------- .. typedef-annotation:: Capsule This boolean annotation may only be used when the base type is ``void *`` and specifies that a Python capsule object is used to wrap the value rather than a :class:`sip.voidptr`. The advantage of using a capsule is that name based type checking is performed using the name of the type being defined. For versions of Python that do not support capules :class:`sip.voidptr` is used instead and name based type checking is not performed. .. typedef-annotation:: Encoding This string annotation serves the same purpose as the :aanno:`Encoding` argument annotation when applied to the mapped type being defined. .. typedef-annotation:: NoTypeName This boolean annotation specifies that the definition of the type rather than the name of the type being defined should be used in the generated code. Normally a typedef would be defined as follows:: typedef bool MyBool; This would result in ``MyBool`` being used in the generated code. Specifying the annotation means that ``bool`` will be used in the generated code instead. .. typedef-annotation:: PyInt This boolean annotation serves the same purpose as the :aanno:`PyInt` argument annotation when applied to the type being defined. .. typedef-annotation:: PyName This name annotation only applies when the typedef is being used to create the wrapping for a class defined using a template and specifies an alternative name for the class when it is referred to from Python. .. seealso:: :directive:`%AutoPyName` .. typedef-annotation:: TypeHint This string annotation specifies the type as it will appear in any generated docstrings and PEP 484 type hints. It is the equivalent of specifying :tanno:`TypeHintIn` and :tanno:`TypeHintOut` with the same value. .. typedef-annotation:: TypeHintIn This string annotation specifies the type as it will appear in any generated docstrings and PEP 484 type hints when it is passed to a function (rather than being returned from a function). It is usually used with arguments of type :stype:`SIP_PYOBJECT` to provide a more specific type. .. typedef-annotation:: TypeHintOut This string annotation specifies the type as it will appear in any generated docstrings and PEP 484 type hints when it is returned from a function (rather than being passed to a function). It is usually used with arguments of type :stype:`SIP_PYOBJECT` to provide a more specific type. .. _ref-variable-annos: Variable Annotations -------------------- .. variable-annotation:: Encoding This string annotation serves the same purpose as the :aanno:`Encoding` argument annotation when applied to the type of the variable being defined. .. variable-annotation:: NoSetter This boolean annotation specifies that the variable will have no setter and will be read-only. Because SIP does not fully understand C/C++ types (particularly ``const`` arrays) it is sometimes necessary to explicitly annotate a variable as being read-only. .. variable-annotation:: NoTypeHint This boolean annotation is used to suppress the generation of the PEP 484 type hint for the variable. .. variable-annotation:: PyInt This boolean annotation serves the same purpose as the :aanno:`PyInt` argument annotation when applied to the type of the variable being defined. .. variable-annotation:: PyName This name annotation specifies an alternative name for the variable being wrapped which is used when it is referred to from Python. .. seealso:: :directive:`%AutoPyName` .. variable-annotation:: TypeHint This string annotation specifies the type of the variable as it will appear in any generated docstrings and PEP 484 type hints. It is usually used with arguments of type :stype:`SIP_PYOBJECT` to provide a more specific type. sip-6.8.6/docs/c_api.rst000066400000000000000000000120551464421045000150660ustar00rootroot00000000000000.. _ref-c-api: C API for Handwritten Code ========================== The :mod:`sip` module provides an API that can be used by handwritten code in specification files. The API has a number of versions each of which is implemented by an ABI. ABIs use semantic versionion so that, for example, v12.1 is compatible with v12.0 but is incompatible with v11.0, v12.2 and v13.0. The API is declared in the :file:`sip.h` header file. SIP will ensure this file is installed in a location such that the compiler will automatically find it. The details of each currently supported ABI are described in subsequent sections. The remainder of this section covers topics that are common to all ABI versions. .. _ref-type-structures: Generated Type Structures ------------------------- SIP generates an opaque type structure for each C structure, C++ class, C++ namespace, traditional named enum or mapped type being wrapped. These are :c:type:`sipTypeDef` structures and are used extensively by the API. The names of these structure are prefixed by ``sipType_``. For those structures that correspond to C structures, C++ classes, C++ namespaces or traditional named enums the remaining part of the name is the fully qualified name of the structure, class, namespace or enum name. Any ``::`` scope separators are replaced by an underscore. For example, the type object for class ``Klass`` is ``sipType_Klass``. For those structure that correspond to mapped types the remaining part of the name is generated by SIP. The only way for handwritten code to obtain a pointer to a structure for a mapped type is to use :c:func:`sipFindType()`. The type structures of all imported types explicitly used by a module are available to handwritten code. .. _ref-derived-classes: Generated Derived Classes ------------------------- For most C++ classes being wrapped SIP generates a derived class with the same name prefixed by ``sip``. For example, the derived class for class ``Klass`` is ``sipKlass``. If a C++ class doesn't have any virtual or protected methods in it or any of it's super-class hierarchy then a derived class is not generated. Most of the time handwritten code should ignore the derived classes. The only exception is that handwritten constructor code specified using the :directive:`%MethodCode` directive should call the derived class's constructor (which has the same C++ signature) rather then the wrapped class's constructor. .. _ref-exception-objects: Generated Exception Objects --------------------------- SIP generates a Python object for each exception defined with the :directive:`%Exception` directive. These objects are named with the fully qualified exception name (i.e. including any enclosing scope) prefixed by ``sipException_``. For example, the type object for exception ``Except`` defined in class ``Klass`` is ``sipException_Klass_Except``. The objects of all imported exceptions are available to handwritten code. Event Handlers -------------- The :mod:`sip` module will trigger a number of events. Handwritten code can supply handlers for these events to allow it to perform additional actions. Each event has a type, described by the :cpp:enum:`sipEventType` enum. An event handler is registered using :c:func:`sipRegisterEventHandler()`. The signature of an event handler is specific to the event type. Using the C API when Embedding ------------------------------ The C API is intended to be called from handwritten code in SIP generated modules. However it is also often necessary to call it from C/C++ applications that embed the Python interpreter and need to pass C/C++ instances between the application and the interpreter. The API is exported by the :mod:`sip` module as a ``sipAPIDef`` data structure (defined in the :file:`sip.h` header file) containing a set of function pointers. The data structure is wrapped as a Python ``PyCapsule`` object. It is referenced by the name ``_C_API`` in the :mod:`sip` module's dictionary. Each member of the data structure is a pointer to one of the functions of the C API. The name of the member can be derived from the function name by replacing the ``sip`` prefix with ``api`` and converting each word in the name to lower case and preceding it with an underscore. For example: ``sipExportSymbol`` becomes ``api_export_symbol`` ``sipWrapperCheck`` becomes ``api_wrapper_check`` Note that the type objects that SIP generates for a wrapped module (see :ref:`ref-type-structures` and :ref:`ref-exception-objects`) cannot be refered to directly and must be obtained using the :c:func:`sipFindType()` function. Of course, the corresponding modules must already have been imported into the interpreter. The following code fragment shows how to get a pointer to the ``sipAPIDef`` data structure:: #include "sip.h" const sipAPIDef *get_sip_api() { return (const sipAPIDef *)PyCapsule_Import("sip._C_API", 0); } ``"sip._C_API"`` should be replaced by the fully qualified name of the :mod:`sip` module. If you need to install a local copy of the :file:`sip.h` header file then you can do so with :program:`sip-module`. sip-6.8.6/docs/command_line_tools.rst000066400000000000000000000434601464421045000176640ustar00rootroot00000000000000Command Line Tools ================== The tools can also be invoked using the ``-m`` command line option of the Python interpreter. For example, the following two command lines are equivalent:: sip-build -h python -m sipbuild.tools.build -h Note that, for the build tools, the command line options described in this section are the standard options. Any of these options could be removed, or new options added, by build system extensions including project-specific :file:`project.py` files. :program:`sip-build` -------------------- :program:`sip-build` builds a project but does not install it. This is useful when developing a set of bindings. The syntax of the :program:`sip-build` command line is:: sip-build [options] The full set of command line options is: .. program:: sip-build .. option:: -h, --help Display a help message. .. option:: -V, --version Display the SIP version number. .. option:: --deprecations-are-errors The use of any deprecated feature is handled as an error rather than a warning. .. option:: --quiet All progress messages are disabled. .. option:: --verbose Verbose progress messages are enabled. .. option:: --api-dir DIR A QScintilla :file:`.api` file is created in ``DIR``. .. option:: --build-dir DIR ``DIR`` is created as a build directory in which all generated files will be created. The build directory is not removed after the build has been completed. The default value is ``build``. .. option:: --concatenate N The generated code is split into ``N`` files. By default one file is generated for each C structure or C++ class. Specifying a low value of ``N`` can significantly speed up the build of large projects. .. option:: --disable NAME The ``NAME`` bindings are disabled and will not be built. This option may be specified multiple times. It is only available if the project contains multiple sets of bindings. .. option:: --disabled-feature TAG The ``TAG`` feature tag is disabled. This option may be specified multiple times. .. option:: --enable NAME The ``NAME`` bindings are enabled and will be built. Any associated configuration tests that would normally be run to determine if the bindings should be built are suppressed. This option may be specified multiple times. It is only available if the project contains multiple sets of bindings. .. option:: --debug A build with debugging symbols is performed. .. option:: --no-compile The compilation of the generated code is disabled. .. option:: --no-docstrings The generation of docstrings that describe the signature of all functions, methods and constructors is disabled. .. option:: --no-version-info No reference the SIP version number is included in any generated code. .. option:: --pep484-pyi The generation of Python type hints stub files is enabled. These files contain a description of a module's API that is compliant with `PEP 484 `__. .. option:: --protected-is-public SIP can generate code to provide access to protected C++ functions from Python. On non-Windows platforms this code can be avoided if the ``protected`` keyword is redefined as ``public`` during compilation. This can result in a significant reduction in the size of a generated Python module. This option enables the redefinition of ``protected`` and is the default on all platforms except Windows. .. option:: --no-protected-is-public This option disables the redefinition of ``protected`` to access protected C++ functions from Python and is the default on Windows. .. option:: --scripts-dir DIR Any project scripts will eventually be installed in ``DIR``. If ``DIR`` is relative then it is taken as relative to the target directory. By default the directory containing the Python interpreter is used. .. option:: --target-dir DIR The project will eventually be installed in ``DIR``. By default it is the :file:`site-packages` directory of the Python installation. .. option:: --tracing Debugging statements that trace the execution of the bindings are automatically generated. By default the statements are not generated. :program:`sip-distinfo` ----------------------- :program:`sip-distinfo` creates and populates a :file:`.dist-info` directory of an installation or a wheel. It is provided for build systems that extend the SIP build system and need to create the :file:`.dist-info` directory from an external tool such as :program:`make`. The syntax of the :program:`sip-distinfo` command line is:: sip-distinfo [options] directory ``directory`` is the full path name of the directory to create. The full set of command line options is: .. program:: sip-distinfo .. option:: -h, --help Display a help message. .. option:: -V, --version Display the SIP version number. .. option:: --console-script ENTRY-POINT The console entry point ``ENTRY-POINT`` is added to the wheel. It is ignored if the :option:`--wheel-tag` option is not specified. This option may be specified multiple times. .. option:: --generator NAME If the :option:`--wheel-tag` option is specified then ``NAME`` is written as part of the ``Generator`` in the :file:`WHEEL` file in the :file:`.dist-info` directory. Otherwise ``NAME`` is written to the :file:`INSTALLER` file. By default ``sipbuild`` is written. .. option:: --generator-version VERSION ``VERSION`` is written as part of the ``Generator`` in the :file:`WHEEL` file in the :file:`.dist-info` directory. By default the SIP version number is written. .. option:: --gui-script ENTRY-POINT The GUI entry point ``ENTRY-POINT`` is added to the wheel. It is ignored if the :option:`--wheel-tag` option is not specified. This option may be specified multiple times. .. option:: --inventory FILE ``FILE`` contains a list of the relative names of the files, one per line, that comprise the installation or wheel contents. This option must be specified. .. option:: --metadata NAME[=VALUE] ``VALUE`` is used instead of any value specified for ``NAME`` in the ``[tool.sip.metadata]`` section of the :file:`pyproject.toml` file. .. option:: --prefix DIR This option is provided as an aid to Linux package builders. ``DIR`` is used to pass the commonly used values of ``DESTDIR`` or ``INSTALL_ROOT``. If specified it should have a trailing native path separator. .. option:: --project-root DIR The name of the directory containing the project's :file:`pyproject.toml` file is ``DIR``. This option must be specified. .. option:: --requires-dist EXPR ``EXPR`` is added to the list of prerequisites written to the :file:`METADATA` file in the :file:`.dist-info` directory. It is normally used to specify a particular version of a package project's :mod:`sip` module. This option may be specified multiple times. .. option:: --wheel-tag TAG ``TAG`` is written as the ``Tag`` in the :file:`WHEEL` file in the :file:`.dist-info` directory. :program:`sip-install` ---------------------- :program:`sip-install` builds and installs a project. The syntax of the :program:`sip-install` command line is:: sip-install [options] The full set of command line options is: .. program:: sip-install .. option:: -h, --help Display a help message. .. option:: -V, --version Display the SIP version number. .. option:: --deprecations-are-errors The use of any deprecated feature is handled as an error rather than a warning. .. option:: --quiet All progress messages are disabled. .. option:: --verbose Verbose progress messages are enabled. .. option:: --api-dir DIR A QScintilla :file:`.api` file is created in ``DIR``. .. option:: --build-dir DIR ``DIR`` is created as a build directory in which all generated files will be created. This build directory is not removed after the build has been completed. By default a temporary build directory is created which is removed after the build has been completed. .. option:: --concatenate N The generated code is split into ``N`` files. By default one file is generated for each C structure or C++ class. Specifying a low value of ``N`` can significantly speed up the build of large projects. .. option:: --disable NAME The ``NAME`` bindings are disabled and will not be built. This option may be specified multiple times. It is only available if the project contains multiple sets of bindings. .. option:: --disabled-feature TAG The ``TAG`` feature tag is disabled. This option may be specified multiple times. .. option:: --enable NAME The ``NAME`` bindings are enabled and will be built. Any associated configuration tests that would normally be run to determine if the bindings should be built are suppressed. This option may be specified multiple times. It is only available if the project contains multiple sets of bindings. .. option:: --debug A build with debugging symbols is performed. .. option:: --no-docstrings The generation of docstrings that describe the signature of all functions, methods and constructors is disabled. .. option:: --no-distinfo The creation of the :file:`.dist-info` directory is disabled. .. option:: --pep484-pyi The generation of Python type hints stub files is enabled. These files contain a description of a module's API that is compliant with `PEP 484 `__. .. option:: --protected-is-public SIP can generate code to provide access to protected C++ functions from Python. On non-Windows platforms this code can be avoided if the ``protected`` keyword is redefined as ``public`` during compilation. This can result in a significant reduction in the size of a generated Python module. This option enables the redefinition of ``protected`` and is the default on all platforms except Windows. .. option:: --no-protected-is-public This option disables the redefinition of ``protected`` to access protected C++ functions from Python and is the default on Windows. .. option:: --scripts-dir DIR Any project scripts will be installed in ``DIR``. If ``DIR`` is relative then it is taken as relative to the target directory. By default the directory containing the Python interpreter is used. .. option:: --target-dir DIR The project will be installed in ``DIR``. By default it is the :file:`site-packages` directory of the Python installation. .. option:: --tracing Debugging statements that trace the execution of the bindings are automatically generated. By default the statements are not generated. :program:`sip-module` --------------------- :program:`sip-module` builds one of more of the elements of the :mod:`sip` module for a set of package projects. The syntax of the :program:`sip-module` command line is:: sip-module [options] name ``name`` is the fully qualified name of the :mod:`sip` module (i.e. including the package name). The full set of command line options is: .. program:: sip-module .. option:: -h, --help Display a help message. .. option:: -V, --version Display the SIP version number. .. option:: --abi-version MAJOR[.MINOR] The major version number of the ABI implemented by the :mod:`sip` module is ``MAJOR``. If a minor version number is also specified it is interpreted as a minimum minor version rather than the exact minor version to be used. By default the very latest version is used. .. option:: --project NAME The name of the project as it would appear on PyPI is ``NAME``. By default the name is derived from the fully qualified name of the :mod:`sip` module. .. option:: --sdist Create an sdist which can then be installed by :program:`pip` or uploaded to PyPI. :program:`pip` can also be used to create a wheel from the sdist. However, for Linux wheels, :program:`auditwheel` must be run for the wheel before it can be uploaded to PyPI. .. option:: --setup-cfg FILE ``FILE`` is copied to the sdist as :file:`setup.cfg` instead of the default version. This allows the sdist to be customised. A number of macros may be specified in the :file:`setup.cfg` file: ``@SIP_MODULE_FQ_NAME@`` is replaced by the fully qualified name of the :mod:`sip` module. ``@SIP_MODULE_PACKAGE_NAME@`` is replaced by the module's project top-level package name. ``@SIP_MODULE_PROJECT_NAME@`` is replaced by the module's project name as it would appear on PyPI. ``@SIP_MODULE_VERSION@`` is replaced by the version number of the module. .. option:: --sip-h Create a :file:`sip.h` header file that defines the C ABI implemented by the :mod:`sip` module. .. option:: --sip-rst Create a :file:`sip.rst` file that documents the Python API implemented by the :mod:`sip` module. .. option:: --target-dir DIR Each of the module's elements will be created in ``DIR``. :program:`sip-sdist` -------------------- :program:`sip-sdist` creates an sdist (a source distribution) than be uploaded to PyPI. The syntax of the :program:`sip-sdist` command line is:: sip-sdist [options] The full set of command line options is: .. program:: sip-sdist .. option:: -h, --help Display a help message. .. option:: -V, --version Display the SIP version number. .. option:: --deprecations-are-errors The use of any deprecated feature is handled as an error rather than a warning. .. option:: --name NAME ``NAME`` is used instead of the PyPI project name in the :file:`pyproject.toml` file in the name of the sdist file. :program:`sip-wheel` -------------------- :program:`sip-wheel` creates a wheel (a binary distribution) than be uploaded to PyPI. The syntax of the :program:`sip-wheel` command line is:: sip-wheel [options] The full set of command line options is: .. program:: sip-wheel .. option:: -h, --help Display a help message. .. option:: -V, --version Display the SIP version number. .. option:: --deprecations-are-errors The use of any deprecated feature is handled as an error rather than a warning. .. option:: --quiet All progress messages are disabled. .. option:: --verbose Verbose progress messages are enabled. .. option:: --api-dir DIR A QScintilla :file:`.api` file is created in ``DIR``. This must be a name relative to the directory where the wheel will be installed. .. option:: --build-dir DIR ``DIR`` is created as a build directory in which all generated files will be created. This build directory is not removed after the build has been completed. By default a temporary build directory is created which is removed after the build has been completed. .. option:: --build-tag TAG ``TAG`` is the build tag to be used in the name of the wheel. By default the name of the wheel does not include a build tag. .. option:: --concatenate N The generated code is split into ``N`` files. By default one file is generated for each C structure or C++ class. Specifying a low value of ``N`` can significantly speed up the build of large projects. .. option:: --disable NAME The ``NAME`` bindings are disabled and will not be built. This option may be specified multiple times. It is only available if the project contains multiple sets of bindings. .. option:: --disabled-feature TAG The ``TAG`` feature tag is disabled. This option may be specified multiple times. .. option:: --enable NAME The ``NAME`` bindings are enabled and will be built. Any associated configuration tests that would normally be run to determine if the bindings should be built are suppressed. This option may be specified multiple times. It is only available if the project contains multiple sets of bindings. .. option:: --no-manylinux Support for ``manylinux`` in the platform tag of a name of a wheel is disabled. It should only be used if support for older versions of :program:`pip` is required. .. option:: --minimum-glibc-version M.N ``M.N`` is the minimum GLIBC version required by the project specified as the major and minor version numbers. This is used to determine the correct platform tag to use for Linux wheels. The default version of GLIBC is v2.5 which corresponds to ``manylinux1``. It is ignored if the ``--no-manylinux`` option is specified. .. option:: --name NAME ``NAME`` is used instead of the PyPI project name in the :file:`pyproject.toml` file in the name of the wheel file. .. option:: --debug A build with debugging symbols is performed. .. option:: --no-docstrings The generation of docstrings that describe the signature of all functions, methods and constructors is disabled. .. option:: --pep484-pyi The generation of Python type hints stub files is enabled. These files contain a description of a module's API that is compliant with `PEP 484 `__. .. option:: --protected-is-public SIP can generate code to provide access to protected C++ functions from Python. On non-Windows platforms this code can be avoided if the ``protected`` keyword is redefined as ``public`` during compilation. This can result in a significant reduction in the size of a generated Python module. This option enables the redefinition of ``protected`` and is the default on all platforms except Windows. .. option:: --no-protected-is-public This option disables the redefinition of ``protected`` to access protected C++ functions from Python and is the default on Windows. .. option:: --tracing Debugging statements that trace the execution of the bindings are automatically generated. By default the statements are not generated. sip-6.8.6/docs/conf.py000066400000000000000000000047251464421045000145650ustar00rootroot00000000000000# Configuration file for the Sphinx documentation builder. # # For the full list of built-in configuration values, see the documentation: # https://www.sphinx-doc.org/en/master/usage/configuration.html from datetime import date # -- Project information ----------------------------------------------------- # https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information project = 'sip' copyright = '{0} Phil Thompson '.format( date.today().year) author = 'Phil Thompson' version = 'v6.8.6' # -- General configuration --------------------------------------------------- # https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration extensions = ['myst_parser', 'sphinx.ext.intersphinx'] intersphinx_mapping = {'python': ('https://docs.python.org/3', None)} #templates_path = ['_templates'] exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] # -- Options for HTML output ------------------------------------------------- # https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output html_theme = 'sphinx_rtd_theme' #html_static_path = ['_static'] # MyST configuration myst_enable_extensions = {'deflist'} # -- Project-specific extensions ----------------------------------------------- def setup(app): """ Define roles specific to SIP. """ app.add_object_type('argument-annotation', 'aanno', indextemplate='single: %s (argument annotation)') app.add_object_type('class-annotation', 'canno', indextemplate='single: %s (class annotation)') app.add_object_type('enum-annotation', 'eanno', indextemplate='single: %s (enum annotation)') app.add_object_type('exception-annotation', 'xanno', indextemplate='single: %s (exception annotation)') app.add_object_type('function-annotation', 'fanno', indextemplate='single: %s (function annotation)') app.add_object_type('mapped-type-annotation', 'manno', indextemplate='single: %s (mapped type annotation)') app.add_object_type('typedef-annotation', 'tanno', indextemplate='single: %s (typedef annotation)') app.add_object_type('variable-annotation', 'vanno', indextemplate='single: %s (variable annotation)') app.add_object_type('sip-type', 'stype', indextemplate='single: %s (SIP type)') app.add_crossref_type('directive', 'directive', indextemplate='single: %s (directive)') sip-6.8.6/docs/directives.rst000066400000000000000000002354361464421045000161660ustar00rootroot00000000000000.. _ref-directives: Directives ========== In this section we describe each of the directives that can be used in specification files. All directives begin with ``%`` as the first non-whitespace character in a line. The format of a generic directive is shown below. .. parsed-literal:: %Directive(arg = value, ...) { %Sub-directive ... }; A directive may have a number of arguments enclosed in parentheses followed by a number of sub-directives enclosed in braces. Individual arguments and sub-directives may be optional. Arguments may be specified in any order. If no arguments are specified then the parentheses can be omitted. If a directive has only one compulsory argument then its value may be specified after the directive name and instead of the parentheses. Sub-directives may be specified in any order. If no sub-directives are specified then the braces can be omitted. If a directive is used to specify handwritten code then it may not have sub-directives. In this case the format is: .. parsed-literal:: %Directive(arg = value, ...) *code* %End Ordinary C/C++ statements may also have sub-directives. These will also be enclosed in braces. In the following descriptions arguments or blocks are shown in *italics*. Optional arguments are enclosed in [*brackets*]. Some directives are used to specify handwritten code. Handwritten code must not define names that start with the prefix ``sip``. .. directive:: %AccessCode :directive:`%AccessCode` ------------------------ .. parsed-literal:: %AccessCode *code* %End This sub-directive is used in the declaration of an instance of a wrapped class or structure, or a pointer to such an instance. You use it to provide handwritten code that overrides the default behaviour. For example:: class Klass; Klass *klassInstance { %AccessCode // In this contrived example the C++ library we are wrapping // defines klassInstance as Klass ** (which SIP doesn't support) so // we explicitly dereference it. if (klassInstance && *klassInstance) return *klassInstance; // This will get converted to None. return 0; %End }; .. seealso:: :directive:`%GetCode`, :directive:`%SetCode` .. directive:: %AutoPyName :directive:`%AutoPyName` ------------------------ .. parsed-literal:: %AutoPyName(remove_leading = *string*) This is a sub-directive of the :directive:`%Module` directive used to specify a rule for automatically providing Python names for classes, enums, functions, methods, variables and exceptions. The directive may be specified any number of times and each rule will be applied in turn. Rules will not be applied if an item has been given an explicit Python name. ``remove_leading`` is a string that will be removed from the beginning of any C++ or C name. For example:: %Module PyQt5.QtCore { %AutoPyName(remove_leading="Q") } .. directive:: %BIGetBufferCode :directive:`%BIGetBufferCode` ----------------------------- .. parsed-literal:: %BIGetBufferCode *code* %End This directive (along with :directive:`%BIReleaseBufferCode`) is used to specify code that implements Python's buffer interface. The variables that are made available to the handwritten code depend on whether or not the limited Python API is enabled or not. The following variables are made available: sipBufferDef \*sipBuffer When the use of the limited API is enabled, this is a pointer to a structure that should be populated by the code. The ``bd_buffer`` field should be set to the address of the buffer. The ``bd_length`` field should be set to the length of the buffer. The ``bd_readonly`` field should be set to a non-zero value if the buffer is read-only. Py_buffer \*sipBuffer When the use of the limited Python API is disabled, this is a pointer to the Python buffer structure that should be populated by the code. *type* \*sipCpp This is a pointer to the structure or class instance. Its *type* is a pointer to the structure or class. int sipFlags When the use of the limited Python API is disabled, these are the flags that specify what elements of the ``sipBuffer`` structure must be populated. int sipRes The handwritten code should set this to 0 if there was no error or -1 if there was an error. PyObject \*sipSelf This is the Python object that wraps the structure or class instance, i.e. ``self``. .. directive:: %BIReleaseBufferCode :directive:`%BIReleaseBufferCode` --------------------------------- .. parsed-literal:: %BIReleaseBufferCode *code* %End This directive (along with :directive:`%BIGetBufferCode`) is used to specify code that implements Python's buffer interface. The variables that are made available to the handwritten code depend on whether or not the limited Python API is enabled or not. The following variables are made available: Py_buffer \*sipBuffer When the use of the limited Python API is disabled, this is a pointer to the Python buffer structure. *type* \*sipCpp This is a pointer to the structure or class instance. Its *type* is a pointer to the structure or class. PyObject \*sipSelf This is the Python object that wraps the structure or class instance, i.e. ``self``. .. directive:: %CompositeModule :directive:`%CompositeModule` ----------------------------- .. parsed-literal:: %CompositeModule(name = *dotted-name*) { [:directive:`%Docstring`] }; A composite module is one that merges a number of related SIP generated modules. For example, a module that merges the modules ``a_mod``, ``b_mod`` and ``c_mod`` is equivalent to the following pure Python module:: from a_mod import * from b_mod import * from c_mod import * Clearly the individual modules should not define module-level objects with the same name. This directive is used to specify the name of a composite module. Any subsequent :directive:`%Module` directive is interpreted as defining a component module. The optional :directive:`%Docstring` sub-directive is used to specify the module's docstring. For example:: %CompositeModule PyQt5.Qt %Include QtCore/QtCoremod.sip %Include QtGui/QtGuimod.sip The main purpose of a composite module is as a programmer convenience as they don't have to remember which individual module an object is defined in. .. directive:: %ConvertFromTypeCode :directive:`%ConvertFromTypeCode` --------------------------------- .. parsed-literal:: %ConvertFromTypeCode *code* %End This directive is used as part of the :directive:`%MappedType` directive or of a class specification to specify the handwritten code that converts an instance of a C/C++ type to a Python object. If used as part of a class specification then instances of the class will be automatically converted to the Python object, even though the class itself has been wrapped. This behaviour can be changed on a temporary basis from an application by calling the :py:func:`sip.enableautoconversion` function, or from handwritten code by calling the :c:func:`sipEnableAutoconversion` function. The following variables are made available to the handwritten code: *type* \*sipCpp This is a pointer to the C/C++ instance to be converted. It will never be zero as the conversion from zero to ``Py_None`` is handled before the handwritten code is called. PyObject \*sipTransferObj This specifies any desired ownership changes to the returned object. If it is ``NULL`` then the ownership should be left unchanged. If it is ``Py_None`` then ownership should be transferred to Python. Otherwise ownership should be transferred to C/C++ and the returned object associated with *sipTransferObj*. The code can choose to interpret these changes in any way. For example, if the code is converting a C++ container of wrapped classes to a Python list it is likely that the ownership changes should be made to each element of the list. The handwritten code must explicitly return a ``PyObject *``. If there was an error then a Python exception must be raised and ``NULL`` returned. The following example converts a ``QList`` instance to a Python list of ``QWidget`` instances:: %ConvertFromTypeCode PyObject *l; // Create the Python list of the correct length. if ((l = PyList_New(sipCpp->size())) == NULL) return NULL; // Go through each element in the C++ instance and convert it to a // wrapped QWidget. for (int i = 0; i < sipCpp->size(); ++i) { QWidget *w = sipCpp->at(i); PyObject *wobj; // Get the Python wrapper for the QWidget instance, creating a new // one if necessary, and handle any ownership transfer. if ((wobj = sipConvertFromType(w, sipType_QWidget, sipTransferObj)) == NULL) { // There was an error so garbage collect the Python list. Py_DECREF(l); return NULL; } // Add the wrapper to the list. PyList_SET_ITEM(l, i, wobj); } // Return the Python list. return l; %End .. directive:: %ConvertToSubClassCode :directive:`%ConvertToSubClassCode` ----------------------------------- .. parsed-literal:: %ConvertToSubClassCode *code* %End This directive is fully described in :ref:`ref-subclass-convertors`. The following variables are made available to the handwritten code: *type* \*sipCpp This is a pointer to the C++ class instance. It's type is the base class of the convertor. void \*\*sipCppRet When the sub-class is derived from more than one super-class then it is possible that the C++ address of the instance as the sub-class is different to that of the super-class. If so, then this must be set to the C++ address of the instance when cast (usually using ``static_cast``) from the super-class to the sub-class. const sipTypeDef \*sipType The handwritten code must set this to the SIP generated type structure that corresponds to the class instance. (The type structure for class ``Klass`` is ``sipType_Klass``.) If the RTTI of the class instance isn't recognised then ``sipType`` must be set to ``NULL``. The code doesn't have to recognise the exact class, only the most specific sub-class that it can. The code may also set the value to a type that is apparently unrelated to the requested type. If this happens then the whole conversion process is started again using the new type as the requested type. This is typically used to deal with classes that have more than one super-class that are subject to this conversion process. It allows the code for one super-class to switch to the code for another (more appropriate) super-class. The handwritten code must not explicitly return. The following example shows the sub-class conversion code for ``QEvent`` based class hierarchy in PyQt5:: class QEvent { %ConvertToSubClassCode // QEvent sub-classes provide a unique type ID. switch (sipCpp->type()) { case QEvent::Timer: sipType = sipType_QTimerEvent; break; case QEvent::KeyPress: case QEvent::KeyRelease: sipType = sipType_QKeyEvent; break; // Skip the remaining event types to keep the example short. default: // We don't recognise the type. sipType = NULL; } %End // The rest of the class specification. }; .. directive:: %ConvertToTypeCode :directive:`%ConvertToTypeCode` ------------------------------- .. parsed-literal:: %ConvertToTypeCode *code* %End This directive is used to specify the handwritten code that converts a Python object to a C/C++ instance and to handle any ownership transfers. It is used as part of the :directive:`%MappedType` directive and as part of a class specification. The code is also called to determine if the Python object is of the correct type prior to conversion. When used as part of a class specification it can automatically convert additional types of Python object. The following variables are made available to the handwritten code: int \*sipIsErr If this is ``NULL`` then the code is being asked to check the type of the Python object. The check must not have any side effects. Otherwise the code is being asked to convert the Python object and a non-zero value should be returned through this pointer if an error occurred during the conversion. PyObject \*sipPy This is the Python object to be converted. *type* \*\*sipCppPtr This is a pointer through which the address of the mapped type instance (or zero if appropriate) is returned. Its value is undefined if ``sipIsErr`` is ``NULL``. PyObject \*sipTransferObj This specifies any desired ownership changes to *sipPy*. If it is ``NULL`` then the ownership should be left unchanged. If it is ``Py_None`` then ownership should be transferred to Python. Otherwise ownership should be transferred to C/C++ and *sipPy* associated with *sipTransferObj*. The code can choose to interpret these changes in any way. void \*\*sipUserStatePtr This is a pointer through which a mapped type convertor can store additional state information. Any value set will be passed to the code defined by the corresponding :directive:`%ReleaseCode` directive invoked by a call to :c:func:`sipReleaseTypeUS()`. The handwritten code must explicitly return an ``int`` the meaning of which depends on the value of ``sipIsErr``. If ``sipIsErr`` is ``NULL`` then a non-zero value is returned if the Python object has a type that can be converted to the mapped type. Otherwise zero is returned. If ``sipIsErr`` is not ``NULL`` then a combination of the following flags is returned. - :c:macro:`SIP_TEMPORARY` is set to indicate that the returned instance is a temporary and should be released, by calling :c:func:`sipReleaseType()` or :c:func:`sipReleaseTypeUS()`, to avoid a memory leak. - :c:macro:`SIP_DERIVED_CLASS` is set by a class convertor to indicate that the type of the returned instance is a derived class. See :ref:`ref-derived-classes`. - :c:macro:`SIP_USER` is a flag that can be returned by a mapped type convertor. It will be passed to the code defined by a corresponding :directive:`%ReleaseCode` directive. The following example converts a Python list of ``QPoint`` instances to a ``QList`` instance:: %ConvertToTypeCode // See if we are just being asked to check the type of the Python // object. if (!sipIsErr) { // Checking whether or not None has been passed instead of a list // has already been done. Note that we don't check the type of the // individual elements of the list. return PyList_Check(sipPy); } // Create the instance on the heap. QList *ql = new QList; for (int i = 0; i < PyList_GET_SIZE(sipPy); ++i) { QPoint *qp; int state; // Get the address of the element's C++ instance. Note that, in // this case, we don't apply any ownership changes to the list // elements, only to the list itself. qp = reinterpret_cast( sipForceConvertToType(PyList_GET_ITEM(sipPy, i), sipType_QPoint, 0, SIP_NOT_NONE, &state, sipIsErr)); // Deal with any errors. if (*sipIsErr) { sipReleaseType(qp, sipType_QPoint, state); // Tidy up. delete ql; // There is no temporary instance. return 0; } ql->append(*qp); // A copy of the QPoint was appended to the list so we no longer // need it. It may be a temporary instance that should be // destroyed, or a wrapped instance that should not be destroyed. // sipReleaseType() will do the right thing. sipReleaseType(qp, sipType_QPoint, state); } // Return the instance. *sipCppPtr = ql; // The instance should be regarded as temporary (and be destroyed as // soon as it has been used) unless it has been transferred from // Python. sipGetState() is a convenience function that implements // this common transfer behaviour. return sipGetState(sipTransferObj); %End When used in a class specification the handwritten code replaces the code that would normally be automatically generated. This means that the handwritten code must also handle instances of the class itself and not just the additional types that are being supported. This should be done by making calls to :c:func:`sipCanConvertToType()` to check the object type and :c:func:`sipConvertToTypeUS()` or :c:func:`sipConvertToType()` to convert the object. The :c:macro:`SIP_NO_CONVERTORS` flag *must* be passed to these functions to prevent recursive calls to the handwritten code. .. directive:: %Copying :directive:`%Copying` --------------------- .. parsed-literal:: %Copying *text* %End This directive is used to specify some arbitrary text that will be included at the start of all source files generated by SIP. It is normally used to include copyright and licensing terms. For example:: %Copying Copyright (c) 2019 Riverbank Computing Limited %End .. directive:: %DefaultDocstringFormat :directive:`%DefaultDocstringFormat` ------------------------------------ .. parsed-literal:: %DefaultDocstringFormat(name = ["raw" | "deindented"]) This directive is used to specify the default formatting of docstrings, i.e. when the :directive:`%Docstring` directive does not specify an explicit format. See the :directive:`%Docstring` directive for an explanation of the different formats. If the directive is not specified then the default format used is ``"raw"``. For example:: %DefaultDocstringFormat "deindented" .. directive:: %DefaultDocstringSignature :directive:`%DefaultDocstringSignature` --------------------------------------- .. parsed-literal:: %DefaultDocstringSignature(name = ["discarded" | "prepended" | "appended"]) This directive is used to specify the default positioning of signatures in docstrings, i.e. when the :directive:`%Docstring` directive is used but does not specify an explicit positioning. See the :directive:`%Docstring` directive for an explanation of the different ways signatures are positioned. If the directive is not specified then the default positioning is ``"discarded"``. For example:: %DefaultDocstringSignature "prepended" .. directive:: %DefaultEncoding :directive:`%DefaultEncoding` ----------------------------- .. parsed-literal:: %DefaultEncoding(name = ["ASCII" | "Latin-1" | "UTF-8" | "None"]) This directive is used to specify the default encoding used for ``char``, ``const char``, ``char *`` or ``const char *`` values. An encoding of ``"None"`` means that the value is unencoded. The default can be overridden for a particular value using the :aanno:`Encoding` annotation. If the directive is not specified then the default encoding of the last imported module is used, if any. For example:: %DefaultEncoding "Latin-1" .. directive:: %DefaultMetatype :directive:`%DefaultMetatype` ----------------------------- .. parsed-literal:: %DefaultMetatype(name = *dotted-name*) This directive is used to specify the Python type that should be used as the meta-type for any C/C++ data type defined in the same module, and by importing modules, that doesn't have an explicit meta-type. If this is not specified then ``sip.wrappertype`` is used. You can also use the :canno:`Metatype` class annotation to specify the meta-type used by a particular C/C++ type. See the section :ref:`ref-types-metatypes` for more details. For example:: %DefaultMetatype PyQt5.QtCore.pyqtWrapperType .. directive:: %DefaultSupertype :directive:`%DefaultSupertype` ------------------------------ .. parsed-literal:: %DefaultSupertype(name = *dotted-name*) This directive is used to specify the Python type that should be used as the super-type for any C/C++ data type defined in the same module that doesn't have an explicit super-type. If this is not specified then ``sip.wrapper`` is used. You can also use the :canno:`Supertype` class annotation to specify the super-type used by a particular C/C++ type. See the section :ref:`ref-types-metatypes` for more details. For example:: %DefaultSupertype sip.simplewrapper .. directive:: %Docstring :directive:`%Docstring` ----------------------- .. parsed-literal:: %Docstring(format = ["raw" | "deindented"], signature = ["discarded" | "prepended" | "appended"]) *text* %End This directive is used to specify explicit docstrings for modules, classes, functions, methods, typedefs and properties. The docstring of a class is made up of the docstring specified for the class itself, with the docstrings specified for each contructor appended. The docstring of a function or method is made up of the concatenated docstrings specified for each of the overloads. .. note:: Specifying an explicit docstring will mean that SIP will generate less informative exceptions (i.e. without a full signature) when it fails to match a set of arguments to any function or method overload. ``format`` may either be ``"raw"`` or ``"deindented"``. If it is not specified then the value specified by any :directive:`%DefaultDocstringFormat` directive is used. If ``format`` is ``"raw"`` then the docstring is used as it appears in the specification file. If ``format`` is ``"deindented"`` then any leading spaces common to all non-blank lines of the docstring are removed. ``signature`` may either be ``"discarded"``, ``"prepended"`` or ``"appended"``. It is ignored unless applied to the docstring of a class, function or method. If it is not specified then the value specified by any :directive:`%DefaultDocstringSignature` directive is used. If ``signature`` is ``"discarded"`` then the automatically generated function or method signature is discarded. In the context of a class's docstring then this refers to all of the constructors' docstrings. If ``signature`` is ``"prepended"`` then the automatically generated function or method signature is placed before the docstring. In the context of a class's docstring then this refers to all of the constructors' docstrings. If ``signature`` is ``"appended"`` then the automatically generated function or method signature is placed after the docstring. In the context of a class's docstring then this refers to all of the constructors' docstrings. For example:: class Klass { %Docstring This will be at the start of the class's docstring. %End public: Klass(); %Docstring "deindented" This will be appended to the class's docstring and will not be indented. This will be indented by four spaces. %End }; .. seealso:: :directive:`%DefaultDocstringFormat`, :directive:`%DefaultDocstringSignature` .. directive:: %End :directive:`%End` ----------------- This isn't a directive in itself, but is used to terminate a number of directives that allow a block of handwritten code or text to be specified. .. directive:: %Exception :directive:`%Exception` ----------------------- .. parsed-literal:: %Exception *name* [(*base-exception*)] { [:directive:`%TypeHeaderCode`] :directive:`%RaiseCode` }; This directive is used to define new Python exceptions, or to provide a stub for existing Python exceptions. It allows handwritten code to be provided that implements the translation between C++ exceptions and Python exceptions. The arguments to a function's ``throw ()`` specifiers must either be names of classes or the names of Python exceptions defined by this directive. If a ``throw ()`` specifier has no arguments, or the ``noexcept`` specifier is used, then it is assumed that the function never raises an exception. .. note:: ``throw ()`` specifiers are ignored (and should be omitted) when using ABI v13.1 or later and v12.9 or later. *name* is the name of the exception. *base-exception* is the optional base exception. This may be either one of the standard Python exceptions or one defined with a previous :directive:`%Exception` directive. If it is omitted then a new Python exception is not created. The optional :directive:`%TypeHeaderCode` sub-directive is used to specify any external interface to the exception being defined. The :directive:`%RaiseCode` sub-directive is used to specify the handwritten code that converts a reference to the C++ exception to the Python exception. For example:: %Exception std::exception(SIP_Exception) /PyName=StdException/ { %TypeHeaderCode #include %End %RaiseCode const char *detail = sipExceptionRef.what(); SIP_BLOCK_THREADS PyErr_SetString(sipException_std_exception, detail); SIP_UNBLOCK_THREADS %End }; In this example we map the standard C++ exception to a new Python exception. The new exception is called ``StdException`` and is derived from the standard Python exception ``Exception``. An exception may be annotated with :xanno:`Default` to specify that it should be caught by default if there is no ``throw`` clause. In this second example we do not create a new Python exception but instead convert the standard C++ ``std::out_of_range`` exception to the standard Python ``IndexError`` exception:: %Exception std::out_of_range { %TypeHeaderCode #include %End %RaiseCode const char *detail = sipExceptionRef.what(); SIP_BLOCK_THREADS PyErr_SetString(PyExc_IndexError, detail); SIP_UNBLOCK_THREADS %End }; .. directive:: %ExportedHeaderCode :directive:`%ExportedHeaderCode` -------------------------------- .. parsed-literal:: %ExportedHeaderCode *code* %End This directive is used to specify handwritten code, typically the declarations of types, that is placed in a header file that is included by all generated code for all modules. It should not include function declarations because Python modules should not explicitly call functions in another Python module. .. seealso:: :directive:`%ModuleCode`, :directive:`%ModuleHeaderCode` .. directive:: %ExportedTypeHintCode :directive:`%ExportedTypeHintCode` ---------------------------------- .. parsed-literal:: %ExportedTypeHintCode *code* %End This directive is used to specify handwritten code, typically the declarations of types, that is placed in the PEP 484 type hint stub file for any module that imports it. Note that it is not included in the stub file for the module itself. .. seealso:: :directive:`%TypeHintCode` .. directive:: %Extract :directive:`%Extract` --------------------- .. parsed-literal:: %Extract(id = *name* [, order = *integer*]) *text* %End This directive is used to specify part of an extract. An extract is a collection of arbitrary text specified as one or more parts each having the same ``id``. SIP places no interpretation on an identifier, or on the contents of the extract. Extracts may be used for any purpose, e.g. documentation, tests etc. The part's optional ``order`` determines its position relative to the extract's other parts. If the order is not specified then the part is appended to the extract. For example:: %Extract example This will be the last line because there is no explicit order. %End %Extract(id=example, order=20) This will be the second line. %End %Extract(id=example, order=10) This will be the first line. %End .. directive:: %Feature :directive:`%Feature` --------------------- .. parsed-literal:: %Feature(name = *name*) This directive is used to declare a feature. Features (along with :directive:`%Platforms` and :directive:`%Timeline`) are used by the :directive:`%If` directive to control whether or not parts of a specification are processed or ignored. Features are mutually independent of each other - any combination of features may be enabled or disabled. By default all features are enabled. The ``disabled-features`` value of the bindings section in :file:`pyproject.toml` is used to specify any disabled features. If a feature is enabled then SIP will automatically generate a corresponding C preprocessor symbol for use by handwritten code. The symbol is the name of the feature prefixed by ``SIP_FEATURE_``. For example:: %Feature FOO_SUPPORT %If (FOO_SUPPORT) void foo(); %End .. directive:: %FinalisationCode :directive:`%FinalisationCode` ------------------------------ .. parsed-literal:: %FinalisationCode *code* %End This directive is used to specify handwritten code that is executed once the instance of a wrapped class has been created. The handwritten code is passed a dictionary of any remaining keyword arguments. It must explicitly return an integer result which should be ``0`` if there was no error. If an error occurred then ``-1`` should be returned and a Python exception raised. The following variables are made available to the handwritten code: PyObject \*sipSelf This is the Python object that wraps the structure or class instance, i.e. ``self``. *type* \*sipCpp This is a pointer to the structure or class instance. Its *type* is a pointer to the structure or class. PyObject \*sipKwds This is an optional dictionary of unused keyword arguments. It may be ``NULL`` or refer to an empty dictionary. If the handwritten code handles any of the arguments then, if ``sipUnused`` is ``NULL``, those arguments must be removed from the dictionary. If ``sipUnused`` is not ``NULL`` then the ``sipKwds`` dictionary must not be updated. Instead a new dictionary must be created that contains any remaining unused keyword arguments and the address of the new dictionary returned via ``sipUnused``. This rather complicated API ensures that new dictionaries are created only when necessary. PyObject \*\*sipUnused This is an optional pointer to where the handwritten code should save the address of any new dictionary of unused keyword arguments that it creates. If it is ``NULL`` then the handwritten code is allowed to update the ``sipKwds`` dictionary. .. directive:: %GCClearCode :directive:`%GCClearCode` ------------------------- .. parsed-literal:: %GCClearCode *code* %End Python has a cyclic garbage collector which can identify and release unneeded objects even when their reference counts are not zero. If a wrapped C structure or C++ class keeps its own reference to a Python object then, if the garbage collector is to do its job, it needs to provide some handwritten code to traverse and potentially clear those embedded references. See the section `Supporting Cyclic Garbage Collection `__ in the Python documentation for the details. This directive is used to specify the code that clears any embedded references. (See :directive:`%GCTraverseCode` for specifying the code that traverses any embedded references.) The following variables are made available to the handwritten code: *type* \*sipCpp This is a pointer to the structure or class instance. Its *type* is a pointer to the structure or class. int sipRes The handwritten code should set this to the result to be returned. The following simplified example is taken from PyQt5. The ``QCustomEvent`` class allows arbitary data to be attached to the event. In PyQt5 this data is always a Python object and so should be handled by the garbage collector:: %GCClearCode PyObject *obj; // Get the object. obj = reinterpret_cast(sipCpp->data()); // Clear the pointer. sipCpp->setData(0); // Clear the reference. Py_XDECREF(obj); // Report no error. sipRes = 0; %End .. directive:: %GCTraverseCode :directive:`%GCTraverseCode` ---------------------------- .. parsed-literal:: %GCTraverseCode *code* %End This directive is used to specify the code that traverses any embedded references for Python's cyclic garbage collector. (See :directive:`%GCClearCode` for a full explanation.) The following variables are made available to the handwritten code: *type* \*sipCpp This is a pointer to the structure or class instance. Its *type* is a pointer to the structure or class. visitproc sipVisit This is the visit function provided by the garbage collector. void \*sipArg This is the argument to the visit function provided by the garbage collector. int sipRes The handwritten code should set this to the result to be returned. The following simplified example is taken from PyQt5's ``QCustomEvent`` class:: %GCTraverseCode PyObject *obj; // Get the object. obj = reinterpret_cast(sipCpp->data()); // Call the visit function if there was an object. if (obj) sipRes = sipVisit(obj, sipArg); else sipRes = 0; %End .. directive:: %GetCode :directive:`%GetCode` --------------------- .. parsed-literal:: %GetCode *code* %End This sub-directive is used in the declaration of a C++ class variable or C structure member to specify handwritten code to convert it to a Python object. It is usually used to handle types that SIP cannot deal with automatically. The following variables are made available to the handwritten code: *type* \*sipCpp This is a pointer to the structure or class instance. Its *type* is a pointer to the structure or class. It is not made available if the variable being wrapped is a static class variable. PyObject \*sipPy The handwritten code must set this to the Python representation of the class variable or structure member. If there is an error then the code must raise an exception and set this to ``NULL``. PyObject \*sipPyType If the variable being wrapped is a static class variable then this is the Python type object of the class from which the variable was referenced (*not* the class in which it is defined). It may be safely cast to a PyTypeObject \* or a sipWrapperType \*. For example:: struct Entity { /* * In this contrived example the C library we are wrapping actually * defines this as char buffer[100] which SIP cannot handle * automatically. */ char *buffer { %GetCode sipPy = PyString_FromStringAndSize(sipCpp->buffer, 100); %End %SetCode char *ptr; int length; if (PyString_AsStringAndSize(sipPy, &ptr, &length) == -1) { sipErr = 1; } else if (length != 100) { /* * Raise an exception because the length isn't exactly * right. */ PyErr_SetString(PyExc_ValueError, "an Entity.buffer must be exactly 100 bytes"); sipErr = 1; } else { memcpy(sipCpp->buffer, ptr, 100); } %End }; } .. seealso:: :directive:`%AccessCode`, :directive:`%SetCode` .. directive:: %HideNamespace :directive:`%HideNamespace` --------------------------- .. parsed-literal:: %HideNamespace(name = *name*) This directive is used to specify that a C++ namespace, which would normally be wrapped as a Python class, is not wrapped. The contents of the namespace are still wrapped but are placed in the module dictionary. This is usually used when a library being wrapped uses a single namespace that includes everything in the library. In Python the module itself performs the same function as the namespace and so the namespace would just add an unneccessary extra level of indirection. .. directive:: %If :directive:`%If` ---------------- .. parsed-literal:: %If (*expression*) *specification* %End where .. parsed-literal:: *expression* ::= [*ored-qualifiers* | *range*] *ored-qualifiers* ::= [*qualifier* | *qualifier* **||** *ored-qualifiers*] *qualifier* ::= [**!**] [*feature* | *platform*] *range* ::= [*version*] **-** [*version*] This directive is used in conjunction with features (see :directive:`%Feature`), platforms (see :directive:`%Platforms`) and versions (see :directive:`%Timeline`) to control whether or not parts of a specification are processed or not. A *range* of versions means all versions starting with the lower bound up to but excluding the upper bound. If the lower bound is omitted then it is interpreted as being before the earliest version. If the upper bound is omitted then it is interpreted as being after the latest version. For example:: %Feature SUPPORT_FOO %Platforms {WIN32_PLATFORM POSIX_PLATFORM MACOS_PLATFORM} %Timeline {V1_0 V1_1 V2_0 V3_0} %If (!SUPPORT_FOO) // Process this if the SUPPORT_FOO feature is disabled. %End %If (POSIX_PLATFORM || MACOS_PLATFORM) // Process this if either the POSIX_PLATFORM or MACOS_PLATFORM // platforms are enabled. %End %If (V1_0 - V2_0) // Process this if either V1_0 or V1_1 is enabled. %End %If (V2_0 - ) // Process this if either V2_0 or V3_0 is enabled. %End %If (SIP_5_1_0 - ) // SIP v5.1.0 and later will process this. %End %If ( - ) // Always process this. %End Also note that the only way to specify the logical and of qualifiers is to use nested :directive:`%If` directives. .. directive:: %Import :directive:`%Import` -------------------- .. parsed-literal:: %Import(name = *filename*) This directive is used to import the specification of another module. This is needed if the current module makes use of any types defined in the imported module, e.g. as an argument to a function, or to sub-class. If ``name`` cannot be opened then SIP prepends ``name`` with the name of the directory containing the current specification file (i.e. the one containing the :directive:`%Import` directive) and tries again. If this also fails then SIP prepends ``name`` with each of the directories, in turn, specified by the ``sip-include-dirs`` value of the project section in :file:`pyproject.toml`. Directory separators must always be ``/``. For example:: %Import QtCore/QtCoremod.sip .. directive:: %Include :directive:`%Include` --------------------- .. parsed-literal:: %Include(name = *filename* [, optional = [True | False]]) This directive is used to include contents of another file as part of the specification of the current module. It is the equivalent of the C preprocessor's ``#include`` directive and is used to structure a large module specification into manageable pieces. :directive:`%Include` follows the same search process as the :directive:`%Import` directive when trying to open ``name``. if ``optional`` is set then SIP will silently continue processing if the file could not be opened. Directory separators must always be ``/``. For example:: %Include qwidget.sip .. directive:: %InitialisationCode :directive:`%InitialisationCode` -------------------------------- .. parsed-literal:: %InitialisationCode *code* %End This directive is used to specify handwritten code that is embedded in-line in the generated module initialisation code after the SIP module has been imported but before the module itself has been initialised. It is typically used to call :c:func:`sipRegisterPyType()`. For example:: %InitialisationCode // The code will be executed when the module is first imported, after // the SIP module has been imported, but before other module-specific // initialisation has been completed. %End .. directive:: %InstanceCode :directive:`%InstanceCode` -------------------------- .. parsed-literal:: %InstanceCode *code* %End There are a number of circumstances where SIP needs to create an instance of a C++ class but may not be able to do so. For example the C++ class may be abstract or may not have an argumentless public constructor. This directive is used in the definition of a class or mapped type to specify handwritten code to create an instance of the C++ class. For example, if the C++ class is abstract, then the handwritten code may return an instance of a concrete sub-class. The following variable is made available to the handwritten code: *type* \*sipCpp This must be set by the handwritten code to the address of an instance of the C++ class. It doesn't matter if the instance is on the heap or not as it will never be explicitly destroyed. .. directive:: %License :directive:`%License` --------------------- .. parsed-literal:: %License(type = *string* [, licensee = *string*] [, signature = *string*] [, timestamp = *string*]) This directive is used to specify the contents of an optional license dictionary. The license dictionary is called :data:`__license__` and is stored in the module dictionary. ``type`` is the type of the license and its value in the license dictionary is accessed using the ``"Type"`` key. No restrictions are placed on the value. ``licensee`` is the optional name of the licensee and its value in the license dictionary is accessed using the ``"Licensee"`` key. No restrictions are placed on the value. ``signature`` is the license's optional signature and its value in the license dictionary is accessed using the ``"Signature"`` key. No restrictions are placed on the value. ``timestamp`` is the license's optional timestamp and its value in the license dictionary is accessed using the ``"Timestamp"`` key. No restrictions are placed on the value. Note that this directive isn't an attempt to impose any licensing restrictions on a module. It is simply a method for easily embedding licensing information in a module so that it is accessible to Python scripts. For example:: %License "GPL" .. directive:: %MappedType :directive:`%MappedType` ------------------------ .. parsed-literal:: template<*type-list*> %MappedType *base-type* { [:directive:`%TypeCode`] [:directive:`%TypeHeaderCode`] [:directive:`%ConvertToTypeCode`] [:directive:`%ConvertFromTypeCode`] [:directive:`%ReleaseCode`] }; %MappedType *base-type* { [:directive:`%TypeCode`] [:directive:`%TypeHeaderCode`] [:directive:`%ConvertToTypeCode`] [:directive:`%ConvertFromTypeCode`] [:directive:`%ReleaseCode`] }; This directive is used to define an automatic mapping between a C or C++ type and a Python type. It can be used as part of a template, or to map a specific type. When used as part of a template *type* cannot itself refer to a template. Any occurrences of any of the type names (but not any ``*`` or ``&``) in *type-list* will be replaced by the actual type names used when the template is instantiated. Template mapped types are instantiated automatically as required (unlike template classes which are only instantiated using ``typedef``). Any explicit mapped type will be used in preference to any template that maps the same type, ie. a template will not be automatically instantiated if there is an explicit mapped type. The optional :directive:`%TypeCode` sub-directive is used to specify additional code, typically utility functions, that can be called from other handwritten code. The optional :directive:`%TypeHeaderCode` sub-directive is used to specify the library interface to the type being mapped. The optional :directive:`%ConvertToTypeCode` sub-directive is used to specify the handwritten code that converts a Python object to an instance of the mapped type. The optional :directive:`%ConvertFromTypeCode` sub-directive is used to specify the handwritten code that converts an instance of the mapped type to a Python object. The optional :directive:`%ReleaseCode` sub-directive is used to specify the handwritten code that releases an instance of the mapped type to the heap. For example:: template %MappedType QList { %TypeHeaderCode // Include the library interface to the type being mapped. #include %End %ConvertToTypeCode // See if we are just being asked to check the type of the Python // object. if (sipIsErr == NULL) { // Check it is a list. if (!PyList_Check(sipPy)) return 0; // Now check each element of the list is of the type we expect. // The template is for a pointer type so we don't disallow None. for (int i = 0; i < PyList_GET_SIZE(sipPy); ++i) if (!sipCanConvertToType(PyList_GET_ITEM(sipPy, i), sipType_Type, 0)) return 0; return 1; } // Create the instance on the heap. QList *ql = new QList; for (int i = 0; i < PyList_GET_SIZE(sipPy); ++i) { // Use the SIP API to convert the Python object to the // corresponding C++ instance. Note that we apply any ownership // transfer to the list itself, not the individual elements. Type *t = reinterpret_cast(sipConvertToType( PyList_GET_ITEM(sipPy, i), sipType_Type, 0, 0, 0, sipIsErr)); if (*sipIsErr) { // Tidy up. delete ql; // There is nothing on the heap. return 0; } // Add the pointer to the C++ instance. ql->append(t); } // Return the instance on the heap. *sipCppPtr = ql; // Apply the normal transfer. return sipGetState(sipTransferObj); %End %ConvertFromTypeCode PyObject *l; // Create the Python list of the correct length. if ((l = PyList_New(sipCpp->size())) == NULL) return NULL; // Go through each element in the C++ instance and convert it to the // corresponding Python object. for (int i = 0; i < sipCpp->size(); ++i) { Type *t = sipCpp->at(i); PyObject *tobj; if ((tobj = sipConvertFromType(t, sipType_Type, sipTransferObj)) == NULL) { // There was an error so garbage collect the Python list. Py_DECREF(l); return NULL; } PyList_SET_ITEM(l, i, tobj); } // Return the Python list. return l; %End }; Using this we can use, for example, ``QList`` throughout the module's specification files (and in any module that imports this one). The generated code will automatically map this to and from a Python list of QObject instances when appropriate. .. directive:: %MethodCode :directive:`%MethodCode` ------------------------ .. parsed-literal:: %MethodCode *code* %End This directive is used as part of the specification of a global function, class method, operator, constructor or destructor to specify handwritten code that replaces the normally generated call to the function being wrapped. It is usually used to handle argument types and results that SIP cannot deal with automatically. Normally the specified code is embedded in-line after the function's arguments have been successfully converted from Python objects to their C or C++ equivalents. In this case the specified code must not include any ``return`` statements. However if the :fanno:`NoArgParser` annotation has been used then the specified code is also responsible for parsing the arguments. No other code is generated by SIP and the specified code must include a ``return`` statement. In the context of a destructor the specified code is embedded in-line in the Python type's deallocation function. Unlike other contexts it supplements rather than replaces the normally generated code, so it must not include code to return the C structure or C++ class instance to the heap. The code is only called if ownership of the structure or class is with Python. The specified code must also handle the Python Global Interpreter Lock (GIL). If compatibility with SIP v3.x is required then the GIL must be released immediately before the C++ call and reacquired immediately afterwards as shown in this example fragment:: Py_BEGIN_ALLOW_THREADS sipCpp->foo(); Py_END_ALLOW_THREADS If compatibility with SIP v3.x is not required then this is optional but should be done if the C++ function might block the current thread or take a significant amount of time to execute. (See :ref:`ref-gil` and the :fanno:`ReleaseGIL` and :fanno:`HoldGIL` annotations.) If the :fanno:`NoArgParser` annotation has not been used then the following variables are made available to the handwritten code: *type* a0 There is a variable for each argument of the Python signature (excluding any ``self`` argument) named ``a0``, ``a1``, etc. If ``use_argument_names`` has been set in the :directive:`%Module` directive then the name of the argument is the real name. The *type* of the variable is the same as the type defined in the specification with the following exceptions: - if the argument is only used to return a value (e.g. it is an ``int *`` without an :aanno:`In` annotation) then the type has one less level of indirection (e.g. it will be an ``int``) - if the argument is a structure or class (or a reference or a pointer to a structure or class) then *type* will always be a pointer to the structure or class. Note that handwritten code for destructors never has any arguments. PyObject \*a0Wrapper This variable is made available only if the :aanno:`GetWrapper` annotation is specified for the corresponding argument. The variable is a pointer to the Python object that wraps the argument. If ``use_argument_names`` has been set in the :directive:`%Module` directive then the name of the variable is the real name of the argument with ``Wrapper`` appended. *type* \*sipCpp If the directive is used in the context of a class constructor then this must be set by the handwritten code to the constructed instance. If it is set to ``0`` and no Python exception is raised then SIP will continue to try other Python signatures. If the directive is used in the context of a method (but not the standard binary operator methods, e.g. :meth:`__add__`) or a destructor then this is a pointer to the C structure or C++ class instance. Its *type* is a pointer to the structure or class. Standard binary operator methods follow the same convention as global functions and instead define two arguments called ``a0`` and ``a1``. sipErrorState sipError The handwritten code should set this to either ``sipErrorContinue`` or ``sipErrorFail``, and raise an appropriate Python exception, if an error is detected. Its initial value will be ``sipErrorNone``. When ``sipErrorContinue`` is used, SIP will remember the exception as the reason why the particular overloaded callable could not be invoked. It will then continue to try the next overloaded callable. It is typically used by code that needs to do additional type checking of the callable's arguments. When ``sipErrorFail`` is used, SIP will report the exception immediately and will not attempt to invoke other overloaded callables. ``sipError`` is not provided for destructors. int sipIsErr The handwritten code should set this to a non-zero value, and raise an appropriate Python exception, if an error is detected. This is the equivalent of setting ``sipError`` to ``sipErrorFail``. Its initial value will be ``0``. ``sipIsErr`` is not provided for destructors. *type* sipRes The handwritten code should set this to the result to be returned. The *type* of the variable is the same as the type defined in the Python signature in the specification with the following exception: - if the argument is a structure or class (or a reference or a pointer to a structure or class) then *type* will always be a pointer to the structure or class. ``sipRes`` is not provided for inplace operators (e.g. ``+=`` or :meth:`__imul__`) as their results are handled automatically, nor for class constructors or destructors. PyObject \*sipSelf If the directive is used in the context of a class constructor, destructor or non-static method then this is the Python object that wraps the structure or class instance, i.e. ``self``. Starting with ABI v13.0, in the context of a static method, this will contain the Python type object of the class. bool sipSelfWasArg This is only made available for non-abstract, virtual methods. It is set if ``self`` was explicitly passed as the first argument of the method rather than being bound to the method. In other words, the call was:: Klass.foo(self, ...) rather than:: self.foo(...) If the :fanno:`NoArgParser` annotation has been used then only the following variables are made available to the handwritten code: PyObject \*sipArgs This is the tuple of arguments. PyObject \*sipKwds This is the dictionary of keyword arguments. The following is a complete example:: class Klass { public: virtual int foo(SIP_PYTUPLE); %MethodCode // The C++ API takes a 2 element array of integers but passing a // two element tuple is more Pythonic. int iarr[2]; if (PyArg_ParseTuple(a0, "ii", &iarr[0], &iarr[1])) { Py_BEGIN_ALLOW_THREADS sipRes = sipSelfWasArg ? sipCpp->Klass::foo(iarr) : sipCpp->foo(iarr); Py_END_ALLOW_THREADS } else { // PyArg_ParseTuple() will have raised the exception. sipIsErr = 1; } %End }; As the example is a virtual method [#]_, note the use of ``sipSelfWasArg`` to determine exactly which implementation of ``foo()`` to call. If a method is in the ``protected`` section of a C++ class then SIP generates helpers that provide access to method. However, these are not available if the Python module is being built with ``protected`` redefined as ``public``. The following pattern should be used to cover all possibilities:: #if defined(SIP_PROTECTED_IS_PUBLIC) sipRes = sipSelfWasArg ? sipCpp->Klass::foo(iarr) : sipCpp->foo(iarr); #else sipRes = sipCpp->sipProtectVirt_foo(sipSelfWasArg, iarr); #endif If a method is in the ``protected`` section of a C++ class but is not virtual then the pattern should instead be:: #if defined(SIP_PROTECTED_IS_PUBLIC) sipRes = sipCpp->foo(iarr); #else sipRes = sipCpp->sipProtect_foo(iarr); #endif .. [#] See :directive:`%VirtualCatcherCode` for a description of how SIP generated code handles the reimplementation of C++ virtual methods in Python. .. directive:: %Module :directive:`%Module` -------------------- .. parsed-literal:: %Module(name = *dotted-name* [, all_raise_py_exception = [True | False]] [, call_super_init = [True | False]] [, default_VirtualErrorHandler = *name*] [, keyword_arguments = ["None" | "All" | "Optional"]] [, language = *string*] [, py_ssize_t_clean = [True | False]] [, use_argument_names = [True | False]] [, use_limited_api = [True | False]]) { [:directive:`%AutoPyName`] [:directive:`%Docstring`] }; This directive is used to specify the name of a module and a number of other attributes. ``name`` may contain periods to specify that the module is part of a Python package. ``all_raise_py_exception`` specifies that all constructors, functions and methods defined in the module raise a Python exception to indicate that an error occurred. It is the equivalent of using the :fanno:`RaisesPyException` function annotation on every constructor, function and method. ``call_super_init`` specifies that the ``__init__()`` method of a wrapped class should automatically call it's super-class's ``__init__()`` method passing a dictionary of any unused keyword arguments. In other words, wrapped classes support cooperative multi-inheritance. This means that sub-classes, and any mixin classes, should always use call ``super().__init__()`` and not call any super-class's ``__init__()`` method explicitly. ``default_VirtualErrorHandler`` specifies the handler (defined by the :directive:`%VirtualErrorHandler` directive) that is called when a Python re-implementation of any virtual C++ function raises a Python exception. If no handler is specified for a virtual C++ function then ``PyErr_Print()`` is called. ``keyword_arguments`` specifies the default level of support for Python keyword arguments. See the :fanno:`KeywordArgs` annotation for an explaination of the possible values and their effect. ``language`` specifies the implementation language of the library being wrapped. Its value is either ``"C++"`` (the default) or ``"C"``. ``py_ssize_t_clean`` specifies that the generated code should include ``#define PY_SSIZE_T_CLEAN`` before any ``#include ``. When providing handwritten code as part of either the :directive:`%MethodCode` or :directive:`%VirtualCatcherCode` directives the names of the arguments of the function or method are based on the number of the argument, i.e. the first argument is named ``a0``, the second ``a1`` and so on. ``use_argument_names`` is set to specify that the real name of the argument, if any, should be used instead. It also affects the name of the variable created when the :aanno:`GetWrapper` argument annotation is used. ``use_limited_api`` specifies that the generated code will only use the limited Python API defined in `PEP 384 `__. It also ensures that the C preprocessor symbol ``Py_LIMITED_API`` is defined before the :file:`Python.h` header file is included. Python extensions built in this way are independent of the version of Python being used. The optional :directive:`%AutoPyName` sub-directive is used to specify a rule for automatically providing Python names. The optional :directive:`%Docstring` sub-directive is used to specify the module's docstring. For example:: %Module PyQt5.QtCore .. directive:: %ModuleCode :directive:`%ModuleCode` ------------------------ .. parsed-literal:: %ModuleCode *code* %End This directive is used to specify handwritten code, typically the implementations of utility functions, that can be called by other handwritten code in the module. For example:: %ModuleCode // Print an object on stderr for debugging purposes. void dump_object(PyObject *o) { PyObject_Print(o, stderr, 0); fprintf(stderr, "\n"); } %End .. seealso:: :directive:`%ExportedHeaderCode`, :directive:`%ModuleHeaderCode` .. directive:: %ModuleHeaderCode :directive:`%ModuleHeaderCode` ------------------------------ .. parsed-literal:: %ModuleHeaderCode *code* %End This directive is used to specify handwritten code, typically the declarations of utility functions, that is placed in a header file that is included by all generated code for the same module. For example:: %ModuleHeaderCode void dump_object(PyObject *o); %End .. seealso:: :directive:`%ExportedHeaderCode`, :directive:`%ModuleCode` .. directive:: %PickleCode :directive:`%PickleCode` ------------------------ .. parsed-literal:: %PickleCode *code* %End This directive is used to specify handwritten code to pickle a C structure or C++ class instance. The following variables are made available to the handwritten code: *type* \*sipCpp This is a pointer to the structure or class instance. Its *type* is a pointer to the structure or class. PyObject \*sipRes The handwritten code must set this to a tuple of the arguments that will be passed to the type's ``__init__()`` method when the structure or class instance is unpickled. If there is an error then the code must raise an exception and set this to ``NULL``. For example:: class Point { Point(int x, y); int x() const; int y() const; %PickleCode sipRes = Py_BuildValue("ii", sipCpp->x(), sipCpp->y()); %End } Note that SIP works around the Python limitation that prevents nested types being pickled. Both named and unnamed enums can be pickled automatically without providing any handwritten code. .. directive:: %Platforms :directive:`%Platforms` ----------------------- .. parsed-literal:: %Platforms {*name* *name* ...} This directive is used to declare a set of platforms. Platforms (along with :directive:`%Feature` and :directive:`%Timeline`) are used by the :directive:`%If` directive to control whether or not parts of a specification are processed or ignored. Platforms are mutually exclusive - only one platform can be enabled at a time. By default all platforms are disabled. The ``tags`` value of the bindings section in :file:`pyproject.toml` is used to enable a platform. If a platform is enabled then SIP will automatically generate a corresponding C preprocessor symbol for use by handwritten code. The symbol is the name of the platform prefixed by ``SIP_PLATFORM_``. For example:: %Platforms {WIN32_PLATFORM POSIX_PLATFORM MACOS_PLATFORM} %If (WIN32_PLATFORM) void undocumented(); %End %If (POSIX_PLATFORM) void documented(); %End .. directive:: %PostInitialisationCode :directive:`%PostInitialisationCode` ------------------------------------ .. parsed-literal:: %PostInitialisationCode *code* %End This directive is used to specify handwritten code that is embedded in-line at the very end of the generated module initialisation code. The following variables are made available to the handwritten code: PyObject \*sipModule This is the module object returned by ``Py_InitModule()``. PyObject \*sipModuleDict This is the module's dictionary object returned by ``Py_ModuleGetDict()``. For example:: %PostInitialisationCode // The code will be executed when the module is first imported and // after all other initialisation has been completed. %End .. directive:: %PreInitialisationCode :directive:`%PreInitialisationCode` ----------------------------------- .. parsed-literal:: %PreInitialisationCode *code* %End This directive is used to specify handwritten code that is embedded in-line at the very start of the generated module initialisation code. For example:: %PreInitialisationCode // The code will be executed when the module is first imported and // before other initialisation has been completed. %End .. directive:: %Property :directive:`%Property` ---------------------- .. parsed-literal:: %Property(name = *name*, get = *name* [, set = *name*]) { [:directive:`%Docstring`] }; This directive is used to define a Python property. ``name`` is the name of the property. ``get`` is the Python name of the getter method and must refer to a method in the same class. ``set`` is the Python name of the optional setter method and must refer to a method in the same class. The optional :directive:`%Docstring` sub-directive is used to specify the property's docstring. For example:: class Klass { public: int get_count() const; void set_count(); %Property(name=count, get=get_count, set=set_count) }; .. directive:: %PreMethodCode :directive:`%PreMethodCode` --------------------------- .. parsed-literal:: %PreMethodCode *code* %End This directive is used as part of the specification of a global function, class method, operator, constructor or destructor to specify handwritten code that is inserted before the default code for calling the wrapped function, or before the :directive:`%MethodCode` directive if it is also given. .. directive:: %RaiseCode :directive:`%RaiseCode` ----------------------- .. parsed-literal:: %RaiseCode *code* %End This directive is used as part of the definition of an exception using the :directive:`%Exception` directive to specify handwritten code that raises a Python exception when a C++ exception has been caught. The code is embedded in-line as the body of a C++ ``catch ()`` clause. The specified code must handle the Python Global Interpreter Lock (GIL) if necessary. The GIL must be acquired before any calls to the Python API and released after the last call as shown in this example fragment:: SIP_BLOCK_THREADS PyErr_SetNone(PyErr_Exception); SIP_UNBLOCK_THREADS Finally, the specified code must not include any ``return`` statements. The following variable is made available to the handwritten code: *type* &sipExceptionRef This is a reference to the caught C++ exception. See the :directive:`%Exception` directive for an example. .. directive:: %ReleaseCode :directive:`%ReleaseCode` ------------------------- .. parsed-literal:: %ReleaseCode *code* %End This directive is used to specify handwritten code that releases a C/C++ instance to the heap. By default a C instance is released by calling :c:func:`sipFree()` and a C++ instance is released by calling its destructor. It is specified as part of the :directive:`%MappedType` directive. The following variables are made available to the handwritten code: *type* \*sipCpp This is a pointer to the C/C++ instance. int sipState This is the value of the :c:macro:`SIP_USER` returned by the code specified by the corresponding :directive:`%ConvertToTypeCode` directive. void \*sipUserState This is the value of the pointer saved by the code specified by the corresponding :directive:`%ConvertToTypeCode` directive. .. directive:: %SetCode :directive:`%SetCode` --------------------- .. parsed-literal:: %SetCode *code* %End This sub-directive is used in the declaration of a C++ class variable or C structure member to specify handwritten code to convert it from a Python object. It is usually used to handle types that SIP cannot deal with automatically. The following variables are made available to the handwritten code: *type* \*sipCpp This is a pointer to the structure or class instance. Its *type* is a pointer to the structure or class. It is not made available if the variable being wrapped is a static class variable. int sipErr If the conversion failed then the handwritten code should raise a Python exception and set this to a non-zero value. Its initial value will be automatically set to zero. PyObject \*sipPy This is the Python object that the handwritten code should convert. PyObject \*sipPyType If the variable being wrapped is a static class variable then this is the Python type object of the class from which the variable was referenced (*not* the class in which it is defined). It may be safely cast to a PyTypeObject \* or a sipWrapperType \*. .. seealso:: :directive:`%AccessCode`, :directive:`%GetCode` .. directive:: %Timeline :directive:`%Timeline` ---------------------- .. parsed-literal:: %Timeline {*name* *name* ...} This directive is used to declare a set of versions released over a period of time. Versions (along with :directive:`%Feature` and :directive:`%Platforms`) are used by the :directive:`%If` directive to control whether or not parts of a specification are processed or ignored. Versions are mutually exclusive - only one version can be enabled at a time. The ``tags`` value of the bindings section in :file:`pyproject.toml` is used to enable a version. If a timeline does not have a version explicitly enabled then the latest version will be enabled automatically. The :directive:`%Timeline` directive can be used any number of times in a module to allow multiple libraries to be wrapped in the same module. SIP automatically defines a timeline containing all versions of SIP. The name of the version is ``SIP_`` followed by the individual parts of the version number separated by an underscore. SIP v5.0.1 is therefore ``SIP_5_0_1``. If a particular version is enabled then SIP will automatically generate a corresponding C preprocessor symbol for use by handwritten code. The symbol is the name of the version prefixed by ``SIP_TIMELINE_``. For example:: %Timeline {V1_0 V1_1 V2_0 V3_0} %If (V1_0 - V2_0) void foo(); %End %If (V2_0 -) void foo(int = 0); %End %If (- SIP_4_13) void bar(); %End .. directive:: %TypeCode :directive:`%TypeCode` ---------------------- .. parsed-literal:: %TypeCode *code* %End This directive is used as part of the specification of a C structure, a C++ class or a :directive:`%MappedType` directive to specify handwritten code, typically the implementations of utility functions, that can be called by other handwritten code in the structure or class. For example:: class Klass { %TypeCode // Print an instance on stderr for debugging purposes. static void dump_klass(const Klass *k) { fprintf(stderr,"Klass %s at %p\n", k->name(), k); } %End // The rest of the class specification. }; Because the scope of the code is normally within the generated file that implements the type, any utility functions would normally be declared ``static``. However a naming convention should still be adopted to prevent clashes of function names within a module in case the SIP ``-j`` command line option is used. .. directive:: %TypeHeaderCode :directive:`%TypeHeaderCode` ---------------------------- .. parsed-literal:: %TypeHeaderCode *code* %End This directive is used to specify handwritten code that defines the interface to a C or C++ type being wrapped, either a structure, a class, or a template. It is used within a class definition or a :directive:`%MappedType` directive. Normally *code* will be a pre-processor ``#include`` statement. For example:: // Wrap the Klass class. class Klass { %TypeHeaderCode #include %End // The rest of the class specification. }; .. directive:: %TypeHintCode :directive:`%TypeHintCode` -------------------------- .. parsed-literal:: %TypeHintCode *code* %End This directive is used, in the context of a module or an individual class, to specify handwritten code, typically the import of additional modules, that is placed in the PEP 484 type hint stub file for the module or class. .. seealso:: :directive:`%ExportedTypeHintCode` .. directive:: %UnitCode :directive:`%UnitCode` ---------------------- .. parsed-literal:: %UnitCode *code* %End This directive is used to specify handwritten code that is included at the very start of a generated compilation unit (ie. C or C++ source file). It is typically used to ``#include`` a C++ precompiled header file. .. directive:: %UnitPostIncludeCode :directive:`%UnitPostIncludeCode` --------------------------------- .. parsed-literal:: %UnitPostIncludeCode *code* %End This directive is used to specify handwritten code that is included following the ``#include`` of all header files in a generated compilation unit (ie. C or C++ source file). .. directive:: %VirtualCallCode :directive:`%VirtualCallCode` ----------------------------- .. parsed-literal:: %VirtualCallCode *code* %End For most classes there are corresponding :ref:`generated derived classes ` that contain reimplementations of the class's virtual methods. These methods (which SIP calls catchers) determine if there is a corresponding Python reimplementation and call it if so. If there is no Python reimplementation then the method in the original class is called instead. This directive is used to specify handwritten code that replaces the normally generated call to the original class method if there is no Python reimplementation. The following variables are made available to the handwritten code in the context of a method: *type* a0 There is a variable for each argument of the C++ signature named ``a0``, ``a1``, etc. If ``use_argument_names`` has been set in the :directive:`%Module` directive then the name of the argument is the real name. The *type* of the variable is the same as the type defined in the specification. *type* sipRes The handwritten code should set this to any result to be returned. The *type* of the variable is the same as the type defined in the C++ signature in the specification. .. directive:: %VirtualCatcherCode :directive:`%VirtualCatcherCode` -------------------------------- .. parsed-literal:: %VirtualCatcherCode *code* %End This directive is used to specify handwritten code that replaces the normally generated call to the Python reimplementation of a virtual method and the handling of any returned results. It is usually used to handle argument types and results that SIP cannot deal with automatically. This directive can also be used in the context of a class destructor to specify handwritten code that is embedded in-line in the internal derived class's destructor. In the context of a method the Python Global Interpreter Lock (GIL) is automatically acquired before the specified code is executed and automatically released afterwards. In the context of a destructor the specified code must handle the GIL. The GIL must be acquired before any calls to the Python API and released after the last call as shown in this example fragment:: SIP_BLOCK_THREADS Py_DECREF(obj); SIP_UNBLOCK_THREADS The following variables are made available to the handwritten code in the context of a method: *type* a0 There is a variable for each argument of the C++ signature named ``a0``, ``a1``, etc. If ``use_argument_names`` has been set in the :directive:`%Module` directive then the name of the argument is the real name. The *type* of the variable is the same as the type defined in the specification. int a0Key There is a variable for each argument of the C++ signature that has a type where it is important to ensure that the corresponding Python object is not garbage collected too soon. This only applies to output arguments that return ``'\0'`` terminated strings. The variable would normally be passed to :c:func:`sipParseResult()` using either the ``A`` or ``B`` format characters. If ``use_argument_names`` has been set in the :directive:`%Module` directive then the name of the variable is the real name of the argument with ``Key`` appended. int sipIsErr The handwritten code should set this to a non-zero value, and raise an appropriate Python exception, if an error is detected. PyObject \*sipMethod This object is the Python reimplementation of the virtual C++ method. It is normally passed to :c:func:`sipCallMethod()`. *type* sipRes The handwritten code should set this to any result to be returned. The *type* of the variable is the same as the type defined in the C++ signature in the specification. int sipResKey This variable is only made available if the result has a type where it is important to ensure that the corresponding Python object is not garbage collected too soon. This only applies to ``'\0'`` terminated strings. The variable would normally be passed to :c:func:`sipParseResult()` using either the ``A`` or ``B`` format characters. sipSimpleWrapper \*sipPySelf This variable is only made available if either the ``a0Key`` or ``sipResKey`` are made available. It defines the context within which keys are unique. The variable would normally be passed to :c:func:`sipParseResult()` using the ``S`` format character. No variables are made available in the context of a destructor. For example:: class Klass { public: virtual int foo(SIP_PYTUPLE) [int (int *)]; %MethodCode // The C++ API takes a 2 element array of integers but passing a // two element tuple is more Pythonic. int iarr[2]; if (PyArg_ParseTuple(a0, "ii", &iarr[0], &iarr[1])) { Py_BEGIN_ALLOW_THREADS sipRes = sipCpp->Klass::foo(iarr); Py_END_ALLOW_THREADS } else { // PyArg_ParseTuple() will have raised the exception. sipIsErr = 1; } %End %VirtualCatcherCode // Convert the 2 element array of integers to the two element // tuple. PyObject *result; result = sipCallMethod(&sipIsErr, sipMethod, "ii", a0[0], a0[1]); if (result != NULL) { // Convert the result to the C++ type. sipParseResult(&sipIsErr, sipMethod, result, "i", &sipRes); Py_DECREF(result); } %End }; .. directive:: %VirtualErrorHandler :directive:`%VirtualErrorHandler` --------------------------------- .. parsed-literal:: %VirtualErrorHandler(name = *name*) *code* %End This directive is used to define the handwritten code that implements a handler that is called when a Python re-implementation of a virtual C++ function raises a Python exception. If a virtual C++ function does not have a handler the ``PyErr_Print()`` function is called. The handler is called after all tidying up has been completed, with the Python Global Interpreter Lock (GIL) held and from the thread that raised the exception. If the handler wants to change the execution path by, for example, throwing a C++ exception, it must first release the GIL by calling :c:func:`SIP_RELEASE_GIL`. It must not call :c:func:`SIP_RELEASE_GIL` if the execution path is not changed. The following variables are made available to the handwritten code: sipSimpleWrapper \*sipPySelf This is the class instance containing the Python reimplementation. sip_gilstate_t sipGILState This is an opaque value that must be passed to :c:func:`SIP_RELEASE_GIL` in order to release the GIL prior to changing the execution path. For example:: %VirtualErrorHandler my_handler PyObject *exception, *value, *traceback; PyErr_Fetch(&exception, &value, &traceback); SIP_RELEASE_GIL(sipGILState); throw my_exception(sipPySelf, exception, value, traceback); %End .. seealso:: :fanno:`NoVirtualErrorHandler`, :fanno:`VirtualErrorHandler`, :canno:`VirtualErrorHandler` sip-6.8.6/docs/examples.rst000066400000000000000000000352361464421045000156370ustar00rootroot00000000000000Examples ======== In this section we walk through two simple examples, one a standalone project and the other a pair of package projects. These will introduce the basic features of SIP. Other sections of this documentation will contain complete descriptions of all available features. A Standalone Project -------------------- This project implements a module called :mod:`fib` that contains a function :func:`fib_n` which takes a single integer argument ``n`` and returns the n'th value of the Fibonacci series. Note that the example does not wrap a separate C/C++ library that implements :c:func:`fib_n`. Instead it provides the C implementation within the :file:`.sip` specification file itself. While this is not the way SIP is normally used it means that the example is entirely self contained. Later in this section we will describe the changes to the project that would be needed if a separate library was being wrapped. First of all is the project's :file:`pyproject.toml` file (downloadable from :download:`here <../examples/standalone/pyproject.toml>`) which we show in its entirety below. .. literalinclude:: ../examples/standalone/pyproject.toml The file is in `TOML `__ format and the structure is defined in `PEP 518 `__. The ``[build-system]`` section is used by build frontends to determine what version of what build backend is to be used. :program:`pip`, for example, will download, install and invoke an appropriate version automatically. The ``[project]`` section specified the name of the project (as it would appear on PyPI). This is the minimum information needed to build a standalone project. Next is the module's :file:`.sip` specification file (downloadable from :download:`here <../examples/standalone/fib.sip>`) which we also show in its entirety below. .. literalinclude:: ../examples/standalone/fib.sip The first line of interest is the :directive:`%Module` directive. This defines the name of the extension module that will be created. In the case of standalone projects this would normally be the same as the name defined in the :file:`pyproject.toml` file. It also specifies that the code being wrapped is implemented in C (as opposed to C++). The next line of interest is the declaration of the :c:func:`fib_n` function to be wrapped. The remainder of the file is the :directive:`%MethodCode` directive attached to the function declaration. This is used to provide the actual implementation of the :c:func:`fib_n` function and would not be needed if we were wrapping a separate library. In this code ``a0`` is the value of the first argument passed to the function and converted from a Python ``int`` object, and ``sipRes`` is the value that will be converted to a Python ``int`` object and returned by the function. The project may be built and installed by running:: sip-install It may also be built and installed by running:: pip install . An sdist (to be installed by :program:`pip`) may be created for the project by running:: sip-sdist A wheel (to be installed by :program:`pip`) may be created for the project by running:: sip-wheel An installed project may be uninstalled by running:: pip uninstall fib Using a Real Library .................... If there was a real ``fib`` library to be wrapped, with a corresponding :file:`fib.h` header file, then the :file:`pyproject.toml` file would look more like that shown below. .. parsed-literal:: # Specify sip v6 as the build system for the package. [build-system] requires = ["sip >=6, <7"] build-backend = "sipbuild.api" # Specify the PEP 621 metadata for the project. [project] name = "fib" # Configure the building of the fib bindings. [tool.sip.bindings.fib] headers = ["fib.h"] include-dirs = ["/path/to/headers"] libraries = ["fib"] library-dirs = ["/path/to/libraries"] The ``include-dirs`` and ``library-dirs`` would only need to be specified if they are not installed in standard locations and found automatically by the compiler. Note that POSIX path separators are used. SIP will automatically convert these to native path separators when required. The :file:`.sip` file would look more like that shown below. .. parsed-literal:: // Define the SIP wrapper to the (actual) fib library. %Module(name=fib, language="C") %ModuleCode #include %End int fib_n(int n); The :directive:`%MethodCode` directive has been removed and the :directive:`%ModuleCode` directive has been added. Configuring a Build ................... How should a project deal with the situation where, for example, the ``fib`` library has been installed in a non-standard location? There are a couple of possible approaches: - the user modifies :file:`pyproject.toml` to set the values of ``include-dirs`` and ``library-dirs`` appropriately - the project provides command line options to SIP's build tools (i.e. :program:`sip-build`, :program:`sip-install` and :program:`sip-wheel`) that allows the user to specify the locations. The first approach, while not particularly user friendly, is legitimate so long as you document it. However note that it cannot work when building and installing directly from an sdist because :program:`pip` does not currently fully implement `PEP 517 `__. The second approach is the most flexible but requires code to implement it. If SIP finds a file called (by default) :file:`project.py` in the same directory as :file:`pyproject.toml` then it is assumed to be an extension to the build system. Specifically it is expected to implement a class that is a sub-class of SIP's :mod:`~sipbuild.AbstractProject` class. Below is a complete :file:`project.py` that adds options to allow the user to specify the locations of the ``fib`` header file and library. .. parsed-literal:: import os from sipbuild import Option, Project class FibProject(Project): """ A project that adds an additional configuration options to specify the locations of the fib header file and library. """ def get_options(self): """ Return the sequence of configurable options. """ # Get the standard options. options = super().get_options() # Add our new options. inc_dir_option = Option('fib_include_dir', help="the directory containing fib.h", metavar="DIR") options.append(inc_dir_option) lib_dir_option = Option('fib_library_dir', help="the directory containing the fib library", metavar="DIR") options.append(lib_dir_option) return options def apply_user_defaults(self, tool): """ Apply any user defaults. """ # Ensure any user supplied include directory is an absolute path. if self.fib_include_dir is not None: self.fib_include_dir = os.path.abspath(self.fib_include_dir) # Ensure any user supplied library directory is an absolute path. if self.fib_library_dir is not None: self.fib_library_dir = os.path.abspath(self.fib_library_dir) # Apply the defaults for the standard options. super().apply_user_defaults(tool) def update(self, tool): """ Update the project configuration. """ # Get the fib bindings object. fib_bindings = self.bindings['fib'] # Use any user supplied include directory. if self.fib_include_dir is not None: fib_bindings.include_dirs = [self.fib_include_dir] # Use any user supplied library directory. if self.fib_library_dir is not None: fib_bindings.library_dirs = [self.fib_library_dir] The :meth:`~sipbuild.Project.get_options` method is reimplemented to add two new :class:`~sipbuild.Option` instances. An :class:`~sipbuild.Option` defines a key that can be used in :file:`pyproject.toml`. Because these :class:`~sipbuild.Option`\s are defined as part of the :class:`~sipbuild.Project` then the keys are used in the ``[tool.sip.project]`` section of :file:`pyproject.toml`. In addition, because each :class:`~sipbuild.Option` has help text, these are defined as *user options* and therefore are also added as command line options to each of SIP's build tools. Note that in both :file:`pyproject.toml` and the command line any ``_`` in the :class:`~sipbuild.Option` name is converted to ``-``. The :meth:`~sipbuild.Project.apply_user_defaults` method is reimplemented to provide a default value for an :class:`~sipbuild.Option`. Note that the value is accessed as an instance attribute of the object for which the :class:`~sipbuild.Option` is defined. In this case there are no default values but we want to make sure that any values that are provided are absolute path names. The :meth:`~sipbuild.Project.update` method is reimplemented to update the :class:`~sipbuild.Bindings` object for the ``fib`` bindings with any values provided by the user from the command line. Package Projects ---------------- We now describe two package projects. The ``examples-core`` project contains a single set of bindings called :mod:`~examples.core`. The ``examples-extras`` project contains a single set of bindings called :mod:`~examples.extras`. The :mod:`~examples.extras` module imports the :mod:`~examples.core` module. Both modules are part of the top-level :mod:`examples` package. The :mod:`~examples.sip` module required by all related package projects is also part of the top-level :mod:`examples` package. Again with this example, in order to make it self-contained, we are not creating bindings for real libraries but instead embedding the implementation within the :file:`.sip` files. :mod:`examples.sip` ................... In order to create an sdist for the :mod:`~examples.sip` module, run:: sip-module --sdist examples.sip If you want to create a wheel from the sdist then run:: pip wheel examples_sip-X.Y.Z.tar.gz ``X.Y.Z`` is the version number of the ABI implemented by the :mod:`~examples.sip` module and it will default to the latest version. :mod:`examples.core` .................... We now look at the :file:`pyproject.toml` file for the ``examples-core`` project (downloadable from :download:`here <../examples/package/core/pyproject.toml>`) below. .. literalinclude:: ../examples/package/core/pyproject.toml Compared to the standalone project's version of the file we have added the ``[tool.sip.bindings.core]`` section to specify that the project contains a single set of bindings called :mod:`~examples.core`. We need to do this because the name is no longer the same as the name of the project itself as defined by the ``name`` key of the ``[project]`` section. The bindings section is empty because all the default values are appropriate in this case. We have also added the ``[tool.sip.project]`` section containing the ``sip-module`` key, which specifies the full package name of the :mod:`~examples.sip` module and the ``dunder-init`` key, which specifies that an :file:`__init__.py` file (empty by default) is created in the top-level :mod:`examples` package. We next look at the :file:`core.sip` file (downloadable from :download:`here <../examples/package/core/core.sip>`) below. .. literalinclude:: ../examples/package/core/core.sip The :directive:`%Module` directive, as well as specifying the full package name of the :mod:`~examples.core` module, specifies that the bindings will use the `PEP 384 `__ stable ABI. The :directive:`%DefaultEncoding` directive specifies that any character conversions between C/C++ and Python ``str`` objects will default to the ASCII codec. The :directive:`%Platforms` directive defines three mutually exclusive *tags* that can be used by :directive:`%If` directives to select platform-specific parts of the bindings. The remaining parts of the file are three different platform-specific implementations of a function called :c:func:`what_am_i` which just returns a name for the platform. We are then left will the question as to how we specify which of the platform tags should be selected for a particular build. For this we need the :file:`project.py` file file (downloadable from :download:`here <../examples/package/core/project.py>`) shown below. .. literalinclude:: ../examples/package/core/project.py As before we reimplement the :meth:`~sipbuild.Project.get_options` method to add a new :class:`~sipbuild.Option` instance to specify the platform tag. Because no help text has been specified the :class:`~sipbuild.Option` can only be used as a key in the ``[tool.sip.project]`` section of :file:`pyproject.toml` and will not be added to the command line options of the build tools. We reimplement the :meth:`~sipbuild.Project.apply_nonuser_defaults` method provide a default value for the new :class:`~sipbuild.Option` if it hasn't been specified in :file:`pyproject.toml`. If it has been specified in :file:`pyproject.toml` then we validate it. (Why would be want to specify the platform in :file:`pyproject.toml`? Perhaps we are cross-compiling and introspecting the host platform would be inappropriate.) The :meth:`~sipbuild.Project.update` method is reimplemented to update the :class:`~sipbuild.Bindings` object for the ``core`` bindings with the validated platform tag. :mod:`examples.extras` ...................... We now look at the :file:`pyproject.toml` file for the ``example-extras`` project (downloadable from :download:`here <../examples/package/extras/pyproject.toml>`) below. .. literalinclude:: ../examples/package/extras/pyproject.toml Compared to the ``examples-core`` project's version of the file we have added the ``dependencies`` key to the ``[project]`` section which will ensure that the ``examples-core`` project will be automatically installed as a prerequisite of the ``examples-extras`` project. SIP will automatically add a similar line to ensure the :mod:`~examples.sip` module is also installed. Of course we have specifed an appropriately named bindings section. We have also removed the ``dunder-init`` key from the ``[tool.sip.project.section]`` section. We next look at the :file:`extras.sip` file (downloadable from :download:`here <../examples/package/extras/extras.sip>`) below. .. literalinclude:: ../examples/package/extras/extras.sip This is very similar to the :mod:`~examples.core` module in that it implements simple platform-specific functions. The key thing to notice is that there is no need to specify the platform tag as part of the configuration as it is obtained automatically from the installed ``examples-core`` project. The ``examples-extras`` project has no need for a :file:`project.py` file. sip-6.8.6/docs/index.rst000066400000000000000000000004511464421045000151170ustar00rootroot00000000000000SIP Documentation ================= .. toctree:: :maxdepth: 2 :caption: Contents introduction examples command_line_tools specification_files directives annotations other_topics c_api abi_13 abi_12 pyproject_toml sipbuild_api releases sip-6.8.6/docs/introduction.rst000066400000000000000000000220571464421045000165370ustar00rootroot00000000000000Introduction ============ SIP is a tool for automatically generating `Python `__ bindings for C and C++ libraries. SIP was originally developed in 1998 for `PyQt `__ - the Python bindings for the Qt GUI toolkit - but is suitable for generating bindings for any C or C++ library. SIP can also be used write self contained extension modules, i.e. without a library to be wrapped. This version of SIP generates bindings for Python v3.8 and later. SIP is hosted at `GitHub `__. The documentation is hosted at `Read the Docs `__. License ------- SIP is licensed under the BSD 2 clause license. Features -------- SIP, and the bindings it produces, have the following features: - bindings run under Linux, Windows, macOS, Android and iOS - bindings can be built to use the `PEP 384 `__ stable ABI so that they do not need to be built for each supported version of Python - an extendable, `PEP 517 `__-compliant build system that will build and install your bindings and create sdist and wheel files that you can upload to PyPI - bindings are fast to load and minimise memory consumption especially when only a small sub-set of a large library is being used - automatic conversion between standard Python and C/C++ data types - overloading of functions and methods with different argument signatures - support for Python's keyword argument syntax - support for both explicitly specified and automatically generated docstrings - access to a C++ class's protected methods - the ability to define a Python class that is a sub-class of a C++ class, including abstract C++ classes - Python sub-classes can implement the :meth:`__dtor__` method which will be called from the C++ class's virtual destructor - support for ordinary C++ functions, class methods, static class methods, virtual class methods and abstract class methods - the ability to re-implement C++ virtual and abstract methods in Python - support for global and class variables - support for global and class operators - support for C++ namespaces - support for C++ templates - support for C++ exceptions and wrapping them as Python exceptions - the automatic generation of complementary rich comparison slots - support for deprecation warnings - the ability to define mappings between C++ classes and similar Python data types that are automatically invoked - the ability to automatically exploit any available run time type information to ensure that the class of a Python instance object matches the class of the corresponding C++ instance - the ability to change the type and meta-type of the Python object used to wrap a C/C++ data type - full support of the Python global interpreter lock, including the ability to specify that a C++ function of method may block, therefore allowing the lock to be released and other Python threads to run - support for the concept of ownership of a C++ instance (i.e. what part of the code is responsible for calling the instance's destructor) and how the ownership may change during the execution of an application - the ability to generate bindings for a C++ class library that itself is built on another C++ class library which also has had bindings generated so that the different bindings integrate and share code properly - a sophisticated versioning system that allows the full lifetime of a C++ class library, including any platform specific or optional features, to be described in a single set of specification files - support for the automatic generation of `PEP 484 `__ type hint stub files - the ability to include documentation in the specification files which can be extracted and subsequently processed by external tools - the ability to include copyright notices and licensing information in the specification files that is automatically included in all generated source code. SIP supports C++ compilers that implement the C++11 standard as a minimum. Older compilers may work depending on what features are used. Overview -------- At its simplest a SIP project contains a :ref:`specification file ` (:file:`.sip` file) that describes the API that the generated bindings will wrap, and a :file:`pyproject.toml` file that describes how the bindings will be built. A specification file is very like a C/C++ header file with embedded :ref:`directives ` and :ref:`annotations `. The format of a :file:`pyproject.toml` file is described in `PEP 518 `__. A SIP project can either be a *standalone* project or a *package* project. A standalone project implements a single set of bindings (i.e. a single extension module) that cannot be extended by another set of bindings. A package project implements one or more sets of mutually dependent bindings (i.e. one set of bindings will import another set of bindings). Such bindings may be defined in the same project or a completely different package project (possibly with a different maintainer). Often the bindings of all related package projects will be installed as part of a single top-level Python package. For example, the whole of PyQt5 is current implemented as 6 separate package projects each containing between 1 and 52 sets of bindings all installed as part of the :mod:`PyQt5` top-level package. However there are also 3rd-party packages that extend PyQt5 but are not installed in the :mod:`PyQt5` top-level package. SIP also generates a :mod:`sip` module which performs the following functions: - it implements a private C ABI used by the bindings of package projects that allows them to interact - it implements a public C API used by bindings authors in hand-written code in situations where SIP's normal behaviour is insufficient and also when embedding Python in C/C++ applications - it implements a public Python API used by application authors typically to configure the behaviour of bindings and to aid debugging. The :mod:`sip` module does not use the `PEP 384 `__ stable ABI and so must be built for each supported version of Python. The version number of the :mod:`sip` module is the version number of the ABI that the module implements. Like SIP itself, this uses `semantic versioning `__. When used with standalone projects the :mod:`sip` module is not a separate module and is instead embedded in the single set of bindings. When used with package projects the :mod:`sip` module is a separate extension module installed somewhere under the top-level package. `PEP 517 `__ describes the concepts of a *build frontend* and a *build backend*. SIP implements a compliant backend and provides a number of frontends each performing a specific type of build. :program:`sip-build` This builds the project but does not install it. This is useful when developing a set of bindings. :program:`sip-install` This builds and installs a project. :program:`sip-sdist` This creates an sdist (a source distribution) that can be uploaded to PyPI. :program:`sip-wheel` This creates a wheel (a binary distribution) that can be uploaded to PyPI. Collectively the above are SIP's *build tools*. :program:`pip` can also be used as a build frontend. This has the advantage that the user does not need to explicitly install SIP, :program:`pip` will do that automatically. However it has the disadvantage that :program:`pip` does not (yet) allow the user to configure the backend using command line options. SIP also includes some additional command line tools. :program:`sip-distinfo` This creates and populates a :file:`.dist-info` directory of an installation or a wheel. It is provided for build systems that extend the SIP build system and need to create the :file:`.dist-info` directory from an external tool such as :program:`make`. :program:`sip-module` This builds one or more elements of the :mod:`sip` module for a set of package projects: - an sdist of the module which can be installed by :program:`pip` or uploaded to PyPI - a :file:`sip.h` header file which defines the module's ABI. Normally you do not need to worry about this file but this will install a local copy of it if required - a :file:`sip.rst` file that documents the Python API of the module for inclusion in your project's documentation. Installation ------------ To install SIP from PyPI, run:: pip install sip SIP is also included with all of the major Linux distributions. However, it may be a version or two out of date. Support for Old Versions of Python ---------------------------------- When a Python version reaches it's end-of-life, support for it will be removed in the next minor release of SIP. For example, if the current version of SIP is v6.x.y then the support will be removed in v6.x+1.0. sip-6.8.6/docs/make.bat000066400000000000000000000014401464421045000146620ustar00rootroot00000000000000@ECHO OFF pushd %~dp0 REM Command file for Sphinx documentation if "%SPHINXBUILD%" == "" ( set SPHINXBUILD=sphinx-build ) set SOURCEDIR=. set BUILDDIR=_build %SPHINXBUILD% >NUL 2>NUL if errorlevel 9009 ( echo. echo.The 'sphinx-build' command was not found. Make sure you have Sphinx echo.installed, then set the SPHINXBUILD environment variable to point echo.to the full path of the 'sphinx-build' executable. Alternatively you echo.may add the Sphinx directory to PATH. echo. echo.If you don't have Sphinx installed, grab it from echo.https://www.sphinx-doc.org/ exit /b 1 ) if "%1" == "" goto help %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% goto end :help %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% :end popd sip-6.8.6/docs/other_topics.rst000066400000000000000000000320231464421045000165120ustar00rootroot00000000000000Other Topics ============ Wrapping Enums -------------- SIP wraps C/C++ enums using a dedicated Python type and implements behaviour that mimics the C/C++ behaviour regarding the visibility of the enum's members. In other words, an enum's members have the same visibility as the enum itself. For example:: class MyClass { public: enum MyEnum { Member } } In Python the ``Member`` member is referenced as ``MyClass.Member``. This behaviour makes it easier to translate C/C++ code to Python. In more recent times C++11 has introduced scoped enums and Python has introduced the :mod:`enum` module. In both cases a member is only visible in the scope of the enum. In other words, the ``Member`` member is referenced as ``MyClass.MyEnum.Member``. SIP generates bindings for C++11 scoped enums and implements them as Python :class:`enum.Enum` objects. A disadvantage of the above is that the Python programmer needs to know the nature of the C/C++ enum in order to access its members. In order to avoid this, SIP makes the members of traditional C/C++ enums visible from the scope of the enum as well. It is recommended that Python code should always specify the enum scope when referencing an enum member. A future version of SIP will remove support for the traditional behaviour. .. _ref-object-ownership: Ownership of Objects -------------------- When a C++ instance is wrapped a corresponding Python object is created. The Python object behaves as you would expect in regard to garbage collection - it is garbage collected when its reference count reaches zero. What then happens to the corresponding C++ instance? The obvious answer might be that the instance's destructor is called. However the library API may say that when the instance is passed to a particular function, the library takes ownership of the instance, i.e. responsibility for calling the instance's destructor is transferred from the SIP generated module to the library. Ownership of an instance may also be associated with another instance. The implication being that the owned instance will automatically be destroyed if the owning instance is destroyed. SIP keeps track of these relationships to ensure that Python's cyclic garbage collector can detect and break any reference cycles between the owning and owned instances. The association is implemented as the owning instance taking a reference to the owned instance. The :aanno:`TransferThis`, :aanno:`Transfer` and :aanno:`TransferBack` argument annotations are used to specify where, and it what direction, transfers of ownership happen. It is very important that these are specified correctly to avoid crashes (where both Python and C++ call the destructor) and memory leaks (where neither Python and C++ call the destructor). This applies equally to C structures where the structure is returned to the heap using the :c:func:`free` function. See also :c:func:`sipTransferTo()` and :c:func:`sipTransferBack()`. .. _ref-types-metatypes: Types and Meta-types -------------------- Every Python object (with the exception of the :class:`object` object itself) has a meta-type and at least one super-type. By default an object's meta-type is the meta-type of its first super-type. SIP implements two super-types, :class:`sip.simplewrapper` and :class:`sip.wrapper`, and a meta-type, :class:`sip.wrappertype`. :class:`sip.simplewrapper` is the super-type of :class:`sip.wrapper`. The super-type of :class:`sip.simplewrapper` is :class:`object`. :class:`sip.wrappertype` is the meta-type of both :class:`sip.simplewrapper` and :class:`sip.wrapper`. The super-type of :class:`sip.wrappertype` is :class:`type`. :class:`sip.wrapper` supports the concept of object ownership described in :ref:`ref-object-ownership` and, by default, is the super-type of all the types that SIP generates. :class:`sip.simplewrapper` does not support the concept of object ownership but SIP generated types that are sub-classed from it have Python objects that take less memory. SIP allows a class's meta-type and super-type to be explicitly specified using the :canno:`Metatype` and :canno:`Supertype` class annotations. SIP also allows the default meta-type and super-type to be changed for a module using the :directive:`%DefaultMetatype` and :directive:`%DefaultSupertype` directives. Unlike the default super-type, the default meta-type is inherited by importing modules. If you want to use your own meta-type or super-type then they must be sub-classed from one of the SIP provided types. Your types must be registered using :c:func:`sipRegisterPyType()`. This is normally done in code specified using the :directive:`%InitialisationCode` directive. .. note:: It is not possible to define new super-types or meta-types if the limited Python API is enabled. .. _ref-lazy-type-attributes: Lazy Type Attributes -------------------- Instead of populating a wrapped type's dictionary with its attributes (or descriptors for those attributes) SIP only creates objects for those attributes when they are actually needed. This is done to reduce the memory footprint and start up time when used to wrap large libraries with hundreds of classes and tens of thousands of attributes. SIP allows you to extend the handling of lazy attributes to your own attribute types by allowing you to register an attribute getter handler (using :c:func:`sipRegisterAttributeGetter()`). This will be called just before a type's dictionary is accessed for the first time. Support for Python's Buffer Interface ------------------------------------- SIP supports Python's buffer interface in that whenever C/C++ requires a ``char`` or ``char *`` type then any Python type that supports the buffer interface (including ordinary Python strings) can be used. Support for Wide Characters --------------------------- SIP supports the use of wide characters (i.e. the ``wchar_t`` type). Python's C API includes support for converting between ``str`` objects and wide character strings and arrays. When converting from a ``str`` object to wide characters SIP creates the string or array on the heap (using memory allocated using :c:func:`sipMalloc()`). This then raises the problem of how this memory is subsequently freed. The following describes how SIP handles this memory in the different situations where this is an issue. - When a wide string or array is passed to a function or method then the memory is freed (using :c:func:`sipFree()`) after that function or method returns. - When a wide string or array is returned from a virtual method then SIP does not free the memory until the next time the method is called. - When an assignment is made to a wide string or array instance variable then SIP does not first free the instance's current string or array. .. _ref-gil: The Python Global Interpreter Lock ---------------------------------- Python's Global Interpretor Lock (GIL) must be acquired before calls can be made to the Python API. It should also be released when a potentially blocking call to C/C++ library is made in order to allow other Python threads to be executed. In addition, some C/C++ libraries may implement their own locking strategies that conflict with the GIL causing application deadlocks. SIP provides ways of specifying when the GIL is released and acquired to ensure that locking problems can be avoided. SIP always ensures that the GIL is acquired before making calls to the Python API. By default SIP does not release the GIL when making calls to the C/C++ library being wrapped. The :fanno:`ReleaseGIL` annotation can be used to override this behaviour when required. If the ``release-gil`` key is set to ``true`` in the bindings-specific section of the ``pyproject.toml`` file then (for that set of bindings) then the default behaviour is changed and SIP releases the GIL every time is makes calls to the C/C++ library being wrapped. The :fanno:`HoldGIL` annotation can be used to override this behaviour when required. .. _ref-subclass-convertors: Writing :directive:`%ConvertToSubClassCode` ------------------------------------------- When SIP needs to wrap a C++ class instance it first checks to make sure it hasn't already done so. If it has then it just returns a new reference to the corresponding Python object. Otherwise it creates a new Python object of the appropriate type. In C++ a function may be defined to return an instance of a certain class, but can often return a sub-class instead. The :directive:`%ConvertToSubClassCode` directive is used to specify handwritten code that exploits any available real-time type information (RTTI) to see if there is a more specific Python type that can be used when wrapping the C++ instance. The RTTI may be provided by the compiler or by the C++ instance itself. The directive is included in the specification of one of the classes that the handwritten code handles the type conversion for. It doesn't matter which one, but a sensible choice would be the one at the root of that class hierarchy in the module. .. note:: In a future version of SIP this use of the directive will be deprecated and it will instead be placed outside any class specification. If a class hierarchy extends over a number of modules then this directive should be used in each of those modules to handle the part of the hierarchy defined in that module. SIP will ensure that the different pieces of code are called in the right order to determine the most specific Python type to use. A class has at least one convertor if it or any super-class defines :directive:`%ConvertToSubClassCode`. A convertor has a base class. If a class that defines :directive:`%ConvertToSubClassCode` does not have a super-class that defines :directive:`%ConvertToSubClassCode` then that class is the base class. Otherwise the base class is that of the right-most super-class that has a convertor. In this case the :directive:`%ConvertToSubClassCode` extends all other convertors with the same base class. Consider the following class hierarchy:: A \ B* C* \ / \ D E / \ F G* The classes marked with an asterisk define :directive:`%ConvertToSubClassCode`. Classes ``A`` to ``F`` are implemented in module ``X``. Class ``G`` is implemented in module ``Y``. We can say the following: - ``A`` has no convertor, all other classes do. - The base class of ``B``'s :directive:`%ConvertToSubClassCode` is ``B``. - The base class of ``C``'s :directive:`%ConvertToSubClassCode` is ``C``. - ``D`` and ``F`` have two convertors ``B`` and ``C``. - ``E``'s convertor is ``C``. - The base class of ``G``'s :directive:`%ConvertToSubClassCode` is ``C``. - ``B``'s :directive:`%ConvertToSubClassCode` must handle instances of ``B``, ``D`` and ``F`` (i.e. those sub-classes of its base class defined in the same module). - ``C``'s :directive:`%ConvertToSubClassCode` must handle instances of ``C``, ``D``, ``E`` and ``F``. - ``G``'s :directive:`%ConvertToSubClassCode` must handle instances of ``G``. A convertor is invoked when SIP needs to wrap a C++ instance and the type of that instance is a sub-class of the convertor's base class. The convertor is passed a pointer to the instance cast to the base class. The convertor then, if possible, casts that pointer to an instance of a sub-class of its original class. It also returns a pointer to the corresponding :ref:`generated type structure `. It is possible for a convertor to switch to another convertor. This can avoid duplication of convertor code where there is multiple inheritance. When more than one convertor may be invoked they are done so in the order that reflects the module hierarchy. When the convertors are defined in the same module then the order is undefined. Convertors must be written with this mind. Given the class hierarchy shown above, lets say that SIP needs to wrap an instance of known to be of class ``D`` but is actually of class ``F``. We want the conversion mechanism to recognise that fact and return a Python object of type ``F``. The following steps are taken: - ``G``'s :directive:`%ConvertToSubClassCode` is invoked and passed the pointer to ``D`` cast to ``C``. This convertor only recognises instances of class ``G`` and so returns a value that indicates it was unable to perform a conversion. - SIP will now invoke either ``B``'s :directive:`%ConvertToSubClassCode` or ``C``'s :directive:`%ConvertToSubClassCode`. As they are defined in the same module which is chosen is undefined. Let's assume it is the ``C`` convertor that is invoked. - The convertor recognises that the instance is of class ``D`` (rather than ``C`` or ``E``). It must also determine whether this really is ``D`` or whether it is actually ``F``. Of course ``B``'s :directive:`%ConvertToSubClassCode` must also make the same distinction. Rather than possibly duplicating the required code in both convertors the ``C`` convertor switches to the ``B`` convertor. It does this by casting the pointer it is trying to convert to ``B`` and returns ``B``'s :ref:`generated type structure `. sip-6.8.6/docs/pyproject_toml.rst000066400000000000000000000422251464421045000170670ustar00rootroot00000000000000:file:`pyproject.toml` Reference ================================ Note that, for the build tools, the keys described in this section are the standard keys. Any of these keys could be removed, or new keys added, by build system extensions including project-specific :file:`project.py` files. Some string values are interpreted as a Python callable. A callable can be interpreted in three different ways. If the value has a :file:`.py` extension then it is assumed to be the name of a Python script which is then imported. The imported module is then searched for an object that is of a type (or sub-type) expected by the particular key. If the value is a simple name then it is assumed to be the name of a module which is then imported and searched as before. Finally if the value contains an embedded ``:`` then the part to the left of the ``:`` is taken to be the name of a module and the part to the right is taken to be the name of a factory callable within the module. The factory is then called to create the required object. ``[build-system]`` Section -------------------------- The content of this section is defined in `PEP 518 `__ and should be similar to the following:: [build-system] requires = ["sip >=5, <6"] build-backend = "sipbuild.api" This specifies that v5 of the ``sip`` package at PyPI should be used. You may want to adjust the ``requires`` value if you use features introduced in a later version of SIP. ``[tool.sip]`` Section ---------------------- The key/values in this section apply to the build system as a whole. Unless stated otherwise, all values are strings. **project-factory** The value is used to identify a callable that will return an object that is a sub-class of :class:`~sipbuild.AbstractProject`. If the value is the name of a :file:`.py` file then that file is evaluated and the resulting module is searched for a type object that is sub-classed from :class:`~sipbuild.AbstractProject`. Otherwise the value must be the name of a module to be imported which is then searched for an appropriate type object. The name of the module may have the name of the callable appended (and separated by ``:``) in which case the type of the object is ignored. The default project factory is :file:`project.py`. ``[tool.sip.builder]`` Section ------------------------------ Unless stated otherwise, the values of all list options may contain environment markers as defined in `PEP 508 `__. The SIP build system does not define any key/values in this section but build system extensions may do so. ``[tool.sip.metadata]`` Section ------------------------------- .. deprecated:: 6.8 The ``[project]`` section as defined in `PEP 621 `__ should be used instead. The key/values in this section are interpreted as meta-data describing the project as specified in `PEP 566 `__. SIP will provide default values for ``metadata-version``, ``requires-python`` and ``version``. The only value that must be specified is ``name``. SIP also implements the additional ``description-file`` key. The value of this is the name of a file (relative to the directory containing :file:`pyproject.toml`) that contains a description of the project. The description is read and appended to the meta-data. If a value is a list of individual values then the key will be repeated in the generated meta-data for each of those values. Note that SIP does not check the validity of the key/values in this section. ``[tool.sip.project]`` Section ------------------------------ The key/values in this section apply to the project as a whole. Unless stated otherwise, all values are strings. Unless stated otherwise, the values of all list options may contain environment markers as defined in `PEP 508 `__. **abi-version** The minimum version number of the ABI of the :mod:`sip` module being used. If only the major version number is specified then the minor version defaults to 0. By the default the latest major version is used. **api-dir** The value is the name of a the directory in which a QScintilla :file:`.api` file is created. By default no :file:`.api` file is created. There is also a corresponding command line option. **bindings-factory** The value is a callable that will return an object that is a sub-class of :class:`~sipbuild.Bindings`. The default bindings factory is :class:`~sipbuild.Bindings`. Bindings factories can also be specified programmatically using :attr:`~sipbuild.Project.bindings_factories`. **build-dir** The value is the name of a directory in which all generated files will be created. The directory will not be removed after the build has been completed. The default depends on which build tool is being used. There is also a corresponding command line option. **build-tag** The value is the build tag to be used in the name of a wheel. There is also a corresponding command line option. **builder-factory** The value is a callable that will return an object that is a sub-class of :class:`~sipbuild.AbstractBuilder`. For Python v3.10 and later the default builder factory is :class:`~sipbuild.SetuptoolsBuilder`. For earlier versions of Python the default builder factory is :class:`~sipbuild.DistutilsBuilder`. **compile** The boolean value specifies if the generated code is to be compiled. By default it is compiled. There is also a corresponding command line option. **console-scripts** The value is a list of entry points that defines one or more console scripts to be installed as part of the project. **disable** The value is a list of the names of the bindings that are disabled and will not be built. There is also a corresponding command line option. **dunder-init** The boolean value specifies if a :file:`__init__.py` file should be installed in the top level package directory. By default it is not installed and the value is ignored for standalone projects. If it is set and at least one set of bindings specify that a Python type hints stub file be generated (by setting the ``pep484-pyi`` option) then the stub file for the ``sip`` module and a PEP 561-compatible ``py.typed`` marker file is also installed. **distinfo** The boolean value specifies if a :file:`.dist-info` directory is to be created when a project is installed. By default it is enabled. There is also a corresponding command line option. **enable** The value is a list of the names of the bindings that are enabled and will be built. Any associated configuration tests that would normally be run to determine if the bindings should be built are suppressed. There is also a corresponding command line option. **gui-scripts** The value is a list of entry points that defines one or more GUI scripts to be installed as part of the project. **manylinux** The boolean value specifies if support for ``manylinux`` in the platform tag of a name of a wheel is enabled. By default ``manylinux`` support is enabled. It should only be disabled if support for older versions of :program:`pip` is required. There is also a corresponding command line option. **minimum-glibc-version** The minimum GLIBC version required by the project specified as a 2-tuple of the major and minor version numbers. This is used to determine the correct platform tag to use for Linux wheels. The default version of GLIBC is v2.5 which corresponds to ``manylinux1``. It is ignored if the ``manylinux`` option is False. There is also a corresponding command line option. **minimum-macos-version** The minimum macOS version required by the project specified as a 2-tuple of the major and minor version numbers. This is used to determine the correct platform tag to use for macOS wheels. The default version is that required by the Python interpreter. **name** The value is used instead of the value of the ``name`` key in the ``[tool.sip.metadata]`` section in the name of an sdist or wheel. There is also a corresponding command line option. **py-debug** The boolean value specifies if a debug build of Python is being used. By default this is determined dynamically from the Python installation. **py-include-dir** The value is the name of the directory containing the :file:`Python.h` header file. By default this is determined dynamically from the Python installation. **py-platform** The value is the target Python platform. By default this is determined dynamically from the Python installation. **py-major-version** The value is the major version number of the version of Python being targetted. By default this is determined dynamically from the Python installation. **py-minor-version** The value is the minor version number of the version of Python being targetted. By default this is determined dynamically from the Python installation. **quiet** The boolean value enables or disables the display of progress messages. By default progress messages are displayed. There is also a corresponding command line option. **scripts-dir** The value is the name of the directory where any project scripts will be installed in. If name is relative then it is taken as relative to the target directory. By default the directory containing the Python interpreter is used. There is also a corresponding command line option. **sdist-excludes** The value is a list of files and directories, expressed as *glob* patterns and relative to the directory containing the :file:`pyproject.toml` file, that should be excluded from an sdist. **sip-files-dir** The value is the name of the directory containing the :file:`.sip` specification files. If the project is a package project then the :file:`.sip` files of each set of bindings are assumed to be in their own bindings-specific sub-directory. The default value is the name of the directory containing the :file:`pyproject.toml` file. **sip-include-dirs** The value is a list of additional directories that should be searched for :file:`.sip` files. **sip-module** The value is the fully qualified package name of the :mod:`sip` module. If it is not specified then the project is assumed to be a standalone project. **target-dir** The value is the name of the directory where the project will be installed in. By default it is the :file:`site-packages` directory of the Python installation. There is also a corresponding command line option. **verbose** The boolean value enables or disables the display of verbose progress messages. By default verbose progress messages are not displayed. There is also a corresponding command line option. **version-info** The boolean value determines if the generated code includes a reference to the SIP version number. By default a reference is included. There is also a corresponding command line option. **wheel-includes** The value is a list of files and directories, specified as *glob* patterns, that should be included in a wheel. If a pattern is relative then it is taken as being relative to the project directory. If an element of the list is a string then it is a pattern and files and directories are installed in the target directory. If an element is a 2-tuple then the first part is the pattern and the second part is the name of a sub-directory relative to the target directory where the files and directories are installed. Bindings Sections ----------------- Each set of bindings has its own section called ``[tool.sip.bindings.name]`` where ``name`` is the name of the bindings. If no bindings are explicitly defined then SIP will look in the directory containing the :file:`pyproject.toml` file for a :file:`.sip` file with the same name as the value of the ``name`` key in the ``[tool.sip.metadata]`` section and, if found, will assume that it defines the bindings of a standalone project. Unless stated otherwise, all values are strings. Unless stated otherwise, the values of all list options may contain environment markers as defined in `PEP 508 `__. **builder-settings** The value is a list of values that are passed to the builder. It is up to the builder to determine how these values are used. **concatenate** The value, interpreted as a number, specifies that the generated code is split into that number of source files. By default one file is generated for each C structure or C++ class. Specifying a low value can significantly speed up the build of large projects. There is also a corresponding command line option. **debug** The boolean value specifies if a build with debugging symbols is performed. By default a debug build is not performed. There is also a corresponding command line option. **define-macros** The value is a list of ``#define`` names and values in the form ``"NAME"`` or ``"NAME=VALUE"``. **disabled-features** The value is a list of disabled :directive:`%Feature` tags. There is also a corresponding command line option. **docstrings** The boolean value specifies if docstrings that describe the signature of all functions, methods and constructors should be generated. By default docstrings are generated. There is also a corresponding command line option. **exceptions** The boolean values specifies if support for C++ exceptions in the library being wrapped is enabled. By default exception support is disabled. **extra-compile-args** The value is a list of extra command line arguments to pass to the compiler. **extra-link-args** The value is a list of extra command line arguments to pass to the linker. **extra-objects** The value is a list of extra compiled object files to link. **generate-extracts** The value is a list of extracts (defined by the :directive:`%Extract` directive). Each value is the identifier of the extract and the name of the file that the extract is written to separated by a ``:``. **headers** The value is a list of additional :file:`.h` header files needed to build the bindings. **include-dirs** The value is a list of additional directories to search for :file:`.h` header files. **internal** The boolean value specifies if the set of bindings are internal. Internal bindings never have :file:`.sip`, :file:`.pyi` or :file:`.api` files installed. By default the bindings are not internal. **libraries** The value is a list of libraries to link the source code with and should include any library being wrapped. **library-dirs** The value is a list of directories that will be searched, in addition to the standard system directories, for any libraries. **pep484-pyi** The boolean value specifies if a Python type hints stub file is generated. This file contains a description of the module's API that is compliant with `PEP 484 `__. By default the stub file is not generated. There is also a corresponding command line option. **protected-is-public** The boolean value specifies if SIP redefines the ``protected`` keyword as ``public`` during compilation. On non-Windows platforms this can result in a significant reduction in the size of a generated Python module. By default SIP redefines the keyword on non-Windows platforms. There is also a corresponding command line option. **release-gil** The boolean value specifies if the Python GIL is always released when calling a function in the library being wrapped irrespective of any :fanno:`ReleaseGIL` annotation. By default the GIL is only released as determined by :fanno:`ReleaseGIL`. **static** The boolean value specifies that the bindings should be built as a static library. By default the bindings are built as a dynamically loaded library. Note that not all builders (including the default :class:`~sipbuild.DistutilsBuilder` builder) can build static libraries. **sip-file** The name of the :file:`.sip` specification file that defines the set of bindings. If it is a relative name then it is assumed to be relative to the value of the ``sip-files-dir`` key of the ``[tool.sip.project]`` section. By default it is the name of the bindings with :file:`.sip` appended. **source-suffix** The value is the extension used for the generated source files. By default this is :file:`.c` for C bindings and :file:`.cpp` for C++ bindings. **sources** The value is a list of additional C/C++ source files needed to build the bindings. **tags** The value is a list of :directive:`%Platforms` and :directive:`%Timeline` tags used to configure the bindings. **tracing** The boolean value specifies that debugging statements that trace the execution of the bindings are automatically generated. Be default the statements are not generated. There is also a corresponding command line option. sip-6.8.6/docs/releases.md000066400000000000000000000073521464421045000154120ustar00rootroot00000000000000# Release Notes ## v6.8.6 ### Handle single number macOS deployment targets If the macOS deployment target (as returned by `sysconfig.get_platform()`) was just a major version number then SIP would crash. Resolves [#31](https://github.com/Python-SIP/sip/issues/31) ### Support for architectures where `char` is unsigned Conversions to and from `char` and Python integer objects on architectures where `char` was unsigned (eg. Linux on ARM) have been fixed. The latest `sip` module ABI versions are v12.15 and v13.8. Resolves [#29](https://github.com/Python-SIP/sip/issues/29) ### Support for building from git archives `.git_archival.txt` and `.gitattributes` were added so that git archives contain the necessary metadata for [setuptools-scm](https://setuptools-scm.readthedocs.io/en/stable/usage#git-archives). Pull request [#30](https://github.com/Python-SIP/sip/pull/30) ### Run the tests using the current Python version The tests are run using the current Python version instead of the default one to make it easier to test using multiple Python versions. Pull request [#27](https://github.com/Python-SIP/sip/pull/27) ## v6.8.5 ### Missing dependency in `pyproject.toml` Added `setuptools` as a project dependency. Resolves [#26](https://github.com/Python-SIP/sip/issues/26) ### V6.8.4 release notes are incorrect The incorrect entries in the v6.8.4 release notes regarding the latest ABI version numbers were removed. Resolves [#24](https://github.com/Python-SIP/sip/issues/24) ## v6.8.4 ### Added support for Python v3.13 Python v3.13 raises the minimum macOS version to 10.13. Ensure that this minimum is used for wheel names for projects where all modules use the limited ABI, no matter what the minimum requirement of the version of Python being used to build the wheel is. Eliminated all compiler warnings on all platforms when building the `sip` module. Removed calls to all deprecated parts of the Python API. Resolves [#22](https://github.com/Python-SIP/sip/issues/22) ### Make all tools accessable using `python -m` `sip-distinfo` can now also be run using `python -m sipbuild.tools.distinfo`. `sip-module` can now also be run using `python -m sipbuild.tools.module`. Resolves [#21](https://github.com/Python-SIP/sip/issues/21) ### Assume C99 support `_Bool` and `stdbool.h` are assumed to be available on all supported platforms. `va_copy` is assumed to be available on all supported platforms. Resolves [#13](https://github.com/Python-SIP/sip/issues/13) ### Object map incorrect size assumptions for Windows 64 `uintptr_t` is now used as the hash key in the object map rather than `unsigned long`. Resolves [#14](https://github.com/Python-SIP/sip/issues/14) ### `%MappedType` documentation error The documentation for the `MappedType` directive incorrectly stated that the type was `type` rather than `base-type`. Resolves [#10](https://github.com/Python-SIP/sip/issues/10) ### Missing import affecting XML generation The generation of XML (used by PyQt documentation) failed because of a missing import. Resolves [#18](https://github.com/Python-SIP/sip/issues/18) ### Fixed the generation of module-level attributes This is a regression in SIP v6.8 and only affects attributes defined in hidden namespaces. Resolves [#19](https://github.com/Python-SIP/sip/issues/19) ### Documentation updates The documentation is now hosted at [Read the Docs](https://python-sip.readthedocs.io). Resolves [#2](https://github.com/Python-SIP/sip/issues/2) ### Completion of the migration from Mercurial SIP is now licensed under the BSD-2-Clause license. `README.md` now reflects the state of the migration. The project has now been migrated from `setup.py` to `setuptools_scm` and `pyproject.toml`. Resolves [#1](https://github.com/Python-SIP/sip/issues/1) sip-6.8.6/docs/requirements.in000066400000000000000000000001701464421045000163270ustar00rootroot00000000000000# Rin `pip-compile requirements.in` to create requirements.txt sphinx==7.2.6 sphinx_rtd_theme==2.0.0 myst_parser==2.0.0 sip-6.8.6/docs/requirements.txt000066400000000000000000000026611464421045000165470ustar00rootroot00000000000000# # This file is autogenerated by pip-compile with Python 3.12 # by the following command: # # pip-compile requirements.in # alabaster==0.7.16 # via sphinx babel==2.14.0 # via sphinx certifi==2023.11.17 # via requests charset-normalizer==3.3.2 # via requests docutils==0.20.1 # via # myst-parser # sphinx # sphinx-rtd-theme idna==3.6 # via requests imagesize==1.4.1 # via sphinx jinja2==3.1.3 # via # myst-parser # sphinx markdown-it-py==3.0.0 # via # mdit-py-plugins # myst-parser markupsafe==2.1.4 # via jinja2 mdit-py-plugins==0.4.0 # via myst-parser mdurl==0.1.2 # via markdown-it-py myst-parser==2.0.0 # via -r requirements.in packaging==23.2 # via sphinx pygments==2.17.2 # via sphinx pyyaml==6.0.1 # via myst-parser requests==2.31.0 # via sphinx snowballstemmer==2.2.0 # via sphinx sphinx==7.2.6 # via # -r requirements.in # myst-parser # sphinx-rtd-theme # sphinxcontrib-jquery sphinx-rtd-theme==2.0.0 # via -r requirements.in sphinxcontrib-applehelp==1.0.8 # via sphinx sphinxcontrib-devhelp==1.0.6 # via sphinx sphinxcontrib-htmlhelp==2.0.5 # via sphinx sphinxcontrib-jquery==4.1 # via sphinx-rtd-theme sphinxcontrib-jsmath==1.0.1 # via sphinx sphinxcontrib-qthelp==1.0.7 # via sphinx sphinxcontrib-serializinghtml==1.1.10 # via sphinx urllib3==2.1.0 # via requests sip-6.8.6/docs/sipbuild_api.rst000066400000000000000000000706041464421045000164630ustar00rootroot00000000000000.. py:module:: sipbuild :synopsis: The SIP build system. :py:mod:`sipbuild` Module Reference =================================== The :py:mod:`sipbuild` module implements the SIP build system. The module may be used by project-specific :program:`project.py` scripts or extended by other build systems. .. note:: Most of the classes described here have additional methods and attributes. Unless they are documented here they are **not** part of the public API and may be changed in any future release. .. py:data:: SIP_VERSION :type: int The major.minor.patch version number encoded as an integer. For example v6.5.0 would be encoded as 0x060500. .. py:data:: SIP_VERSION_STR :type: str The version number as it should be displayed to the user. :py:class:`~sipbuild.AbstractBuilder` ------------------------------------- .. py:class:: AbstractBuilder(project, **kwargs) An abstract class that defines the API of a builder. :param Project project: is the project. :param \*\*kwargs: are keyword arguments that define the initial values of any corresponding :py:class:`~sipbuild.Option` defined by the builder. An :py:class:`~sipbuild.Option` value set in this way cannot be overridden in the :file:`pyproject.toml` file or by using a tool command line option. .. py:method:: build() :abstractmethod: Build the project but do not install it. .. py:method:: build_sdist(sdist_directory) :abstractmethod: Build an sdist for the project. :param str sdist_directory: is the name of the directory in which the sdist is created. :return: the name of the sdist file (excluding any path). .. py:method:: build_wheel(wheel_directory) :abstractmethod: Build a wheel for the project. :param str wheel_directory: is the name of the directory in which the wheel is created. :return: the name of the wheel file (excluding any path). .. py:method:: install() :abstractmethod: Build and install the project. .. py:attribute:: project The :py:class:`~sipbuild.Project` object. :py:class:`~sipbuild.AbstractProject` ------------------------------------- .. py:class:: AbstractProject An abstract class that defines the API of a project. .. py:method:: build() :abstractmethod: Build the project but do not install it. .. py:method:: build_sdist(sdist_directory) :abstractmethod: Build an sdist for the project. :param str sdist_directory: is the name of the directory in which the sdist is created. :return: the name of the sdist file (excluding any path). .. py:method:: build_wheel(wheel_directory) :abstractmethod: Build a wheel for the project. :param str wheel_directory: is the name of the directory in which the wheel is created. :return: the name of the wheel file (excluding any path). .. py:method:: import_callable(name, base_type) :staticmethod: Import a callable from a script or module. The callable is identified either by its name (if specified) or its type. :param str name: is a script (with a :file:`.py` extension), a module or the name of an object in a module (specified as ``module:name``). :param type base_type: is the type of the callable and is ignored if the name of the callable is explicitly specified. :return: the callable. .. py:method:: install() :abstractmethod: Build and install the project. .. py:method:: setup(pyproject, tool, tool_description) :abstractmethod: Setup the project from the :file:`pyproject.toml` file. :param PyProject pyproject: is the parsed :file:`pyproject.toml` file. :param str tool: is the name of the tool, either ``'build'``, ``'install'``, ``'sdist'`` or ``'wheel'``. :param str tool_description: is a short description of the tool. :py:class:`~sipbuild.Bindings` ------------------------------ .. py:class:: Bindings(project, name, **kwargs) The encapsulation of a set of bindings. :param Project project: is the project. :param str name: is the name of the bindings. :param \*\*kwargs: are keyword arguments that define the initial values of any corresponding :py:class:`~sipbuild.Option` defined by the bindings. An :py:class:`~sipbuild.Option` value set in this way cannot be overridden in the :file:`pyproject.toml` file or by using a tool command line option. .. py:method:: apply_nonuser_defaults(tool) Called by the bindings to set the default values of any non-user options (i.e. those that cannot be set from a tool command line). If it is re-implemented in a sub-class then the super-class version should be called. :param str tool: is the name of the tool being used. .. py:method:: apply_user_defaults(tool) Called by the bindings to set the default values of any user options (i.e. those that can be set from a tool command line). If it is re-implemented in a sub-class then the super-class version should be called. :param str tool: is the name of the tool being used. .. py:method:: generate() Called by the project to generate the source code of the bindings and return a :py:class:`~sipbuild.BuildableBindings` object containing the details needed by the builder to build the bindings. :return: the :py:class:`~sipbuild.BuildableBindings` object. .. py:method:: get_options() Called by the bindings to get the list of the bindings's options. If it is re-implemented in a sub-class then the super-class version should be called. :return: the list of :py:class:`~sipbuild.Option` objects. .. py:method:: is_buildable() Called by the builder to determine if the bindings are buildable. This will not be called if the bindings have been explicitly enabled. The default implementation returns ``True``. :return: ``True`` if the bindings are buildable. .. py:attribute:: project The :py:class:`~sipbuild.Project` object. :py:class:`~sipbuild.Buildable` ------------------------------- .. py:class:: Buildable(project, name) Encapsulate a generic buildable. :param Project project: is the project. :param str name: is the name of the buildable. .. py:attribute:: build_dir The name of the buildable-specific build directory. This will be created automatically. .. py:attribute:: build_settings A list of values that are passed to the builder. It is up to the builder to determine how these values are used. .. py:attribute:: installables The list of :py:class:`~sipbuild.Installable` objects created by the builder to describe what was built. .. py:attribute:: name The name of the buildable. .. py:attribute:: project The :py:class:`~sipbuild.Project` object. :py:class:`~sipbuild.BuildableBindings` --------------------------------------- .. py:class:: BuildableBindings(bindings, fq_name, *, uses_limited_api=False) A :py:class:`~sipbuild.BuildableModule` sub-class that encapsulates the Python extension module for a set of bindings. :param Bindings bindings: is the bindings. :param str fq_name: is the fully qualified name of the bindings module. :param bool uses_limited_api: is ``True`` if the source code uses only the limited Python API. .. py:attribute:: bindings The :py:class:`~sipbuild.Bindings` object. :py:class:`~sipbuild.BuildableExecutable` ----------------------------------------- .. py:class:: BuildableExecutable(project, name, target, *, uses_limited_api=False) A :py:class:`~sipbuild.BuildableFromSources` sub-class that encapsulates an executable. :param Project project: is the project. :param str name: is the name of the buildable. :param str target: is the platform-independent name of the executable being built. :param bool uses_limited_api: is ``True`` if the source code uses only the limited Python API. :py:class:`~sipbuild.BuildableFromSources` ------------------------------------------ .. py:class:: BuildableFromSources(project, name, target, *, uses_limited_api=False) A :py:class:`~sipbuild.Buildable` sub-class that encapsulates a target that is built from source code. :param Project project: is the project. :param str name: is the name of the buildable. :param str target: is the name of the target being built. :param bool uses_limited_api: is ``True`` if the source code uses only the limited Python API. .. py:attribute:: debug ``True`` if a build with debugging symbols should be performed. .. py:attribute:: define_macros The list of ``#define`` names and values in the form ``"NAME"`` or ``"NAME=VALUE"``. .. py:attribute:: headers The list of :file:`.h` header files. .. py:attribute:: include_dirs The list of directories that will be searched, in additional to the standard system directores, for :file:`.h` header files. .. py:attribute:: libraries The list of libraries to link the source code with. .. py:attribute:: library_dirs The list of directories that will be searched, in addition to the standard system directories, for any libraries. .. py:method:: make_names_relative() Make all the file names relative to the build directory. This isn't necessary but can make any build files easier to read by the user. .. py:attribute:: sources The list of source files. .. py:attribute:: target The name of the target being built. .. py:attribute:: uses_limited_api ``True`` if the source code uses only the limited Python API. :py:class:`~sipbuild.BuildableModule` ------------------------------------- .. py:class:: BuildableModule(project, name, fq_name, *, uses_limited_api=False) A :py:class:`~sipbuild.BuildableFromSources` sub-class that encapsulates a Python extension module. :param Project project: is the project. :param str name: is the name of the buildable. :param str fq_name: is the fully qualified name of the module. :param bool uses_limited_api: is ``True`` if the source code uses only the limited Python API. .. py:attribute:: exceptions ``True`` if the module should be built with support for C++ exceptions. .. py:attribute:: fq_name The fully qualified name of the module. .. py:method:: get_install_subdir() Get the name of the sub-directory (relative to any future target installation directory) that the module should be installed in. :return: the name of the sub-directory. .. py:method:: get_module_extension() Get the platform-specific file name extension that a module should have. :return: the extension. .. py:attribute:: static ``True`` if the module should be built as a static library. :py:class:`~sipbuild.Builder` ----------------------------- .. py:class:: Builder(project, **kwargs) The default base implementation of a builder. :param Project project: is the project :param \*\*kwargs: are keyword arguments that define the initial values of any corresponding :py:class:`~sipbuild.Option` defined by the builder. An :py:class:`~sipbuild.Option` value set in this way cannot be overridden in the :file:`pyproject.toml` file or by using a tool command line option. .. py:method:: apply_nonuser_defaults(tool) Called by the builder to set the default values of any non-user options (i.e. those that cannot be set from a tool command line). If it is re-implemented in a sub-class then the super-class version should be called. :param str tool: is the name of the tool being used. .. py:method:: apply_user_defaults(tool) Called by the builder to set the default values of any user options (i.e. those that can be set from a tool command line). If it is re-implemented in a sub-class then the super-class version should be called. :param str tool: is the name of the tool being used. .. py:method:: build_executable(buildable, *, fatal=True) :abstractmethod: Build an executable from a buildable. :param BuildableExecutable buildable: is the buildable. :param bool fatal: is ``True`` if a :py:exc:`~sipbuild.UserException` should be raised if the build failed. :return: the relative path name of the built executable. .. py:method:: build_project(target_dir, *, wheel_tag=None) :abstractmethod: Build the project either to be installed for use or to create a wheel. :param str target_dir: is the directory in which the project will be installed in. :param str wheel_tag: is the wheel tag if a wheel is being created. .. py:method:: get_options() Called by the builder to get the list of the builder's options. If it is re-implemented in a sub-class then the super-class version should be called. :return: the list of :py:class:`~sipbuild.Option` objects. .. py:method:: install_project(target_dir, *, wheel_tag=None) :abstractmethod: Install a built project either for use or to create a wheel. :param str target_dir: is the directory in which the project will be installed in. :param str wheel_tag: is the wheel tag if a wheel is being created. :py:class:`~sipbuild.DistutilsBuilder` -------------------------------------- .. py:class:: DistutilsBuilder(project, **kwargs) A :py:class:`~sipbuild.Builder` that uses the Python :py:mod:`distutils` package to perform builds. This is the default builder for Python v3.9 and earlier. :param Project project: is the project. :param \*\*kwargs: are keyword arguments that define the initial values of any corresponding :py:class:`~sipbuild.Option` defined by the builder. An :py:class:`~sipbuild.Option` value set in this way cannot be overridden in the :file:`pyproject.toml` file or by using a tool command line option. :py:func:`~sipbuild.handle_exception` ------------------------------------- .. py:function:: handle_exception(e) Handle an exception by displaying an appropriate error message to ``stdout``. The process is then terminated with a non-zero exit code. :param exception e: is the exception to be handled. :py:class:`~sipbuild.Installable` --------------------------------- .. py:class:: Installable(name, *, target_subdir=None) Encapsulate a list of files that will be installed in the same directory. :param str name: is the name of the installable. :param str target_subdir: is the relative path name of a sub-directory in which the installable's files will be installed. If it is an absolute path name then it is used as the eventual full target directory. .. py:attribute:: files The list of file names to be installed. .. py:method:: get_full_target_dir(target_dir) Get the full path name of the directory where the installable's file will be installed. :param str target_dir: is the name of target directory. :return: the full path name of the sub-directory within the target directory where the files will be installed. .. py:method:: install(target_dir, installed, *, do_install=True) Install the installable's files in a target directory. :param str target_dir: is the name of the target directory. :param list[str] installed: is a list of installed files which is updated with the newly installed files. The list is always updated even if the files are not actually installed. :param bool do_install: is ``True`` if the files are actually to be installed. .. py:attribute:: name The name of the installable. .. py:attribute:: target_subdir The name of the target sub-directory. :py:class:`~sipbuild.Option` ---------------------------- .. py:class:: Option(name, *, option_type=str, choices=None, default=None, help=None, metavar=None, inverted=False, tools=None) Encapsulate a configurable option. Option values may be specified in code, in the :file:`pyproject.toml` file or on the command line of SIP's tools. The value of an option is accessed as an attribute of the object for which the option is defined. :param str name: is the name of the option. Any '_' in the name will be replaced by '-' in the context of a :file:`pyproject.toml` key or a command line option. :param type option_type: is the type of the value, either ``bool``, ``int``, ``list`` or ``str`` (the default). :param list choices: is a list of values that are the valid for the option. :param default: is the default value. :param str help: is the short help text. This must be specified if the option is to be used as a tool command line option. :param str metavar: is the name of the option's value when used in tool usage messages. :param bool inverted: is ``True`` if, when used as a tool command line option, the name should be preceded by ``no-``. :param list[str] tools: is the list of tools that use the option as a command line option. If it isn't specified then the list of build tools is used, i.e. ``['build', 'install', 'wheel']``. :py:class:`~sipbuild.Project` ----------------------------- .. py:class:: Project(**kwargs) The default implementation of a project. It has an associated builder which it uses to build a set of buildables. Building a buildable may create one or more installables. :param \*\*kwargs: are keyword arguments that define the initial values of any corresponding :py:class:`~sipbuild.Option` defined by the project. An :py:class:`~sipbuild.Option` value set in this way cannot be overridden in the :file:`pyproject.toml` file or by using a tool command line option. .. py:method:: apply_nonuser_defaults(tool) Called by the project to set the default values of any non-user options (i.e. those that cannot be set from a tool command line). If it is re-implemented in a sub-class then the super-class version should be called. :param str tool: is the name of the tool being used. .. py:method:: apply_user_defaults(tool) Called by the project to set the default values of any user options (i.e. those that can be set from a tool command line). If it is re-implemented in a sub-class then the super-class version should be called. :param str tool: is the name of the tool being used. .. py:attribute:: bindings The :py:class:`~collections.OrderedDict` of :py:class:`~sipbuild.Bindings` objects keyed by the name of the bindings. .. py:attribute:: bindings_factories The list of bindings factories which when called will return a :py:class:`~sipbuild.Bindings` object. There may or may not be a corresponding section in the :file:`pyproject.toml` file. .. py:attribute:: builder The :py:class:`~sipbuild.AbstractBuilder` implementation that the project uses to build buildables. .. py:attribute:: buildables The list of :py:class:`~sipbuild.Buildable` objects that the project will use the builder to build. .. py:method:: get_distinfo_dir(target_dir) Get the path name of the project's :file:`.dist-info` directory. :param str target_dir: is the name of the directory that should contain the :file:`.dist-info` directory. :return: the path name of the :file:`.dist-info` directory. .. py:method:: get_dunder_init() Called by the project to get the contents of of the top-level :file:`__init__.py` file to install. The default implementation returns an empty string. :return: the contents of the :file:`__init__.py` file. .. py:method:: get_metadata_overrides() Called by the project to get a mapping of `PEP 566 `__ metadata names and values that will override any corresponding values defined in the pyproject.toml file. A typical use is to determine a project's version dynamically. :return: the mapping. .. py:method:: get_options() Called by the project to get the list of the project's options. If it is re-implemented in a sub-class then the super-class version should be called. :return: the list of :py:class:`~sipbuild.Option` objects. .. py:method:: get_platform_tag() Get the platform tag to use in a wheel name. This default implementation uses the platform name and applies PEP defined conventions depending on OS version and GLIBC version as appropriate. :return: the platform tag. .. py:method:: get_requires_dists() Get the list of any implicit ``requires-dist`` expressions that should be added to any explicit expressions specified in the ``[tool.sip.metadata]`` section of the :file:`pyproject.toml` file. :return: the list of ``requires-dist`` expressions. .. py:method:: get_sip_distinfo_command_line(sip_distinfo, inventory, generator=None, wheel_tag=None, generator_version=None) Get a sequence of command line arguments to invoke :program:`sip-distinfo`. The :option:`sip-distinfo --console-script`, :option:`sip-distinfo --gui-script`, :option:`sip-distinfo --metadata`, :option:`sip-distinfo --prefix`, :option:`sip-distinfo --project-root` and :option:`sip-distinfo --requires-dist` command line options are handled automatically. The arguments do not contain the name of the :file:`.dist-info` directory to create. :param str sip_distinfo: is the name of the :program:`sip-distinfo` executable. :param str inventory: is the value of the :option:`sip-distinfo --inventory` command line option. :param str generator: is the value of the :option:`sip-distinfo --generator` command line option. :param str wheel_tag: is the value of the :option:`sip-distinfo --wheel-tag` command line option. :param str generator_version: is the value of the :option:`sip-distinfo --generator-version` command line option. :return: the sequence of command line arguments. .. py:attribute:: installables The list of :py:class:`~sipbuild.Installable` objects that the project will use the builder to install. .. py:method:: open_for_writing(fname) :staticmethod: Open a text file for writing. This is a wrapper around :c:func:`open` that handles common user errors. :param str fname: is the name of the file. :return: the open file object. .. py:method:: progress(message) A progress message is written to ``stdout`` if progress messages have not been disabled. If the message does not end with ``.`` then ``...`` is appended. :param str message: is the text of the message. .. py:method:: project_path(path, relative_to=None) A file or directory path, possibly using POSIX separators and possibly relative to another directory is converted to an absolute path with native separators. :param str path: is the path. :param str relative_to: is the absolute path of a directory that the supplied path is relative to. The default is the project directory. :return: the converted path. .. py:method:: read_command_pipe(args, *, and_stderr=False, fatal=True) Create a generator that will return each line of a command's ``stdout``. :param list[str] args: is the list of arguments that make up the command. :param bool and_stderr: is ``True`` if the output from ``stderr`` should be included. :param bool fatal: is ``True`` if a :py:exc:`~sipbuild.UserException` should be raised if the command returns a non-zero exit code. :return: the generator. .. py:method:: run_command(args, *, fatal=True) Run a command and display any output from ``stdout`` or ``stderr`` if verbose progress messages are enabled. :param list[str] args: is the list of arguments that make up the command. :param bool fatal: is ``True`` if a :py:exc:`~sipbuild.UserException` should be raised if the command returns a non-zero exit code. .. py:attribute:: root_dir The name of the directory containing the :file:`pyproject.toml` file. .. py:method:: update(tool) Called by the project to carry out any required updates to the project. The current directory will be the build directory. The default implementation will call :meth:`~sipbuild.Project.update_buildable_bindings` if the tool is a build tool. :param str tool: is the name of the tool being used. .. py:method:: update_buildable_bindings() Update :py:attr:`~sipbuild.Project.bindings` to ensure all bindings are buildable or have been explicitly enabled. :py:class:`~sipbuild.PyProject` ------------------------------- .. py:class:: PyProject An encapsulation of a parsed :file:`pyproject.toml` file. .. py:method:: get_metadata() Get an :py:class:`~collections.OrderedDict` containing the contents of the ``[tool.sip.metadata]`` section. The ``name``, ``version``, ``metadata-version`` and ``requires-python`` keys will be defined. :return: the meta-data. .. py:method:: get_section(section_name: str, *, required=False) Get a section as either an :py:class:`~collections.OrderedDict`, if the section is a table, or a ``list`` if the section is a list. :param str section_name: is the name of the section. :param bool required: is ``True`` if the section must be defined. :return: the section. :py:exc:`~sipbuild.PyProjectOptionException` -------------------------------------------- .. py:exception:: PyProjectOptionException(name, text, *, section_name=None, detail=None) The exception raised to describe an error with a particular option (i.e. key/value) in a particular section of a :file:`pyproject.toml` file. :param str name: is the name of the option. :param str text: is the text describing the error. :param str section_name: is the name of the section, defaulting to ``[tool.sip.project]``. :param str detail: is additional detail about the error. :py:exc:`~sipbuild.PyProjectUndefinedOptionException` ----------------------------------------------------- .. py:exception:: PyProjectUndefinedOptionException(name, *, section_name=None) The exception raised to when a particular option (i.e. key/value) in a particular section of a :file:`pyproject.toml` file has not been defined. :param str name: is the name of the option. :param str section_name: is the name of the section, defaulting to ``[tool.sip.project]``. :py:class:`~sipbuild.SetuptoolsBuilder` --------------------------------------- .. py:class:: SetuptoolsBuilder(project, **kwargs) A :py:class:`~sipbuild.Builder` that uses the Python :py:mod:`setuptools` package to perform builds. This is the default builder for Python v3.10 and later. :param Project project: is the project. :param \*\*kwargs: are keyword arguments that define the initial values of any corresponding :py:class:`~sipbuild.Option` defined by the builder. An :py:class:`~sipbuild.Option` value set in this way cannot be overridden in the :file:`pyproject.toml` file or by using a tool command line option. :py:exc:`~sipbuild.UserException` --------------------------------- .. py:exception:: UserException(text, *, detail=None) The exception raised to describe an anticipated error to the user. :param str text: is the text describing the error. :param str detail: is additional detail about the error. sip-6.8.6/docs/specification_files.rst000066400000000000000000000437701464421045000200250ustar00rootroot00000000000000.. _ref-specification: Specification Files =================== A SIP specification consists of some C/C++ type and function declarations and some :ref:`directives `. The declarations may contain :ref:`annotations ` which provide SIP with additional information that cannot be expressed in C/C++. SIP does not implement a full C/C++ parser. Syntax Definition ----------------- The following is a semi-formal description of the syntax of a specification file. .. parsed-literal:: *specification* ::= {*module-statement*} *module-statement* ::= [*module-directive* | *statement*] *module-directive* ::= [ :directive:`%CompositeModule` | :directive:`%Copying` | :directive:`%DefaultDocstringFormat` | :directive:`%DefaultDocstringSignature` | :directive:`%DefaultEncoding` | :directive:`%DefaultMetatype` | :directive:`%DefaultSupertype` | :directive:`%ExportedHeaderCode` | :directive:`%ExportedTypeHintCode` | :directive:`%Extract` | :directive:`%Feature` | :directive:`%HideNamespace` | :directive:`%Import` | :directive:`%Include` | :directive:`%InitialisationCode` | :directive:`%License` | :directive:`%MappedType` | :directive:`%Module` | :directive:`%ModuleCode` | :directive:`%ModuleHeaderCode` | :directive:`%Platforms` | :directive:`%PreInitialisationCode` | :directive:`%PostInitialisationCode` | :directive:`%Timeline` | :directive:`%TypeHintCode` | :directive:`%UnitCode` | :directive:`%UnitPostIncludeCode` | :directive:`%VirtualErrorHandler` | *mapped-type-template*] *statement* :: [*class-statement* | *function* | *variable*] *class-statement* :: [ :directive:`%If` | *class* | *class-template* | *enum* | *namespace* | *opaque-class* | *operator* | *struct* | *union* | *typedef* | *exception*] *class* ::= **class** *name* [**:** *super-classes*] [*class-annotations*] **{** {*class-line*} **};** *super-classes* ::= [**public** | **protected** | **private**] *name* [**,** *super-classes*] *class-line* ::= [ *class-statement* | :directive:`%BIGetBufferCode` | :directive:`%BIReleaseBufferCode` | :directive:`%ConvertToSubClassCode` | :directive:`%ConvertToTypeCode` | :directive:`%Docstring` | :directive:`%FinalisationCode` | :directive:`%GCClearCode` | :directive:`%GCTraverseCode` | :directive:`%InstanceCode` | :directive:`%PickleCode` | :directive:`%TypeCode` | :directive:`%TypeHeaderCode` | :directive:`%TypeHintCode` | *constructor* | *destructor* | *method* | *static-method* | *virtual-method* | *special-method* | *operator* | *virtual-operator* | *class-variable* | **public:** | **public Q_SLOTS:** | **public slots:** | **protected:** | **protected Q_SLOTS:** | **protected slots:** | **private:** | **private Q_SLOTS:** | **private slots:** | **Q_SIGNALS:** | **signals:**] *constructor* ::= [**explicit**] *name* **(** [*argument-list*] **)** [**noexcept**] [*function-annotations*] [*c++-constructor-signature*] **;** [:directive:`%Docstring`] [:directive:`%MethodCode`] *c++-constructor-signature* ::= **[(** [*argument-list*] **)]** *destructor* ::= [**virtual**] **~** *name* **()** [**noexcept**] [**= 0**] [*function-annotations*] **;** [:directive:`%MethodCode`] [:directive:`%VirtualCatcherCode`] *method* ::= [**Q_SIGNAL**] [**Q_SLOT**] *type* *name* **(** [*argument-list*] **)** [**const**] [**final**] [**noexcept**] [**= 0**] [*function-annotations*] [*c++-signature*] **;** [:directive:`%Docstring`] [:directive:`%MethodCode`] *c++-signature* ::= **[** *type* **(** [*argument-list*] **)]** *static-method* ::= **static** *function* *virtual-method* ::= [**Q_SIGNAL**] [**Q_SLOT**] **virtual** *type* *name* **(** [*argument-list*] **)** [**const**] [**final**] [**noexcept**] [**= 0**] [*function-annotations*] [*c++-signature*] **;** [:directive:`%MethodCode`] [:directive:`%VirtualCatcherCode`] [:directive:`%VirtualCallCode`] *special-method* ::= *type* *special-method-name* **(** [*argument-list*] **)** [*function-annotations*] **;** [:directive:`%MethodCode`] *special-method-name* ::= [**__abs__** | **__add__** | **__and__** | **__aiter__** | **__anext__** | **__await__** | **__bool__** | **__call__** | **__contains__** | **__delattr__** | **__delitem__** | **__div__** | **__eq__** | **__float__** | **__floordiv__** | **__ge__** | **__getattr__** | **__getattribute__** | **__getitem__** | **__gt__** | **__hash__** | **__iadd__** | **__iand__** | **__idiv__** | **__ifloordiv__** | **__ilshift__** | **__imatmul__** | **__imod__** | **__imul__** | **__index__** | **__int__** | **__invert__** | **__ior__** | **__irshift__** | **__isub__** | **__iter__** | **__itruediv__** | **__ixor__** | **__le__** | **__len__** | **__lshift__** | **__lt__** | **__matmul** | **__mod__** | **__mul__** | **__ne__** | **__neg__** | **__next__** | **__or__** | **__pos__** | **__repr__** | **__rshift__** | **__setattr__** | **__setitem__** | **__str__** | **__sub__** | **__truediv__** | **__xor__**] *operator* ::= *operator-type* **(** [*argument-list*] **)** [**const**] [**final**] [**noexcept**] [*function-annotations*] **;** [:directive:`%MethodCode`] *virtual-operator* ::= **virtual** *operator-type* **(** [*argument-list*] **)** [**const**] [**final**] [**noexcept**] [**= 0**] [*function-annotations*] **;** [:directive:`%MethodCode`] [:directive:`%VirtualCatcherCode`] [:directive:`%VirtualCallCode`] *operatator-type* ::= [ *operator-function* | *operator-cast* ] *operator-function* ::= *type* **operator** *operator-name* *operator-cast* ::= **operator** *type* *operator-name* ::= [**+** | **-** | ***** | **/** | **%** | **&** | **|** | **^** | **<<** | **>>** | **+=** | **-=** | ***=** | **/=** | **%=** | **&=** | **|=** | **^=** | **<<=** | **>>=** | **~** | **()** | **[]** | **<** | **<=** | **==** | **!=** | **>** | **>>=** | **=**] *class-variable* ::= [**static**] *variable* *class-template* :: = **template** **<** *type-list* **>** *class* *mapped-type-template* :: = **template** **<** *type-list* **>** :directive:`%MappedType` *enum* ::= **enum** [*enum-key*] [*name*] [*enum-annotations*] **{** {*enum-line*} **};** *enum-key* ::= [**class** | **struct**] *enum-line* ::= [:directive:`%If` | *name* [*enum-annotations*] **,** *function* ::= *type* *name* **(** [*argument-list*] **)** [**noexcept**] [*function-annotations*] **;** [:directive:`%Docstring`] [:directive:`%MethodCode`] *namespace* ::= **namespace** *name* [**{** {*namespace-line*} **}**] **;** *namespace-line* ::= [:directive:`%TypeHeaderCode` | *statement*] *opaque-class* ::= **class** *scoped-name* **;** *struct* ::= **struct** *name* **{** {*class-line*} **};** *union* ::= **union** *name* **{** {*class-line*} **};** *typedef* ::= **typedef** [*typed-name* | *function-pointer*] *typedef-annotations* **;** *variable*::= *typed-name* [*variable-annotations*] **;** [:directive:`%AccessCode`] [:directive:`%GetCode`] [:directive:`%SetCode`] *exception* ::= :directive:`%Exception` *exception-name* [*exception-base*] **{** [:directive:`%TypeHeaderCode`] :directive:`%RaiseCode` **};** *exception-name* ::= *scoped-name* *exception-base* ::= **(** [*exception-name* | *python-exception*] **)** *python-exception* ::= [**SIP_ArithmeticError** | **SIP_AssertionError** | **SIP_AttributeError** | **SIP_BaseException** | **SIP_BlockingIOError** | **SIP_BrokenPipeError** | **SIP_BufferError** | **SIP_ChildProcessError** | **SIP_ConnectionAbortedError** | **SIP_ConnectionError** | **SIP_ConnectionRefusedError** | **SIP_ConnectionResetError** | **SIP_EnvironmentError** | **SIP_EOFError** | **SIP_Exception** | **SIP_FileExistsError** | **SIP_FileNotFoundError** | **SIP_FloatingPointError** | **SIP_GeneratorExit** | **SIP_ImportError** | **SIP_IndentationError** | **SIP_IndexError** | **SIP_InterruptedError** | **SIP_IOError** | **SIP_IsADirectoryError** | **SIP_KeyboardInterrupt** | **SIP_KeyError** | **SIP_LookupError** | **SIP_MemoryError** | **SIP_NameError** | **SIP_NotADirectoryError** | **SIP_NotImplementedError** | **SIP_OSError** | **SIP_OverflowError** | **SIP_PermissionError** | **SIP_ProcessLookupError** | **SIP_ReferenceError** | **SIP_RuntimeError** | **SIP_StandardError** | **SIP_StopIteration** | **SIP_SyntaxError** | **SIP_SystemError** | **SIP_SystemExit** | **SIP_TabError** | **SIP_TimeoutError** | **SIP_TypeError** | **SIP_UnboundLocalError** | **SIP_UnicodeDecodeError** | **SIP_UnicodeEncodeError** | **SIP_UnicodeError** | **SIP_UnicodeTranslateError** | **SIP_ValueError** | **SIP_VMSError** | **SIP_WindowsError** | **SIP_ZeroDivisionError** | **SIP_Warning** | **SIP_BytesWarning** | **SIP_DeprecationWarning** | **SIP_FutureWarning** | **SIP_ImportWarning** | **SIP_PendingDeprecationWarning** | **SIP_ResourceWarning** | **SIP_RuntimeWarning** | **SIP_SyntaxWarning** | **SIP_UnicodeWarning** | **SIP_UserWarning**] *argument-list* ::= *argument* [**,** *argument-list*] [**,** **...**] *argument* ::= *type* [*name*] [*argument-annotations*] [*default-value*] *default-value* ::= **=** *expression* *expression* ::= [*value* | *value* *binary-operator* *expression*] *value* ::= [*unary-operator*] *simple-value* *simple-value* ::= [*scoped-name* | *function-call* | *real-value* | *integer-value* | *boolean-value* | *string-value* | *character-value*] *typed-name*::= *type* *name* *function-pointer*::= *type* **(*** *name* **)(** [*type-list*] **)** *type-list* ::= *type* [**,** *type-list*] *function-call* ::= *scoped-name* **(** [*value-list*] **)** *value-list* ::= *value* [**,** *value-list*] *real-value* ::= a floating point number *integer-value* ::= a number *boolean-value* ::= [**true** | **false**] *string-value* ::= **"** {*character*} **"** *character-value* ::= **'** *character* **'** *unary-operator* ::= [**!** | **~** | **-** | **+** | **\*** | **&**] *binary-operator* ::= [**-** | **+** | ***** | **/** | **&** | **|**] *argument-annotations* ::= see :ref:`ref-arg-annos` *class-annotations* ::= see :ref:`ref-class-annos` *enum-annotations* ::= see :ref:`ref-enum-annos` *function-annotations* ::= see :ref:`ref-function-annos` *typedef-annotations* ::= see :ref:`ref-typedef-annos` *variable-annotations* ::= see :ref:`ref-variable-annos` *type* ::= [**const**] *base-type* {*****} [**&**] *type-list* ::= *type* [**,** *type-list*] *base-type* ::= [*scoped-name* | *template* | **struct** *scoped-name* | **union** *scoped-name* | **char** | **signed char** | **unsigned char** | **wchar_t** | **int** | **unsigned** | **unsigned int** | **size_t** | **short** | **unsigned short** | **long** | **unsigned long** | **long long** | **unsigned long long** | **float** | **double** | **bool** | **void** | **Py_hash_t** | **Py_ssize_t** | **PyObject** | :stype:`SIP_PYBUFFER` | :stype:`SIP_PYCALLABLE` | :stype:`SIP_PYDICT` | :stype:`SIP_PYENUM` | :stype:`SIP_PYLIST` | :stype:`SIP_PYOBJECT` | :stype:`SIP_PYSLICE` | :stype:`SIP_PYTUPLE` | :stype:`SIP_PYTYPE`] *scoped-name* ::= *name* [**::** *scoped-name*] *template* ::= *scoped-name* **<** *type-list* **>** *dotted-name* ::= *name* [**.** *dotted-name*] *name* ::= _A-Za-z {_A-Za-z0-9} Here is a short list of differences between C++ and the subset supported by SIP that might trip you up. - SIP does not support the use of ``[]`` in types. Use pointers instead. - A global ``operator`` can only be defined if its first argument is a class or a named enum that has been wrapped in the same module. - Variables declared outside of a class are effectively read-only. Variable Numbers of Arguments ----------------------------- SIP supports the use of ``...`` as the last part of a function signature. Any remaining arguments are collected as a Python tuple. Additional SIP Types -------------------- SIP supports a number of additional data types that can be used in Python signatures. .. sip-type:: SIP_PYBUFFER This is a ``PyObject *`` that implements the Python buffer protocol. .. sip-type:: SIP_PYCALLABLE This is a ``PyObject *`` that is a Python callable object. .. sip-type:: SIP_PYDICT This is a ``PyObject *`` that is a Python dictionary object. .. sip-type:: SIP_PYENUM This is a ``PyObject *`` that is a Python enum object. .. sip-type:: SIP_PYLIST This is a ``PyObject *`` that is a Python list object. .. sip-type:: SIP_PYOBJECT This is a ``PyObject *`` of any Python type. The type ``PyObject *`` can also be used. .. sip-type:: SIP_PYSLICE This is a ``PyObject *`` that is a Python slice object. .. sip-type:: SIP_PYTUPLE This is a ``PyObject *`` that is a Python tuple object. .. sip-type:: SIP_PYTYPE This is a ``PyObject *`` that is a Python type object. Python API vs. C/C++ API ------------------------ It is important to understand that a SIP specification describes the Python API, i.e. the API available to the Python programmer when they ``import`` the generated module. It does not have to accurately represent the underlying C/C++ library. There is nothing wrong with omitting functions that make little sense in a Python context, or adding functions implemented with handwritten code that have no C/C++ equivalent. It is even possible (and sometimes necessary) to specify a different super-class hierarchy for a C++ class. All that matters is that the generated code compiles properly. In most cases the Python API matches the C/C++ API. In some cases handwritten code (see :directive:`%MethodCode`) is used to map from one to the other without SIP having to know the details itself. However, there are a few cases where SIP generates a thin wrapper around a C++ method or constructor (see :ref:`ref-derived-classes`) and needs to know the exact C++ signature. To deal with these cases SIP allows two signatures to be specified. For example:: class Klass { public: // The Python signature is a tuple, but the underlying C++ signature // is a 2 element array. Klass(SIP_PYTUPLE) [(int *)]; %MethodCode int iarr[2]; if (PyArg_ParseTuple(a0, "ii", &iarr[0], &iarr[1])) { // Note that we use the SIP generated derived class // constructor. Py_BEGIN_ALLOW_THREADS sipCpp = new sipKlass(iarr); Py_END_ALLOW_THREADS } %End }; Namespaces ---------- SIP implements C++ namespaces as a Python class which cannot be instantiated. The contents of the namespace, including nested namespaces, are implemented as attributes of the class. The namespace class is created in the module that SIP is parsing when it first sees the namespace defined. If a function (for example) is defined in a namespace that is first defined in another module then the function is added to the namespace class in that other module. Say that we have a file ``a.sip`` that defines a module ``a_module`` as follows:: %Module a_module namespace N { void hello(); }; We also have a file ``b.sip`` that defines a module ``b_module`` as follows:: %Module b_module %Import a.sip namespace N { void bye(); }; When SIP parses ``b.sip`` it first sees the ``N`` namespace defined in module ``a_module``. Therefore it places the ``bye()`` function in the ``N`` Python class in the ``a_module``. It does not create an ``N`` Python class in the ``b_module``. Consequently the following code will call the ``bye()`` function:: import a_module import b_module a_module.N.bye() While this reflects the C++ usage it may not be obvious to the Python programmer who might expect to call the ``bye()`` function using:: import b_module b_module.N.bye() In order to achieve this behavior make sure that the ``N`` namespace is first defined in the ``b_module``. The following version of ``b.sip`` does this:: %Module b_module namespace N; %Import a.sip namespace N { void bye(); }; Alternatively you could just move the :directive:`%Import` directive so that it is at the end of the file. sip-6.8.6/examples/000077500000000000000000000000001464421045000141445ustar00rootroot00000000000000sip-6.8.6/examples/package/000077500000000000000000000000001464421045000155375ustar00rootroot00000000000000sip-6.8.6/examples/package/README000066400000000000000000000003331464421045000164160ustar00rootroot00000000000000Before running any of the examples in this directory you need to create and install the sip module that they all rely on. To do this, run: sip-module --sdist examples.sip pip install examples_sip-X.Y.Z.tar.gz sip-6.8.6/examples/package/core/000077500000000000000000000000001464421045000164675ustar00rootroot00000000000000sip-6.8.6/examples/package/core/README000066400000000000000000000002221464421045000173430ustar00rootroot00000000000000This is the 'root' project of the examples package (i.e. it does not import any other module in the package (other than the examples.sip module). sip-6.8.6/examples/package/core/core.sip000066400000000000000000000006301464421045000201330ustar00rootroot00000000000000// Define the SIP wrapper to the core library. %Module(name=examples.core, use_limited_api=True) %DefaultEncoding "ASCII" %Platforms {Linux macOS Windows} %If (Linux) const char *what_am_i(); %MethodCode sipRes = "Linux"; %End %End %If (macOS) const char *what_am_i(); %MethodCode sipRes = "macOS"; %End %End %If (Windows) const char *what_am_i(); %MethodCode sipRes = "Windows"; %End %End sip-6.8.6/examples/package/core/project.py000066400000000000000000000034011464421045000205050ustar00rootroot00000000000000from sipbuild import Option, Project, PyProjectOptionException class CoreProject(Project): """ A project that adds an additional configuration option and introspects the system to determine its value. """ def get_options(self): """ Return the sequence of configurable options. """ # Get the standard options. options = super().get_options() # Add our new option. options.append(Option('platform')) return options def apply_nonuser_defaults(self, tool): """ Apply any non-user defaults. """ if self.platform is None: # The option wasn't specified in pyproject.toml so we introspect # the system. from sys import platform if platform == 'linux': self.platform = 'Linux' elif platform == 'darwin': self.platform = 'macOS' elif platform == 'win32': self.platform = 'Windows' else: raise PyProjectOptionException('platform', "the '{0}' platform is not supported".format(platform)) else: # The option was set in pyproject.toml so we just verify the value. if self.platform not in ('Linux', 'macOS', 'Windows'): raise PyProjectOptionException('platform', "'{0}' is not a valid platform".format(self.platform)) # Apply the defaults for the standard options. super().apply_nonuser_defaults(tool) def update(self, tool): """ Update the project configuration. """ # Get the 'core' bindings and add the platform to the list of tags. core_bindings = self.bindings['core'] core_bindings.tags.append(self.platform) sip-6.8.6/examples/package/core/pyproject.toml000066400000000000000000000005561464421045000214110ustar00rootroot00000000000000# Specify sip v6 as the build system for the package. [build-system] requires = ["sip >=6, <7"] build-backend = "sipbuild.api" # Specify the PEP 621 metadata for the project. [project] name = "examples-core" # Specify each set of bindings. [tool.sip.bindings.core] # Configure the project itself. [tool.sip.project] sip-module = "examples.sip" dunder-init = true sip-6.8.6/examples/package/extras/000077500000000000000000000000001464421045000170455ustar00rootroot00000000000000sip-6.8.6/examples/package/extras/README000066400000000000000000000002361464421045000177260ustar00rootroot00000000000000This is a separate project to the 'examples-core' project (possibly with a different maintainer) that extends the 'extras' bindings to the 'example' package. sip-6.8.6/examples/package/extras/extras.sip000066400000000000000000000004251464421045000210710ustar00rootroot00000000000000// Define the SIP wrapper to the extras library. %Module(name=examples.extras, use_limited_api=True) %Import core/core.sip %If (!Windows) bool am_i_posix(); %MethodCode sipRes = true; %End %End %If (Windows) bool am_i_posix(); %MethodCode sipRes = false; %End %End sip-6.8.6/examples/package/extras/pyproject.toml000066400000000000000000000006001464421045000217550ustar00rootroot00000000000000# Specify sip v6 as the build system for the package. [build-system] requires = ["sip >=6, <7"] build-backend = "sipbuild.api" # Specify the PEP 621 metadata for the project. [project] name = "examples-extras" dependencies = ["examples-core"] # Specify each set of bindings. [tool.sip.bindings.extras] # Configure the project itself. [tool.sip.project] sip-module = "examples.sip" sip-6.8.6/examples/standalone/000077500000000000000000000000001464421045000162745ustar00rootroot00000000000000sip-6.8.6/examples/standalone/README000066400000000000000000000004771464421045000171640ustar00rootroot00000000000000To create and install the extension module, run: sip-install or pip install . To create and install an sdist, run:: sip-sdist pip install fib-0.1.tar.gz To create and install a wheel, run:: sip-wheel pip install fib-0.1-*.whl To uninstall the extension module, run: pip uninstall fib sip-6.8.6/examples/standalone/fib.sip000066400000000000000000000005601464421045000175520ustar00rootroot00000000000000// Define the SIP wrapper to the (theoretical) fib library. %Module(name=fib, language="C") int fib_n(int n); %MethodCode if (a0 <= 0) { sipRes = 0; } else { int a = 0, b = 1, c, i; for (i = 2; i <= a0; i++) { c = a + b; a = b; b = c; } sipRes = b; } %End sip-6.8.6/examples/standalone/pyproject.toml000066400000000000000000000003071464421045000212100ustar00rootroot00000000000000# Specify sip v6 as the build system for the package. [build-system] requires = ["sip >=6, <7"] build-backend = "sipbuild.api" # Specify the PEP 621 metadata for the project. [project] name = "fib" sip-6.8.6/pyproject.toml000066400000000000000000000016401464421045000152430ustar00rootroot00000000000000# The project configuration for sip. [build-system] requires = ["setuptools>=64", "setuptools_scm>=8"] build-backend = "setuptools.build_meta" [tool.setuptools_scm] version_file = "sipbuild/_version.py" [project] name = "sip" description = "A Python bindings generator for C/C++ libraries" readme = "README.md" urls.homepage = "https://github.com/Python-SIP/sip" dependencies = ["packaging", "setuptools", "tomli; python_version<'3.11'"] requires-python = ">=3.8" license = {file = "LICENSE"} classifiers = ["License :: OSI Approved :: BSD License"] dynamic = ["version"] [[project.authors]] name = "Phil Thompson" email = "phil@riverbankcomputing.com" [project.scripts] sip-distinfo = "sipbuild.tools.distinfo:main" sip-module = "sipbuild.tools.module:main" sip-build = "sipbuild.tools.build:main" sip-install = "sipbuild.tools.install:main" sip-sdist = "sipbuild.tools.sdist:main" sip-wheel = "sipbuild.tools.wheel:main" sip-6.8.6/sipbuild/000077500000000000000000000000001464421045000141415ustar00rootroot00000000000000sip-6.8.6/sipbuild/__init__.py000066400000000000000000000015651464421045000162610ustar00rootroot00000000000000# SPDX-License-Identifier: BSD-2-Clause # Copyright (c) 2024 Phil Thompson # Publish the public API. from .abstract_builder import AbstractBuilder from .abstract_project import AbstractProject from .bindings import Bindings from .buildable import (Buildable, BuildableBindings, BuildableExecutable, BuildableFromSources, BuildableModule) from .builder import Builder from .configurable import Option from .exceptions import handle_exception, UserException from .installable import Installable from .project import Project from .pyproject import (PyProjectOptionException, PyProjectUndefinedOptionException) from .setuptools_builder import SetuptoolsBuilder from .version import SIP_VERSION, SIP_VERSION_STR # This is deprecated so allow it to fail. try: from .distutils_builder import DistutilsBuilder except ImportError: pass sip-6.8.6/sipbuild/abstract_builder.py000066400000000000000000000017011464421045000200230ustar00rootroot00000000000000# SPDX-License-Identifier: BSD-2-Clause # Copyright (c) 2024 Phil Thompson from abc import ABC, abstractmethod from .configurable import Configurable class AbstractBuilder(Configurable, ABC): """ This specifies the API of a builder. """ def __init__(self, project, **kwargs): """ Initialise the builder. """ super().__init__() self.project = project self.initialise_options(kwargs) @abstractmethod def build(self): """ Build the project in-situ. """ @abstractmethod def build_sdist(self, sdist_directory): """ Build an sdist for the project and return the name of the sdist file. """ @abstractmethod def build_wheel(self, wheel_directory): """ Build a wheel for the project and return the name of the wheel file. """ @abstractmethod def install(self): """ Install the project. """ sip-6.8.6/sipbuild/abstract_project.py000066400000000000000000000130061464421045000200440ustar00rootroot00000000000000# SPDX-License-Identifier: BSD-2-Clause # Copyright (c) 2024 Phil Thompson from abc import ABC, abstractmethod import importlib import importlib.util import os import sys from .exceptions import set_deprecations_are_errors, UserException from .pyproject import PyProject, PyProjectOptionException class AbstractProject(ABC): """ This specifies the API of a project. """ @classmethod def bootstrap(cls, tool, tool_description='', arguments=None): """ Return an AbstractProject instance fully configured for a particular command line tool. """ # We have to do this very early. if '--deprecations-are-errors' in sys.argv: set_deprecations_are_errors(True) # Get the contents of the pyproject.toml file. pyproject = PyProject() # Get the name of the project factory. project_factory_name = None sip_section_name = 'tool.sip' value_name = 'project-factory' sip_section = pyproject.get_section(sip_section_name) if sip_section is not None: f_name = sip_section.get(value_name) if f_name is not None: if not isinstance(f_name, str): raise PyProjectOptionException(value_name, "should be a 'str' and not '{0}'".format( type(f_name).__name__), section_name=sip_section_name) project_factory_name = f_name # See if there is a corresponding callable. if project_factory_name is None: default_factory_py = 'project.py' if os.path.isfile(default_factory_py): project_factory = cls.import_callable(default_factory_py, cls) else: # The default project factory. from .project import Project as project_factory else: project_factory = cls.import_callable(project_factory_name, cls) project = project_factory() if not isinstance(project, cls): raise UserException( "The project factory did not return an AbstractProject " "object") # We set this as an attribute rather than change the API of the ctor or # setup(). project.arguments = arguments # Complete the configuration of the project. project.setup(pyproject, tool, tool_description) return project @abstractmethod def build(self): """ Build the project in-situ. """ @abstractmethod def build_sdist(self, sdist_directory): """ Build an sdist for the project and return the name of the sdist file. """ @abstractmethod def build_wheel(self, wheel_directory): """ Build a wheel for the project and return the name of the wheel file. """ @staticmethod def import_callable(name, base_type): """ Import a callable from either a .py file or specified as module[:object]. """ # See if the name refers to a .py file. if name.endswith('.py'): name = name.replace('/', os.sep) module_name = name[:-3] object_name = None # Try and import the .py file. spec = importlib.util.spec_from_file_location(module_name, name) module = importlib.util.module_from_spec(spec) try: spec.loader.exec_module(module) except Exception as e: raise UserException("Unable to import '{0}'".format(name), detail=str(e)) else: # Extract the module and any object name. parts = name.split(':') if len(parts) > 2: raise UserException( "The callable '{0}' must be specified as " "'module[:name]'".format(name)) module_name = parts[0] object_name = parts[1] if len(parts) == 2 else None # Try and import the module. try: module = importlib.import_module(module_name) except ImportError as e: raise UserException( "Unable to import '{0}'".format(module_name), detail=str(e)) # Get the callable object from the module. if object_name is None: # Look for a class that is a sub-class of the base type. for obj in module.__dict__.values(): if isinstance(obj, type): if issubclass(obj, base_type): # Make sure the type is defined in the module and not # imported by it. if obj.__module__ == module_name: break else: raise UserException( "'{0}' does not define a {1} sub-class".format(name, base_type.__name__)) else: # We have the name of the callable so just get it. obj = getattr(module, object_name) if obj is None: raise UserException( "'{0}' module has no callable '{1}'".format( module_name, object_name)) return obj @abstractmethod def install(self): """ Install the project. """ @abstractmethod def setup(self, pyproject, tool, tool_description): """ Complete the configuration of the project. """ sip-6.8.6/sipbuild/api.py000066400000000000000000000035261464421045000152720ustar00rootroot00000000000000# SPDX-License-Identifier: BSD-2-Clause # Copyright (c) 2024 Phil Thompson from .abstract_project import AbstractProject from .exceptions import handle_exception def build_sdist(sdist_directory, config_settings=None): """ The PEP 517 hook for building an sdist from pyproject.toml. """ project = AbstractProject.bootstrap('sdist', arguments=_convert_config_settings(config_settings)) # pip executes this in a separate process and doesn't handle exceptions # very well. However it does capture stdout and (eventually) show it to # the user so we use our standard exception handling. try: return project.build_sdist(sdist_directory) except Exception as e: handle_exception(e) def build_wheel(wheel_directory, config_settings=None, metadata_directory=None): """ The PEP 517 hook for building a wheel from pyproject.toml. """ project = AbstractProject.bootstrap('wheel', arguments=_convert_config_settings(config_settings)) # pip executes this in a separate process and doesn't handle exceptions # very well. However it does capture stdout and (eventually) show it to # the user so we use our standard exception handling. try: return project.build_wheel(wheel_directory) except Exception as e: handle_exception(e) def _convert_config_settings(config_settings): """ Return any configuration settings from the frontend to a pseudo-command line. """ if config_settings is None: config_settings = {} args = [] for name, value in config_settings.items(): if value: if not isinstance(value, list): value = [value] for m_value in value: args.append(name + '=' + m_value) else: args.append(name) return args sip-6.8.6/sipbuild/argument_parser.py000066400000000000000000000014571464421045000177200ustar00rootroot00000000000000# SPDX-License-Identifier: BSD-2-Clause # Copyright (c) 2024 Phil Thompson from argparse import ArgumentParser as ArgParser from ._version import version class ArgumentParser(ArgParser): """ An argument parser for all sip command line tools. """ def __init__(self, description, build_tool=False, **kwargs): """ Initialise the parser. """ super().__init__(description=description, **kwargs) self.add_argument('-V', '--version', action='version', version=version) # This option is handled by the bootstrap process and is only here to # contribute to the help. if build_tool: self.add_argument('--deprecations-are-errors', action='store_true', help="using deprecated features is an error") sip-6.8.6/sipbuild/bindings.py000066400000000000000000000272031464421045000163140ustar00rootroot00000000000000# SPDX-License-Identifier: BSD-2-Clause # Copyright (c) 2024 Phil Thompson import os import sys from .buildable import BuildableBindings from .configurable import Configurable, Option from .exceptions import UserException from .generator import parse, resolve from .generator.outputs import (output_api, output_code, output_extract, output_pyi) from .installable import Installable from .module import copy_nonshared_sources from .version import SIP_VERSION class Bindings(Configurable): """ The encapsulation of a module's bindings. """ # The configurable options. _options = ( # Any bindings level builder-specific settings. Option('builder_settings', option_type=list), # The list of #define names and values in the format "NAME" or # "NAME=VALUE". Option('define_macros', option_type=list), # Set if exception support is enabled. Option('exceptions', option_type=bool), # The list of extra compiler arguments. Option('extra_compile_args', option_type=list), # The list of extra linker arguments. Option('extra_link_args', option_type=list), # The list of extra compiled object files to link. Option('extra_objects', option_type=list), # The list of extracts to generate. Option('generate_extracts', option_type=list), # The list of additional .h files. Option('headers', option_type=list), # The list of additional C/C++ include directories to search. Option('include_dirs', option_type=list), # Set if the bindings are internal. Internal bindings don't have their # .sip, .pyi or .api files installed. Option('internal', option_type=bool), # The list of library names to link against. Option('libraries', option_type=list), # The list of C/C++ library directories to search. Option('library_dirs', option_type=list), # Set to always release the Python GIL. Option('release_gil', option_type=bool), # Set to compile the module as a static library. Option('static', option_type=bool), # The name of the .sip file that specifies the bindings. If it is # relative then it is relative to the project's 'sip_files_dir'. Option('sip_file'), # The filename extension to use for generated source files. Option('source_suffix'), # The list of additional C/C++ source files to compile and link. Option('sources', option_type=list), # The list of tags to enable. Option('tags', option_type=list), # The user-configurable options. Although the use of a corresponding # command line option will affect all sets of bindings, putting them # here (as opposed to in Project) means they can have individual # values specified in pyproject.toml. Option('concatenate', option_type=int, help="concatenate the generated bindings into N source files", metavar="N"), Option('debug', option_type=bool, help="build with debugging symbols"), Option('disabled_features', option_type=list, help="disable the TAG feature tag", metavar="TAG"), Option('docstrings', option_type=bool, inverted=True, help="disable the generation of docstrings"), Option('pep484_pyi', option_type=bool, help="enable the generation of PEP 484 .pyi files"), Option('protected_is_public', option_type=bool, help="enable the protected/public hack (default on non-Windows)"), Option('protected_is_public', option_type=bool, inverted=True, help="disable the protected/public hack (default on Windows)"), Option('tracing', option_type=bool, help="build with tracing support"), ) def __init__(self, project, name, **kwargs): """ Initialise the bindings. """ super().__init__() self.project = project self.name = name self.initialise_options(kwargs) def apply_nonuser_defaults(self, tool): """ Set default values for each non-user configurable option that hasn't been set yet. """ # Provide a default .sip file name if needed. if self.sip_file is None: self.sip_file = self.name + '.sip' super().apply_nonuser_defaults(tool) def apply_user_defaults(self, tool): """ Set default values for user options that haven't been set yet. """ if self.protected_is_public is None: self.protected_is_public = (self.project.py_platform != 'win32') super().apply_user_defaults(tool) def generate(self): """ Generate the bindings source code and optional additional extracts. and return a BuildableBindings instance containing the details of everything needed to build the bindings. """ project = self.project # The old parser had no concept of the encoding of a .sip file. For # the moment we say that files should be UTF-8. If that proves to be a # problem then a project-specific encoding should be able to be # specified in pyproject.toml which would apply to all .sip files that # make up the project. encoding = 'UTF-8' # Parse the input file. spec, modules, sip_files = parse(self.sip_file, SIP_VERSION, encoding, project.abi_version, self.tags, self.disabled_features, self.protected_is_public, self._sip_include_dirs, project.sip_module) # Resolve the types. resolve(spec, modules) module = spec.module uses_limited_api = module.use_limited_api or spec.is_composite # The details of things that will have been generated. Note that we # don't include anything for .api files or generic extracts as the # arguments include a file name. buildable = BuildableBindings(self, module.fq_py_name.name, uses_limited_api=uses_limited_api) buildable.builder_settings.extend(self.builder_settings) buildable.debug = self.debug buildable.exceptions = self.exceptions buildable.extra_compile_args = self.extra_compile_args buildable.extra_link_args = self.extra_link_args buildable.extra_objects = self.extra_objects buildable.static = self.static # Generate any API file. if project.api_dir and not self.internal: project.progress( "Generating the {0} .api file".format(buildable.target)) output_api(spec, os.path.join(project.build_dir, buildable.target + '.api')) # Generate any extracts. for extract_ref in self.generate_extracts: output_extract(spec, extract_ref) # Generate any type hints file. if self.pep484_pyi and not self.internal: project.progress( "Generating the {0} .pyi file".format(buildable.target)) pyi_path = os.path.join(buildable.build_dir, buildable.target + '.pyi') output_pyi(spec, project, pyi_path) installable = Installable('pyi', target_subdir=buildable.get_install_subdir()) installable.files.append(pyi_path) buildable.installables.append(installable) # Generate the bindings. output_code(spec, self, project, buildable) buildable.headers.extend(self.headers) # Add the sip module code if it is not shared. buildable.include_dirs.append(buildable.build_dir) if project.sip_module: # sip.h will already be in the build directory. buildable.include_dirs.append(project.build_dir) if not self.internal: # Add an installable for the .sip files. installable = buildable.get_bindings_installable('sip') sip_dir = os.path.dirname(self.sip_file) for fn in sip_files: sip_path = os.path.join(sip_dir, fn) # The code generator does not report the full pathname of a # .sip file (only names relative to the search directory in # which it was found). Therefore we need to check if it is # actually in the directory we are installing from and # ignore it if not. This isn't really the right thing to # do but is actually what we want when we have optional # license .sip files. if os.path.isfile(sip_path): installable.files.append(sip_path) buildable.installables.append(installable) else: buildable.sources.extend( copy_nonshared_sources(project.abi_version.split('.')[0], buildable.build_dir)) buildable.include_dirs.extend(self.include_dirs) buildable.sources.extend(self.sources) if self.protected_is_public: buildable.define_macros.append('SIP_PROTECTED_IS_PUBLIC') buildable.define_macros.append('protected=public') buildable.define_macros.extend(self.define_macros) buildable.libraries.extend(self.libraries) buildable.library_dirs.extend(self.library_dirs) return buildable def get_options(self): """ Return the list of configurable options. """ options = super().get_options() options.extend(self._options) return options def is_buildable(self): """ Return True if the bindings are buildable. This will not be called if the bindings have been explicitly enabled. """ return True def verify_configuration(self, tool): """ Verify that the configuration is complete and consistent. """ if tool not in Option.BUILD_TOOLS: return project = self.project super().verify_configuration(tool) # Make sure relevent paths are absolute and use native separators. self.extra_objects = [project.project_path(o) for o in self.extra_objects] self.headers = [project.project_path(h) for h in self.headers] self.include_dirs = [project.project_path(d) for d in self.include_dirs] self.library_dirs = [project.project_path(d) for d in self.library_dirs] self.sip_file = project.project_path(self.sip_file, relative_to=project.sip_files_dir) self.sources = [project.project_path(s) for s in self.sources] # Check the .sip file exists. if not os.path.isfile(self.sip_file): raise UserException( "the file '{0}' for the {1} bindings does not " "exist".format(self.sip_file, self.name)) # On Windows the interpreter must be a debug build if a debug version # is to be built and vice versa. if sys.platform == 'win32': if self.debug: if not project.py_debug: raise UserException( "A debug version of Python must be used when " "building a debug version of the {0} " "bindings".format(self.name)) elif project.py_debug: raise UserException( "A debug version of the {0} bindings must be built " "when a debug version of Python is used".format( self.name)) if not self.source_suffix: self.source_suffix = None sip-6.8.6/sipbuild/bindings_configuration.py000066400000000000000000000045161464421045000212450ustar00rootroot00000000000000# SPDX-License-Identifier: BSD-2-Clause # Copyright (c) 2024 Phil Thompson import os from .exceptions import UserFileException, UserParseException from .module import resolve_abi_version from .toml import toml_load def get_bindings_configuration(abi_major, sip_file, sip_include_dirs): """ Get the configuration of a set of bindings. """ # We make no assumption about the name of the .sip file but we assume that # the directory it is in is the name of the bindings. bindings_name = os.path.basename(os.path.dirname(sip_file)) # See if there is a .toml file. for sip_dir in sip_include_dirs: toml_file = os.path.join(sip_dir, bindings_name, bindings_name + '.toml') if os.path.isfile(toml_file): break else: return [], [] # Read the configuration. try: cfg = toml_load(toml_file) except Exception as e: raise UserParseException(toml_file, detail=str(e)) # Check the ABI version is compatible. cfg_abi_version = cfg.get('sip-abi-version') if cfg_abi_version is None or not isinstance(cfg_abi_version, str): raise UserFileException(toml_file, "'sip-abi-version' must be specified as a string") cfg_abi_major = int(resolve_abi_version(cfg_abi_version).split('.')[0]) if cfg_abi_major != abi_major: raise UserFileException(toml_file, "'{0}' was built against ABI v{1} but this module is being " "built against ABI v{2}".format(bindings_name, cfg_abi_major, abi_major)) # Return the tags and disabled features. return (_get_string_list(toml_file, cfg, 'module-tags'), _get_string_list(toml_file, cfg, 'module-disabled-features')) def _get_string_list(toml_file, cfg, name): """ Get an option from the configuration and check it is a list of strings. """ option_list = cfg.get(name) if option_list is None: option_list = list() elif not isinstance(option_list, list): raise UserFileException(toml_file, "'{0}' must be a list".format(name)) for option in option_list: if not isinstance(option, str): raise UserFileException(toml_file, "elements of '{0}' must be strings".format(name)) return option_list sip-6.8.6/sipbuild/buildable.py000066400000000000000000000143451464421045000164450ustar00rootroot00000000000000# SPDX-License-Identifier: BSD-2-Clause # Copyright (c) 2024 Phil Thompson import os from .exceptions import UserException from .installable import Installable from .py_versions import OLDEST_SUPPORTED_MINOR from .version import SIP_VERSION_STR class Buildable: """ Encapsulate the components used to build something that can be installed. """ def __init__(self, project, name): """ Initialise the buildable. """ self.project = project self.name = name self.builder_settings = [] self.installables = [] self.build_dir = os.path.join(project.build_dir, name) os.makedirs(self.build_dir, exist_ok=True) class BuildableFromSources(Buildable): """ Encapsulate the sources used to build an extension module, executable etc. """ def __init__(self, project, name, target, *, uses_limited_api=False): """ Initialise the buildable. """ super().__init__(project, name) if project.py_debug: uses_limited_api = False elif uses_limited_api and not project.sip_module: raise UserException( "{0} cannot use the limited API without using a shared " "'sip' module".format(name)) self.target = target self.uses_limited_api = uses_limited_api self.define_macros = [] self.sources = [] self.headers = [] self.include_dirs = [] self.libraries = [] self.library_dirs = [] self.extra_compile_args = [] self.extra_link_args = [] self.extra_objects = [] self.debug = False if self.uses_limited_api: self.define_macros.append( 'Py_LIMITED_API=0x03{0:02x}0000'.format( OLDEST_SUPPORTED_MINOR)) def make_names_relative(self): """ Make all file and directory names relative to the build directory. """ # Make the file names relative to the build directory. self.include_dirs = self._relative_names(self.include_dirs) self.headers = self._relative_names(self.headers) self.sources = self._relative_names(self.sources) self.library_dirs = self._relative_names(self.library_dirs) def _relative_names(self, names): """ Return a list of times made relative to build directory. Note that we only really do this for cosmetic reasons to simplify what the user might see. """ rel_names = [] for fn in names: try: common = os.path.commonpath([fn, self.build_dir]) _, common = os.path.splitdrive(common) if len(common) > 1: # Only convert to a relative name if there is at least one # parent directory in common. fn = os.path.relpath(fn, self.build_dir) except ValueError: # This is most likely to happen if the build directory is on a # different Windows drive. pass rel_names.append(fn) return rel_names class BuildableExecutable(BuildableFromSources): """ Encapsulate the sources used to build an executable. """ class BuildableModule(BuildableFromSources): """ Encapsulate the sources used to build an extension module. """ def __init__(self, project, name, fq_name, *, uses_limited_api=False): """ Initialise the sources. """ super().__init__(project, name, fq_name.split('.')[-1], uses_limited_api=uses_limited_api) self.fq_name = fq_name self.exceptions = False self.static = False def get_install_subdir(self): """ Return the sub-directory the extension module should be installed in relative to the eventual target directory. """ return os.sep.join(self.fq_name.split('.')[:-1]) def get_module_extension(self): """ Return the filename extension that a module should have. """ if self.project.py_platform == 'win32': return '.pyd' from importlib.machinery import EXTENSION_SUFFIXES if self.uses_limited_api: for s in EXTENSION_SUFFIXES: if '.abi3' in s: return s return EXTENSION_SUFFIXES[0] class BuildableBindings(BuildableModule): """ Encapsulate the sources used to build the extension module for a set of bindings. """ def __init__(self, bindings, fq_name, *, uses_limited_api=False): """ Initialise the sources. """ super().__init__(bindings.project, fq_name.split('.')[-1], fq_name, uses_limited_api=uses_limited_api) self.bindings = bindings def get_bindings_installable(self, name): """ Return an installable for the buildable's bindings directory. """ target_subdir = os.path.join(self.project.get_bindings_dir(), self.name) return Installable(name, target_subdir=target_subdir) def write_configuration(self, bindings_dir): """ Write the configuration of the bindings and add it as an installable. """ # Make sure the bindings directory exists. bindings_dir = os.path.join(bindings_dir, self.name) os.makedirs(bindings_dir, exist_ok=True) config_path = os.path.join(bindings_dir, self.name + '.toml') # Create an installable for the configuration file. installable = self.get_bindings_installable('config') installable.files.append(config_path) self.installables.append(installable) # Write the configuration file. bindings = self.bindings with open(config_path, 'w') as cf: sip_version_str = SIP_VERSION_STR if self.project.version_info else '' tags = ', '.join(['"{}"'.format(t) for t in bindings.tags]) disabled = ', '.join( ['"{}"'.format(f) for f in bindings.disabled_features]) cf.write("# Automatically generated configuration for {0}.\n".format(self.fq_name)) cf.write(''' sip-version = "{}" sip-abi-version = "{}" module-tags = [{}] module-disabled-features = [{}] '''.format(sip_version_str, self.project.abi_version, tags, disabled)) sip-6.8.6/sipbuild/builder.py000066400000000000000000000311251464421045000161430ustar00rootroot00000000000000# SPDX-License-Identifier: BSD-2-Clause # Copyright (c) 2024 Phil Thompson from abc import abstractmethod import glob import os import shutil import stat import sys from .abstract_builder import AbstractBuilder from .distinfo import write_metadata from .exceptions import UserException from .installable import Installable from .module import copy_sip_h, copy_sip_pyi from .py_versions import OLDEST_SUPPORTED_MINOR class Builder(AbstractBuilder): """ The default base implementation of a project builder. """ def build(self): """ Build the project in-situ. """ self._generate_bindings() self._generate_scripts() if self.project.compile: self.build_project(self.project.target_dir) @abstractmethod def build_executable(self, buildable, *, fatal=True): """ Build an executable from a BuildableExecutable object and return the relative pathname of the executable. """ @abstractmethod def build_project(self, target_dir, *, wheel_tag=None): """ Build the project. """ def build_sdist(self, sdist_directory): """ Build an sdist for the project and return the name of the sdist file. """ project = self.project # The sdist name. sdist_name = '{}-{}'.format(project.name.replace('-', '_'), project.version_str) # Create the sdist root directory. sdist_root = os.path.join(project.build_dir, sdist_name) os.mkdir(sdist_root) # Get a list of all excluded files. excluded = [] for patt in project.sdist_excludes: excluded.extend(glob.glob(os.path.join(project.root_dir, patt))) for dname, dirnames, filenames in os.walk(project.root_dir): # Always ignore certain directories. if dname == project.build_dir: del dirnames[:] continue try: dirnames.remove('__pycache__') except ValueError: pass # Copy each file non-excluded file. for s_fn in filenames: s_fn_path = os.path.join(dname, s_fn) if s_fn_path in excluded: continue d_fn_path = os.path.join(sdist_root, os.path.relpath(s_fn_path, project.root_dir)) os.makedirs(os.path.dirname(d_fn_path), exist_ok=True) shutil.copy2(s_fn_path, d_fn_path) # Create the PKG-INFO file. This is assumed to be identical to the # .dist-info/METADATA file. write_metadata(project.metadata, project.get_requires_dists(), os.path.join(sdist_root, 'PKG-INFO'), project.root_dir) # Create the tarball. sdist_file = sdist_name + '.tar.gz' sdist_path = os.path.abspath(os.path.join(sdist_directory, sdist_file)) saved_cwd = os.getcwd() os.chdir(project.build_dir) import tarfile tf = tarfile.open(sdist_path, 'w:gz', format=tarfile.PAX_FORMAT) tf.add(sdist_name) tf.close() os.chdir(saved_cwd) return sdist_file def build_wheel(self, wheel_directory): """ Build a wheel for the project and return the name of the wheel file. """ project = self.project # Create a temporary directory for the wheel. wheel_build_dir = os.path.join(project.build_dir, 'wheel') os.mkdir(wheel_build_dir) # Build the wheel contents. self._generate_bindings() # Create the wheel tag. If all buildables use the limited API then the # wheel does. wheel_tag = [] if project.all_modules_use_limited_abi: # When the ABI tag is 'abi3' the interpreter tag is interpreted as # a minimum Python version. This doesn't seem to be defined in a # PEP but is implemented in current pips. wheel_tag.append('cp3' + str(OLDEST_SUPPORTED_MINOR)) wheel_tag.append('abi3') else: major_minor = '{}{}'.format((sys.hexversion >> 24) & 0xff, (sys.hexversion >> 16) & 0xff) wheel_tag.append('cp{}'.format(major_minor)) try: wheel_tag.append('cp' + major_minor + sys.abiflags) except AttributeError: wheel_tag.append('none') wheel_tag.append(project.get_platform_tag()) wheel_tag = '-'.join(wheel_tag) # Add any wheel contents defined by the project. for nr, (patt, target_subdir) in enumerate(project.wheel_includes): if not os.path.isabs(patt): patt = os.path.join(project.root_dir, patt) wheel_includes = glob.glob(patt) if wheel_includes: installable = Installable( 'wheel_includes_{}'.format(nr) if nr else 'wheel_includes', target_subdir=target_subdir) installable.files.extend(wheel_includes) project.installables.append(installable) # Build the project. self.build_project(wheel_build_dir, wheel_tag=wheel_tag) # Copy the wheel contents. self.install_project(wheel_build_dir, wheel_tag=wheel_tag) wheel_file = '{}-{}'.format(project.name.replace('-', '_'), project.version_str) if project.build_tag: wheel_file += '-{}'.format(project.build_tag) wheel_file += '-{}.whl'.format(wheel_tag) wheel_path = os.path.abspath(os.path.join(wheel_directory, wheel_file)) # Create the .whl file. saved_cwd = os.getcwd() os.chdir(wheel_build_dir) from zipfile import ZipFile, ZIP_DEFLATED with ZipFile(wheel_path, 'w', compression=ZIP_DEFLATED) as zf: for dirpath, _, filenames in os.walk('.'): for filename in filenames: # This will result in a name with no leading '.'. name = os.path.relpath(os.path.join(dirpath, filename)) zf.write(name) os.chdir(saved_cwd) return wheel_file def install(self): """ Install the project. """ target_dir = self.project.target_dir self._generate_bindings() self._generate_scripts() self.build_project(target_dir) self.install_project(target_dir) @abstractmethod def install_project(self, target_dir, *, wheel_tag=None): """ Install the project into a target directory. """ def _generate_bindings(self): """ Generate the bindings for all enabled modules. """ project = self.project abi_major_version, _ = project.abi_version.split('.') # Get the list of directories to search for .sip files. sip_include_dirs = list(project.sip_include_dirs) if project.sip_module: # Add the project's sip directory. sip_include_dirs.append(project.sip_files_dir) # Add the local bindings directory to pick up the .toml files for # any other bindings in this package. local_bindings_dir = os.path.join(project.build_dir, 'bindings') sip_include_dirs.append(local_bindings_dir) # Add any bindings from previously installed packages. sip_include_dirs.append( os.path.join(project.target_dir, project.get_bindings_dir())) # Generate the sip.h file for the shared sip module. copy_sip_h(abi_major_version, project.build_dir, project.sip_module, version_info=project.version_info) # Generate the code for each set of bindings. api_files = [] for bindings in project.bindings.values(): project.progress( "Generating the {0} bindings".format(bindings.name)) # Generate the source code. We would prefer to pass the include # directories as an argument to generate but this is a fixed public # interface. bindings._sip_include_dirs = sip_include_dirs buildable = bindings.generate() del bindings._sip_include_dirs if not bindings.internal: api_files.append( os.path.join(project.build_dir, buildable.target + '.api')) # Generate the bindings configuration file. if project.sip_module: buildable.write_configuration(local_bindings_dir) project.buildables.append(buildable) # Create __init__.py if required. if project.dunder_init: package_dir = project.get_package_dir() init_path = os.path.join(project.build_dir, '__init__.py') init_f = project.open_for_writing(init_path) init_f.write(project.get_dunder_init()) init_f.close() installable = Installable('init', target_subdir=package_dir) installable.files.append(init_path) project.installables.append(installable) # Include sip.pyi if any of the bindings generate a .pyi file. for bindings in project.bindings.values(): if bindings.pep484_pyi: copy_sip_pyi(abi_major_version, project.build_dir) installable = Installable('sip_pyi', target_subdir=package_dir) installable.files.append( os.path.join(project.build_dir, 'sip.pyi')) project.installables.append(installable) # Create a PEP 561 marker file. py_typed_path = os.path.join(project.build_dir, 'py.typed') with open(py_typed_path, 'w') as _: pass installable = Installable('py_typed', target_subdir=package_dir) installable.files.append(py_typed_path) project.installables.append(installable) break # Create the .api file if required. if project.api_dir: api_fn = project.name + '.api' project.progress("Generating the {0} file".format(api_fn)) # Concatanate the individual .api files. api_path = os.path.join(project.build_dir, api_fn) api_f = project.open_for_writing(api_path) for part_fn in api_files: with open(part_fn) as part_f: api_f.write(part_f.read()) api_f.close() # Add an Installable for the requested API file. installable = Installable('api', target_subdir=project.api_dir) installable.files.append(api_path) project.installables.append(installable) def _generate_scripts(self): """ Generate the scripts for any entry points. """ project = self.project # Handle the trivial case. scripts = project.console_scripts + project.gui_scripts if not scripts: return # Create an installable for the scripts. installable = Installable('scripts', target_subdir=project.scripts_dir) for ep in scripts: # Parse the entry point. ep_parts = ep.replace(' ', '').split('=') if len(ep_parts) != 2: raise UserException( "'{0}' is an invalid script specification".format(ep)) script, module = ep_parts # Remove any callable name. module = module.split(':')[0] if project.py_platform == 'win32': script += '.bat' project.progress("Generating the {} script".format(script)) script_path = os.path.join(project.build_dir, script) installable.files.append(script_path) script_f = project.open_for_writing(script_path) if project.py_platform == 'win32': script_f.write( '@{} -m {} %1 %2 %3 %4 %5 %6 %7 %8 %9\n'.format( sys.executable, module)) else: script_f.write('#!/bin/sh\n') script_f.write( 'exec %s -m %s ${1+"$@"}\n' % (sys.executable, module)) script_f.close() # Make the script executable. os.chmod(script_path, stat.S_IRUSR|stat.S_IWUSR|stat.S_IXUSR| stat.S_IRGRP|stat.S_IXGRP| stat.S_IROTH|stat.S_IXOTH) project.installables.append(installable) sip-6.8.6/sipbuild/configurable.py000066400000000000000000000224051464421045000171560ustar00rootroot00000000000000# SPDX-License-Identifier: BSD-2-Clause # Copyright (c) 2024 Phil Thompson from packaging.markers import Marker from .exceptions import UserException from .pyproject import PyProjectOptionException class Configurable: """ A base class for an object that can be configured by a pyproject.toml, a build script or (possibly) the user via command line options. """ def add_command_line_options(self, parser, tool, all_options, options=None): """ Add the object's command line options to an argument parser and update a map of Object instances and the configurables that they should be applied to. """ if options is None: options = self.get_options() for option in options: # If it has already been set explicitly then the user cannot change # it. if getattr(self, option.name) is not None: continue # If there is no help then the user can never specify it. if option.help is None: continue # See if the option is applicable to this tool. if option.tools and tool not in option.tools: continue # See if the option has already been added. configurables = all_options.setdefault(option, list()) configurables.append(self) if len(configurables) != 1: continue # Add the option according to its type. argument_name = option.user_name if option.inverted: argument_name = 'no-' + argument_name argument_name = '--' + argument_name if option.option_type is bool: parser.add_argument(argument_name, dest=option.dest, action=('store_false' if option.inverted else 'store_true'), help=option.help) elif option.option_type is list: # Remove any plural. if argument_name.endswith('s'): argument_name = argument_name[:-1] parser.add_argument(argument_name, dest=option.dest, choices=option.choices, action='append', help=option.help, metavar=option.metavar) else: parser.add_argument(argument_name, dest=option.dest, type=option.option_type, choices=option.choices, help=option.help, metavar=option.metavar) def apply_nonuser_defaults(self, tool): """ Set default values for each non-user configurable option that hasn't been set yet. """ self._apply_defaults(tool, user=False) def apply_user_defaults(self, tool): """ Set default values for each user configurable option that hasn't been set yet. """ self._apply_defaults(tool, user=True) def configure(self, pyproject, section_name, tool): """ Perform the initial configuration of an object. """ section = pyproject.get_section(section_name) if section is not None: for name, value in section.items(): # Find the corresponding option. for option in self.get_options(): if option.user_name == name: break else: raise PyProjectOptionException(name, "is not a supported option", section_name=section_name) # Check the type of the option. if not isinstance(value, option.option_type): raise PyProjectOptionException(name, "should be of type '{0}' and not '{1}'".format( option.option_type.__name__, type(value).__name__), section_name=section_name) # Check the option hasn't already been initialised. if getattr(self, option.name) is not None: raise PyProjectOptionException(name, "has already been set in code and cannot be " "changed", section_name=section_name) # Evaluate any environment markers if the option supports them. if isinstance(value, list): new_value = [] for v in value: v = self._handle_marker(v, name, section_name) if v is not None: new_value.append(v) value = new_value setattr(self, option.name, value) self.apply_nonuser_defaults(tool) def get_options(self): """ Return a list of configurable options. """ return list() def initialise_options(self, kwargs): """ Initialise the options. """ # Set the value for each option from the keyword arguments or undefined # if not specified. names = [] for option in self.get_options(): name = option.name names.append(name) setattr(self, name, kwargs.get(name)) # Check that all keyword arguments are valid options. for kw in kwargs.keys(): if kw not in names: raise UserException("'{0}' is not a valid option".format(kw)) def verify_configuration(self, tool): """ Verify that the configuration is complete and consistent. """ # This default implementation does nothing. def _apply_defaults(self, tool, user): """ Set default values for each user/non-user option that hasn't been set yet. """ for option in self.get_options(): if user and option.help is None: continue if not user and option.help is not None: continue value = getattr(self, option.name) if value is None: value = option.default if value is None: # Provide a default default based on the type. if option.option_type is list: value = [] elif option.option_type is float: value = 0.0 elif option.option_type is int: value = 0 elif option.option_type is bool: value = True if option.inverted else False elif option.option_type is str: value = '' else: # Make a copy of the default in case it is mutable. value = option.option_type(value) setattr(self, option.name, value) @staticmethod def _handle_marker(value, name, section_name): """ Handle any environment marker in a value. The value is returned if a marker evaluates to True. None is returned if a marker evaluates to False. """ # Handle the trivial case of there being no marker. if ';' not in value: return value value, marker = value.split(';', maxsplit=1) try: satisfied = Marker(marker).evaluate() except: raise PyProjectOptionException(name, "has an invalid marker '{0}'".format(marker), section_name=section_name) return value if satisfied else None class Option: """ Encapsulate a configuration option. This defines and implements an attribute of a Configurable object. The value of the attribute can be set either by __init__(), the pyproject.toml file and by the user using a command line argument (in that order). Once the value is set it cannot be changed subsequently. For example, if an attribute is set in pyproject.toml then the user will not then be able to modify it from the command line. The value can only be changed from the command line if the Option object has help text specified. """ # The tools that will build a set of bindings. BUILD_TOOLS = ('build', 'install', 'wheel') # All the valid tools. _ALL_TOOLS = BUILD_TOOLS + ('sdist', ) # This is used to make sure each option (even if they are handling the same # attribute) has a unique 'dest'. option_nr = 0 def __init__(self, name, *, option_type=str, choices=None, default=None, help=None, metavar=None, inverted=False, tools=None): """ Initialise the option. """ self.name = name self.user_name = name.replace('_', '-') self.option_type = option_type self.default = default self.choices = choices self.help = help self.metavar = metavar self.inverted = inverted if tools is None: self.tools = self.BUILD_TOOLS else: for tool in tools: if tool not in self._ALL_TOOLS: raise UserException( "'{0}' option has an invalid tools '{1}'".format( name, tool)) self.tools = tools self.dest = 'd' + str(type(self).option_nr) type(self).option_nr += 1 sip-6.8.6/sipbuild/distinfo/000077500000000000000000000000001464421045000157605ustar00rootroot00000000000000sip-6.8.6/sipbuild/distinfo/__init__.py000066400000000000000000000003441464421045000200720ustar00rootroot00000000000000# SPDX-License-Identifier: BSD-2-Clause # Copyright (c) 2024 Phil Thompson # Publish the API. This is private to the rest of sip. from .distinfo import create_distinfo, distinfo, write_metadata sip-6.8.6/sipbuild/distinfo/distinfo.py000066400000000000000000000165541464421045000201640ustar00rootroot00000000000000# SPDX-License-Identifier: BSD-2-Clause # Copyright (c) 2024 Phil Thompson import base64 import hashlib import os import shutil import sys from ..exceptions import UserException from ..pyproject import PyProject from ..version import SIP_VERSION_STR # The wheel format defined in PEP 427. WHEEL_VERSION = '1.0' def distinfo(name, console_scripts, gui_scripts, generator, generator_version, inventory, metadata_overrides, prefix, project_root, requires_dists, wheel_tag): """ Create and populate a .dist-info directory from an inventory file. """ if prefix is None: prefix = '' # Read the list of installed files. with open(inventory) as inventory_f: installed_lines = inventory_f.read().strip() installed = installed_lines.split('\n') if installed_lines else [] # Get the pyproject.toml file. saved = os.getcwd() os.chdir(project_root) pyproject = PyProject() os.chdir(saved) # Get the metadata and update it from the command line. metadata = pyproject.get_metadata() if metadata_overrides is not None: for oride in metadata_overrides: parts = oride.split('=', maxsplit=1) or_name = parts[0].strip() or_value = parts[1].strip() if len(parts) == 2 else '' metadata[or_name] = or_value # Create the directory. create_distinfo(name, wheel_tag, installed, metadata, requires_dists, project_root, console_scripts, gui_scripts, prefix_dir=prefix, generator=generator, generator_version=generator_version) def create_distinfo(distinfo_dir, wheel_tag, installed, metadata, requires_dists, project_root, console_scripts, gui_scripts, prefix_dir='', generator=None, generator_version=None): """ Create and populate a .dist-info directory. """ if generator is None: generator = 'sipbuild' if generator_version is None: generator_version = SIP_VERSION_STR # The prefix directory corresponds to DESTDIR or INSTALL_ROOT. real_distinfo_dir = prefix_dir + distinfo_dir # Make sure we have an empty dist-info directory. Handle exceptions as the # user may be trying something silly with a system directory. if os.path.exists(real_distinfo_dir): try: shutil.rmtree(real_distinfo_dir) except Exception as e: raise UserException( "unable remove old dist-info directory '{}'".format( real_distinfo_dir), str(e)) try: os.mkdir(real_distinfo_dir) except Exception as e: raise UserException( "unable create dist-info directory '{}'".format( real_distinfo_dir), str(e)) # Reproducable builds. installed.sort() if wheel_tag is None: # Create the INSTALLER file. installer_fn = os.path.join(distinfo_dir, 'INSTALLER') installed.append(installer_fn) with open(prefix_dir + installer_fn, 'w') as installer_f: print(generator, file=installer_f) else: # Define any entry points. if console_scripts or gui_scripts: eps_fn = os.path.join(distinfo_dir, 'entry_points.txt') installed.append(eps_fn) with open(prefix_dir + eps_fn, 'w') as eps_f: if console_scripts: eps_f.write( '[console_scripts]\n' + '\n'.join( console_scripts) + '\n') if gui_scripts: eps_f.write( '[gui_scripts]\n' + '\n'.join(gui_scripts) + '\n') # Create the WHEEL file. WHEEL = '''Wheel-Version: {} Generator: {} {} Root-Is-Purelib: false Tag: {} ''' wheel_fn = os.path.join(distinfo_dir, 'WHEEL') installed.append(wheel_fn) with open(prefix_dir + wheel_fn, 'w') as wheel_f: wheel_f.write( WHEEL.format(WHEEL_VERSION, generator, generator_version, wheel_tag)) # Create the METADATA file. metadata_fn = os.path.join(distinfo_dir, 'METADATA') write_metadata(metadata, requires_dists, metadata_fn, project_root, prefix_dir=prefix_dir) installed.append(metadata_fn) # Create the RECORD file. record_fn = os.path.join(distinfo_dir, 'RECORD') distinfo_path, distinfo_base = os.path.split(distinfo_dir) real_distinfo_path = os.path.normcase(prefix_dir + distinfo_path) with open(prefix_dir + record_fn, 'w') as record_f: for name in installed: real_name = prefix_dir + name if os.path.isdir(real_name): all_fns = [] for root, dirs, files in os.walk(real_name): # Reproducable builds. dirs.sort() files.sort() for f in files: all_fns.append(os.path.join(root, f)) if '__pycache__' in dirs: dirs.remove('__pycache__') else: all_fns = [real_name] for fn in all_fns: norm_fn = os.path.normcase(fn) if norm_fn.startswith(real_distinfo_path): fn_name = fn[len(real_distinfo_path) + 1:].replace('\\', '/') elif norm_fn.startswith(prefix_dir + sys.prefix): fn_name = os.path.relpath( fn, real_distinfo_path).replace('\\', '/') else: fn_name = fn[len(prefix_dir):] fn_f = open(fn, 'rb') data = fn_f.read() fn_f.close() digest = base64.urlsafe_b64encode( hashlib.sha256(data).digest()).rstrip(b'=').decode('ascii') record_f.write( '{},sha256={},{}\n'.format(fn_name, digest, len(data))) record_f.write('{}/RECORD,,\n'.format(distinfo_base)) def write_metadata(metadata, requires_dists, metadata_fn, project_root, prefix_dir=''): """ Write the meta-data, with additional requirements to a file. """ if requires_dists: rd = metadata.get('requires-dist', []) if isinstance(rd, str): rd = [rd] metadata['requires-dist'] = requires_dists + rd with open(prefix_dir + metadata_fn, 'w') as metadata_f: description = None # Do these first for cosmetic reasons. for name in ('metadata-version', 'name', 'version', 'requires-python'): _write_metadata_item(name, metadata.pop(name), metadata_f) for name, value in metadata.items(): if name == 'description-file': description = value else: _write_metadata_item(name, value, metadata_f) if description is not None: metadata_f.write('\n') # The description file uses posix separators. description = description.replace('/', os.sep) with open(os.path.join(project_root, description)) as description_f: metadata_f.write(description_f.read()) def _write_metadata_item(name, value, metadata_f): """ Write a single metadata item. """ if isinstance(value, str): value = [value] for v in value: metadata_f.write('{}: {}\n'.format(name.title(), v)) sip-6.8.6/sipbuild/distutils_builder.py000066400000000000000000000133251464421045000202510ustar00rootroot00000000000000# SPDX-License-Identifier: BSD-2-Clause # Copyright (c) 2024 Phil Thompson from distutils.command.build_ext import build_ext from distutils.dist import Distribution from distutils.extension import Extension from distutils.log import ERROR, INFO, set_threshold import os from .buildable import BuildableModule from .builder import Builder from .exceptions import UserException from .installable import Installable class DistutilsBuilder(Builder): """ The implementation of a distutils-based project builder. """ def build_executable(self, buildable, *, fatal=True): """ Build an executable from a BuildableExecutable object and return the relative pathname of the executable. """ raise UserException("DistutilsBuilder cannot build executables") def build_project(self, target_dir, *, wheel_tag=None): """ Build the project. """ project = self.project # On macOS respect the minimum macOS version. remove_macos_target = False if project.py_platform == 'darwin': # If the target version has already been set then assume the user # knows what they are doing and leave it as it is. if 'MACOSX_DEPLOYMENT_TARGET' not in os.environ: if project.minimum_macos_version: macos_target = '.'.join( [str(v) for v in project.minimum_macos_version]) os.environ['MACOSX_DEPLOYMENT_TARGET'] = macos_target remove_macos_target = True # Build the buildables. for buildable in project.buildables: if isinstance(buildable, BuildableModule): if buildable.static: raise UserException( "DistutilsBuilder cannot build static modules") self._build_extension_module(buildable) else: raise UserException( "DistutilsBuilder cannot build '{0}' buildables".format( type(buildable).__name__)) # Tidy up. if remove_macos_target: del os.environ['MACOSX_DEPLOYMENT_TARGET'] def install_project(self, target_dir, *, wheel_tag=None): """ Install the project into a target directory. """ project = self.project installed = [] # Install any project-level installables. for installable in project.installables: installable.install(target_dir, installed) # Install any installables from built buildables. for buildable in project.buildables: for installable in buildable.installables: installable.install(target_dir, installed) if project.distinfo: from .distinfo import create_distinfo create_distinfo(project.get_distinfo_dir(target_dir), wheel_tag, installed, project.metadata, project.get_requires_dists(), project.root_dir, project.console_scripts, project.gui_scripts) def _build_extension_module(self, buildable): """ Build an extension module from the sources. """ project = self.project set_threshold(INFO if project.verbose else ERROR) distribution = Distribution() module_builder = ExtensionCommand(distribution, buildable) module_builder.build_lib = buildable.build_dir module_builder.debug = buildable.debug if buildable.debug: # Enable assert(). module_builder.undef = 'NDEBUG' module_builder.ensure_finalized() # Convert the #defines. define_macros = [] for macro in buildable.define_macros: parts = macro.split('=', maxsplit=1) name = parts[0] try: value = parts[1] except IndexError: value = None define_macros.append((name, value)) buildable.make_names_relative() module_builder.extensions = [ Extension(buildable.fq_name, buildable.sources, define_macros=define_macros, extra_compile_args=buildable.extra_compile_args, extra_link_args=buildable.extra_link_args, extra_objects=buildable.extra_objects, include_dirs=buildable.include_dirs, libraries=buildable.libraries, library_dirs=buildable.library_dirs)] project.progress( "Compiling the '{0}' module".format(buildable.fq_name)) saved_cwd = os.getcwd() os.chdir(buildable.build_dir) try: module_builder.run() except Exception as e: raise UserException( "Unable to compile the '{0}' module".format( buildable.fq_name), detail=str(e)) # Add the extension module to the buildable's list of installables. installable = Installable('module', target_subdir=buildable.get_install_subdir()) installable.files.append( module_builder.get_ext_fullpath(buildable.fq_name)) buildable.installables.append(installable) os.chdir(saved_cwd) class ExtensionCommand(build_ext): """ Extend the distutils command to build an extension module. """ def __init__(self, distribution, buildable): """ Initialise the object. """ super().__init__(distribution) self._buildable = buildable def get_ext_filename(self, ext_name): """ Reimplemented to handle modules that use the limited API. """ return os.path.join(*ext_name.split('.')) + self._buildable.get_module_extension() sip-6.8.6/sipbuild/exceptions.py000077500000000000000000000050121464421045000166750ustar00rootroot00000000000000# SPDX-License-Identifier: BSD-2-Clause # Copyright (c) 2024 Phil Thompson import os import sys from .version import SIP_VERSION # Set if deprecations should be treated as errors. _deprecations_are_errors = False def set_deprecations_are_errors(deprecations_are_errors): """ If set then deprecations will be handled as errors. """ global _deprecations_are_errors _deprecations_are_errors = deprecations_are_errors class UserException(Exception): """ An exception capturing user friendly information. """ def __init__(self, text, *, detail=None): """ Initialise the exception with its user friendly text and the optional detail. """ super().__init__() self.text = text self.detail = detail class UserFileException(UserException): """ An exception related to a specific file. """ def __init__(self, filename, text, *, detail=None): """ Initialise the exception. """ super().__init__("{0}: {1}".format(filename, text), detail=detail) class UserParseException(UserFileException): """ An exception related to the parsing of a specific file. """ def __init__(self, filename, *, detail=None): """ Initialise the exception. """ super().__init__(filename, "unable to parse file", detail=detail) def handle_exception(e): """ Tell the user about an exception. """ if isinstance(e, UserException): # An "expected" exception. if e.detail is not None: message = "{0}: {1}".format(e.text, e.detail) else: message = e.text print("{0}: {1}".format(os.path.basename(sys.argv[0]), message), file=sys.stderr) sys.exit(1) # An internal error. print( "{0}: An internal error occurred...".format( os.path.basename(sys.argv[0])), file=sys.stderr) raise e def deprecated(thing, *, filename=None, line_nr=None, instead=None): """ Tell the user about a deprecation. """ next_major_version = (SIP_VERSION >> 16) + 1 if filename is not None: prefix = filename + ': ' if line_nr is not None: prefix += f"line {line_nr}: " else: prefix = '' message = f"{prefix}{thing} is deprecated and will be removed in SIP v{next_major_version}.0.0" if instead is not None: message += f", use {instead} instead" if _deprecations_are_errors: raise UserException(message) print(message, file=sys.stderr) sip-6.8.6/sipbuild/generator/000077500000000000000000000000001464421045000161275ustar00rootroot00000000000000sip-6.8.6/sipbuild/generator/__init__.py000066400000000000000000000003341464421045000202400ustar00rootroot00000000000000# SPDX-License-Identifier: BSD-2-Clause # Copyright (c) 2024 Phil Thompson # Publish the API. This is private to the rest of sip. from .parser import parse from .resolver import resolve sip-6.8.6/sipbuild/generator/error_log.py000066400000000000000000000020751464421045000204770ustar00rootroot00000000000000# SPDX-License-Identifier: BSD-2-Clause # Copyright (c) 2024 Phil Thompson from ..exceptions import UserException class ErrorLog: """ This object logs errors and raises a corresponding exception when requested. """ def __init__(self): """ Initialise the error log. """ self._errors = [] def log(self, text, source_location=None): """ Log an error with an optional source location. """ # Format the entry. if source_location is None: entry = '' else: entry = source_location.sip_file + ": " if source_location.line > 0: entry += "line {0}: ".format(source_location.line) if source_location.column > 0: entry += "column {0}: ".format(source_location.column) entry += text self._errors.append(entry) def as_exception(self): """ Raise a UserException for any logged errors. """ if self._errors: raise UserException('\n'.join(self._errors)) sip-6.8.6/sipbuild/generator/instantiations.py000066400000000000000000000407571464421045000215650ustar00rootroot00000000000000# SPDX-License-Identifier: BSD-2-Clause # Copyright (c) 2024 Phil Thompson from copy import copy from .scoped_name import ScopedName from .specification import (Argument, ArgumentType, FunctionCall, IfaceFileType, KwArgs, Signature, TypeHints, Value, ValueType) from .templates import (template_code, template_code_blocks, template_expansions, template_string) from .utils import append_iface_file, cached_name, normalised_scoped_name def instantiate_class(p, symbol, fq_cpp_name, tmpl_names, proto_class, template, py_name, no_type_name, docstring, pm): """ Instantiate a class template. """ # Create the expansions. expansions = template_expansions(tmpl_names, template.types) # Add a mapping from the template name to the instantiated name. expansions[str(proto_class.iface_file.fq_cpp_name)] = str(fq_cpp_name) # Create the new class starting with a shallow copy. i_class = copy(proto_class) if docstring is not None: i_class.docstring = docstring i_class.mro = [] i_class.virtual_overloads = [] i_class.visible_members = [] i_class.py_name = cached_name(pm.spec, py_name) i_class.template = template i_class.no_type_name = no_type_name # Handle the interface file. i_class.iface_file = pm.find_iface_file(p, symbol, fq_cpp_name, IfaceFileType.CLASS) i_class.iface_file.module = pm.module_state.module used = i_class.iface_file.used i_class.iface_file.type_header_code = template_code_blocks(pm.spec, used, proto_class.iface_file.type_header_code, expansions) # Make a copy of the used list and add the enclosing scope. for iface_file in proto_class.iface_file.used: append_iface_file(i_class.iface_file.used, iface_file) # Include any scope header code. i_class.scope = pm.scope if i_class.scope is not None: i_class.iface_file.type_header_code.extend( i_class.scope.iface_file.type_header_code) if pm.in_main_module: i_class.iface_file.cpp_name.used = True i_class.py_name.used = True # Handle any type hints. if proto_class.type_hints is not None: i_class.type_hints = instantiate_type_hints(pm.spec, proto_class.type_hints, expansions) # Handle any flagged enums. if proto_class.pyqt_flags_enums is not None: i_class.pyqt_flags_enums = [template_string(s, expansions) for s in proto_class.pyqt_flags_enums] # Handle the super-classes. i_class.superclasses = [] for superclass in proto_class.superclasses: superclass_name = superclass.iface_file.fq_cpp_name # Don't try and expand defined or scoped classes. if superclass.iface_file.module is not None or not superclass_name.is_simple: i_class.superclasses.append(superclass) continue for a, arg in enumerate(tmpl_names.args): if superclass_name.base_name == arg.definition.base_name: tmpl_arg = template.types.args[a] if tmpl_arg.type is ArgumentType.DEFINED: i_superclass = pm.find_class(p, symbol, IfaceFileType.CLASS, tmpl_arg.definition) elif tmpl_arg.type is ArgumentType.CLASS: i_superclass = tmpl_arg.definition else: pm.parser_error(p, symbol, "template argument '{0}' must expand to a class".format(superclass_name)) i_superclass = superclass i_class.superclasses.append(i_superclass) # Handle the enums. _instantiate_enums(tmpl_names, proto_class, template, i_class, expansions, pm) # Handle the variables. _instantiate_vars(tmpl_names, proto_class, template, i_class, expansions, pm) # Handle the typedefs. _instantiate_typedefs(p, symbol, tmpl_names, proto_class, template, i_class, expansions, pm) # Handle the ctors. i_class.ctors = _instantiate_ctors(tmpl_names, proto_class, template, i_class, expansions, pm) # Handle the methods. i_class.members = _instantiate_methods(proto_class.members, i_class.iface_file.module, pm) i_class.overloads = _instantiate_overloads(proto_class.overloads, proto_class.members, i_class.members, tmpl_names, proto_class, template, i_class, expansions, pm) # Handle the remaining handwritten code. i_class.bi_get_buffer_code = template_code(pm.spec, used, proto_class.bi_get_buffer_code, expansions) i_class.bi_release_buffer_code = template_code(pm.spec, used, proto_class.bi_release_buffer_code, expansions) i_class.convert_from_type_code = template_code(pm.spec, used, proto_class.convert_from_type_code, expansions) i_class.convert_to_subclass_code = template_code(pm.spec, used, proto_class.convert_to_subclass_code, expansions) i_class.convert_to_type_code = template_code(pm.spec, used, proto_class.convert_to_type_code, expansions) i_class.dealloc_code = template_code_blocks(pm.spec, used, proto_class.dealloc_code, expansions) i_class.dtor_virtual_catcher_code = template_code(pm.spec, used, proto_class.dtor_virtual_catcher_code, expansions) i_class.finalisation_code = template_code(pm.spec, used, proto_class.finalisation_code, expansions) i_class.gc_clear_code = template_code(pm.spec, used, proto_class.gc_clear_code, expansions) i_class.gc_traverse_code = template_code(pm.spec, used, proto_class.gc_traverse_code, expansions) i_class.instance_code = template_code(pm.spec, used, proto_class.instance_code, expansions) i_class.pickle_code = template_code(pm.spec, used, proto_class.pickle_code, expansions) i_class.type_code = template_code_blocks(pm.spec, used, proto_class.type_code, expansions) i_class.type_hint_code = template_code(pm.spec, used, proto_class.type_hint_code, expansions) pm.spec.classes.insert(0, i_class) def _instantiate_argument(proto_arg, proto_class, tmpl_names, template, i_class, expansions, pm): """ Return an instantiated Argument object. """ # Start with a shallow copy. i_arg = copy(proto_arg) # Descend into any sub-templates. if proto_arg.type is ArgumentType.TEMPLATE: proto_template = proto_arg.definition i_template = copy(proto_template) i_template.types = _instantiate_signature(proto_template.types, proto_class, tmpl_names, template, i_class, expansions, pm) i_arg.definition = i_template # Handle any default value. if proto_arg.default_value is not None: i_arg.default_value = [_instantiate_value(v, expansions) for v in proto_arg.default_value] # Handle any type hints. if proto_arg.type_hints is not None: i_arg.type_hints = instantiate_type_hints(pm.spec, proto_arg.type_hints, expansions) # Handle arguments that are unscoped names. if proto_arg.type is ArgumentType.DEFINED and proto_arg.definition.is_simple: name = proto_arg.definition.base_name for a, arg in enumerate(tmpl_names.args): if name == arg.definition.base_name: tad = template.types.args[a] i_arg.type = tad.type i_arg.definition = tad.definition # We take the constrained flag from the real type. i_arg.is_constrained = tad.is_constrained break else: # Handle the class name itself. if name == proto_class.iface_file.fq_cpp_name.base_name: i_arg.type = ArgumentType.CLASS i_arg.definition = i_class i_arg.original_type = None return i_arg def _instantiate_ctors(tmpl_names, proto_class, template, i_class, expansions, pm): """ Return a list of the instantiated ctors of a template class. """ i_ctors = [] used = i_class.iface_file.used for proto_ctor in proto_class.ctors: # Start with a shallow copy. i_ctor = copy(proto_ctor) i_ctor.py_signature = _instantiate_signature(proto_ctor.py_signature, proto_class, tmpl_names, template, i_class, expansions, pm, kw_args=proto_ctor.kw_args) if proto_ctor.cpp_signature is proto_ctor.py_signature: i_ctor.cpp_signature = i_ctor.py_signature else: i_ctor.cpp_signature = _instantiate_signature( proto_ctor.cpp_signature.cpp_signature, proto_class, tmpl_names, template, i_class, expansions, pm) i_ctor.method_code = template_code(pm.spec, used, proto_ctor.method_code, expansions) i_ctor.premethod_code = template_code(pm.spec, used, proto_ctor.premethod_code, expansions) # Handle the default ctor. if proto_class.default_ctor is proto_ctor: i_class.default_ctor = i_ctor i_ctors.append(i_ctor) return i_ctors def _instantiate_enums(tmpl_names, proto_class, template, i_class, expansions, pm): """ Instantiate the enums for a template class. """ for proto_enum in list(pm.spec.enums): if proto_enum.scope is not proto_class: continue # Start with a shallow copy. i_enum = copy(proto_enum) if proto_enum.fq_cpp_name is not None: i_enum.fq_cpp_name = normalised_scoped_name(proto_enum.fq_cpp_name, i_class) i_enum.cached_fq_cpp_name = cached_name(pm.spec, str(i_enum.fq_cpp_name)) if pm.in_main_module: if i_enum.py_name is not None: i_enum.py_name = True if i_enum.cached_fq_cpp_name is not None: i_enum.cached_fq_cpp_name.used = True i_enum.scope = i_class i_enum.module = i_class.iface_file.module i_enum.members = [] for proto_member in proto_enum.members: # Start with a shallow copy. w_member = copy(proto_member) w_member.scope = i_enum i_enum.members.append(w_member) pm.spec.enums.insert(0, i_enum) def _instantiate_methods(proto_methods, target_module, pm): """ Return a list of the instantiated methods of a template class or enum. """ i_methods = [] for proto_method in proto_methods: # Start with a shallow copy. i_method = copy(proto_method) i_method.module = target_module if pm.in_main_module: i_method.py_name.used = True i_methods.append(i_method) return i_methods def _instantiate_overloads(proto_overloads, proto_methods, i_methods, tmpl_names, proto_class, template, i_class, expansions, pm): """ Return a list of the instantiated overloads of a template class or enum. """ i_overloads = [] used = i_class.iface_file.used for proto_overload in proto_overloads: # Start with a shallow copy. i_overload = copy(proto_overload) for i_method, proto_method in zip(i_methods, proto_methods): if proto_overload.common is proto_method: i_overload.common = i_method break i_overload.py_signature = _instantiate_signature( proto_overload.py_signature, proto_class, tmpl_names, template, i_class, expansions, pm, kw_args=proto_overload.kw_args) if proto_overload.cpp_signature is proto_overload.py_signature: i_overload.cpp_signature = i_overload.py_signature else: i_overload.cpp_signature = _instantiate_signature( proto_overload.cpp_signature.cpp_signature, proto_class, tmpl_names, template, i_class, expansions, pm) i_overload.method_code = template_code(pm.spec, used, proto_overload.method_code, expansions) i_overload.premethod_code = template_code(pm.spec, used, proto_overload.premethod_code, expansions) i_overload.virtual_call_code = template_code(pm.spec, used, proto_overload.virtual_call_code, expansions) i_overload.virtual_catcher_code = template_code(pm.spec, used, proto_overload.virtual_catcher_code, expansions) i_overloads.append(i_overload) return i_overloads def _instantiate_signature(proto_signature, proto_class, tmpl_names, template, i_class, expansions, pm, kw_args=KwArgs.NONE): """ Return an instantiated Signature object. """ i_signature = Signature() for proto_arg in proto_signature.args: i_arg = _instantiate_argument(proto_arg, proto_class, tmpl_names, template, i_class, expansions, pm) i_signature.args.append(i_arg) # Make sure we have the name of any keyword argument. if pm.in_main_module and i_arg.name is not None: if kw_args is KwArgs.ALL or (kw_args is KwArgs.OPTIONAL and i_arg.default_value is not None): i_arg.name.used = True if proto_signature.result is not None: i_signature.result = _instantiate_argument(proto_signature.result, proto_class, tmpl_names, template, i_class, expansions, pm) return i_signature def instantiate_type_hints(spec, proto_type_hints, expansions): """ Return an instantiated TypeHints object. """ if proto_type_hints.hint_in is not None: hint_in = template_string(proto_type_hints.hint_in, expansions, scope_replacement='.') else: hint_in = None if proto_type_hints.hint_out is not None: hint_out = template_string(proto_type_hints.hint_out, expansions, scope_replacement='.') else: hint_out = None return TypeHints(hint_in=hint_in, hint_out=hint_out, default_value=proto_type_hints.default_value) def _instantiate_typedefs(p, symbol, tmpl_names, proto_class, template, i_class, expansions, pm): """ Instantiate the typedefs of a template class. """ for proto_typedef in pm.spec.typedefs: if proto_typedef.scope is not proto_class: continue # Start with a shallow copy. i_typedef = copy(proto_typedef) i_typedef.fq_cpp_name = normalised_scoped_name( proto_typedef.fq_cpp_name, i_class) i_typedef.scope = i_class i_typedef.module = i_class.iface_file.module i_typedef.type = _instantiate_argument(proto_typedef.type, proto_class, tmpl_names, template, i_class, expansions, pm) pm.add_typedef(p, symbol, i_typedef) def _instantiate_value(proto_value, expansions): """ Return an instantiated Value object. """ # We only handle the subset where the value is an function call, ie. a # template ctor. i_value = proto_value if proto_value.value_type is ValueType.FCALL and proto_value.value.result.type is ArgumentType.DEFINED: proto_name = proto_value.value.result.definition if proto_name.is_simple: i_name = ScopedName.parse( template_string(proto_name.base_name, expansions)) i_result = Argument(type=ArgumentType.DEFINED, definition=i_name) i_fcall = FunctionCall(result=i_result, args=proto_value.value.args) i_value = Value(ValueType.FCALL, value=i_fcall) return i_value def _instantiate_vars(tmpl_names, proto_class, template, i_class, expansions, pm): """ Instantiate the enums for a template class. """ used = i_class.iface_file.used for proto_var in pm.spec.variables: if proto_var.scope is not proto_class: continue # Start with a shallow copy. i_var = copy(proto_var) if pm.in_main_module: i_var.py_name.used = True i_var.fq_cpp_name = normalised_scoped_name(proto_var.fq_cpp_name, i_class) i_var.scope = i_class i_var.module = i_class.iface_file.module i_var.type = _instantiate_argument(proto_var.type, proto_class, tmpl_names, template, i_class, expansions, pm) i_var.access_code = template_code(pm.spec, used, proto_var.access_code, expansions) i_var.get_code = template_code(pm.spec, used, proto_var.get_code, expansions) i_var.set_code = template_code(pm.spec, used, proto_var.set_code, expansions) pm.spec.variables.append(i_var) sip-6.8.6/sipbuild/generator/outputs/000077500000000000000000000000001464421045000176525ustar00rootroot00000000000000sip-6.8.6/sipbuild/generator/outputs/__init__.py000066400000000000000000000004371464421045000217670ustar00rootroot00000000000000# SPDX-License-Identifier: BSD-2-Clause # Copyright (c) 2024 Phil Thompson # Publish the API. This is private to the rest of sip. from .api import output_api from .code import output_code from .extracts import output_extract from .pyi import output_pyi sip-6.8.6/sipbuild/generator/outputs/api.py000066400000000000000000000110221464421045000207710ustar00rootroot00000000000000# SPDX-License-Identifier: BSD-2-Clause # Copyright (c) 2024 Phil Thompson from enum import IntEnum from ..specification import AccessSpecifier, ArgumentType, ArrayArgument from .formatters import fmt_argument_as_py_type, fmt_scoped_py_name class IconNumber(IntEnum): """ The numbers of the different icons. The values are those used by the eric IDE. """ CLASS = 1 METHOD = 4 VARIABLE = 7 ENUM = 10 def output_api(spec, api_filename): """ Output a QScintilla API file. """ with open(api_filename, 'w', encoding='UTF-8') as af: module = spec.module _enums(af, spec, module) _variables(af, spec, module) for overload in module.overloads: if overload.common.module is module and overload.common.py_slot is None: _overload(af, spec, module, overload) for klass in spec.classes: if klass.iface_file.module is module and not klass.external: _enums(af, spec, module, scope=klass) _variables(af, spec, module, scope=klass) for ctor in klass.ctors: if ctor.access_specifier is not AccessSpecifier.PRIVATE: _ctor(af, spec, module, ctor, klass) for overload in klass.overloads: if overload.access_specifier is not AccessSpecifier.PRIVATE and overload.common.py_slot is None: _overload(af, spec, module, overload, scope=klass) def _ctor(af, spec, module, ctor, scope): """ Generate an API ctor. """ py_class = module.py_name + '.' + fmt_scoped_py_name(scope.scope, scope.py_name.name) py_arguments = _get_py_arguments(spec, ctor.py_signature) # Do the callable type form. args_s = ', '.join(py_arguments) af.write(f'{py_class}?{IconNumber.CLASS}({args_s})\n') # Do the call __init__ form. py_arguments.insert(0, 'self') args_s = ', '.join(py_arguments) af.write(f'{py_class}.__init__?{IconNumber.CLASS}({args_s})\n') def _enums(af, spec, module, scope=None): """ Generate the APIs for all the enums in a scope. """ for enum in spec.enums: if enum.module is module and enum.scope is scope: if enum.py_name is not None: py_name = fmt_scoped_py_name(enum.scope, enum.py_name.name) af.write(f'{module.py_name}.{py_name}?{IconNumber.ENUM}\n') for member in enum.members: enum_name = enum.module.py_name if enum.py_name is not None: enum_name += '.' enum_name += fmt_scoped_py_name(enum.scope, enum.py_name.name) af.write(f'{enum_name}.{member.py_name.name}?{IconNumber.ENUM}\n') def _variables(af, spec, module, scope=None): """ Generate the APIs for all the variables in a scope. """ for variable in spec.variables: if variable.module is module and variable.scope is scope: py_name = fmt_scoped_py_name(variable.scope, variable.py_name.name) af.write(f'{module.py_name}.{py_name}?{IconNumber.VARIABLE}\n') def _overload(af, spec, module, overload, scope=None): """ Generate a single API overload. """ py_arguments = _get_py_arguments(spec, overload.py_signature) py_results = _get_py_results(spec, overload.py_signature) scoped_py_name = fmt_scoped_py_name(scope, overload.common.py_name.name) args_s = ', '.join(py_arguments) s = f'{module.py_name}.{scoped_py_name}?{IconNumber.METHOD}({args_s})' if len(py_results) != 0: s += ' -> ' results_s = ', '.join(py_results) if len(py_results) > 1: s += '(' + results_s + ')' else: s += results_s s += '\n' af.write(s) def _get_py_arguments(spec, py_signature): """ Return the list of Python arguments. """ args = [] for arg in py_signature.args: if arg.array is not ArrayArgument.ARRAY_SIZE and arg.is_in: args.append(fmt_argument_as_py_type(spec, arg, default_value=True)) return args def _get_py_results(spec, py_signature): """ Return the list of Python results. """ results = [] if py_signature.result is not None: if py_signature.result.type is not ArgumentType.VOID or len(py_signature.result.derefs) != 0: results.append(fmt_argument_as_py_type(spec,py_signature.result)) for arg in py_signature.args: if arg.is_out: results.append(fmt_argument_as_py_type(spec, arg)) return results sip-6.8.6/sipbuild/generator/outputs/code.py000066400000000000000000011211171464421045000211420ustar00rootroot00000000000000# SPDX-License-Identifier: BSD-2-Clause # Copyright (c) 2024 Phil Thompson import os from ...exceptions import UserException from ...version import SIP_VERSION_STR from ..python_slots import (is_hash_return_slot, is_inplace_number_slot, is_inplace_sequence_slot, is_int_arg_slot, is_int_return_slot, is_multi_arg_slot, is_number_slot, is_rich_compare_slot, is_ssize_return_slot, is_void_return_slot, is_zero_arg_slot) from ..scoped_name import STRIP_GLOBAL, STRIP_NONE from ..specification import (AccessSpecifier, Argument, ArgumentType, ArrayArgument, CodeBlock, DocstringSignature, GILAction, IfaceFileType, KwArgs, MappedType, PyQtMethodSpecifier, PySlot, QualifierType, Transfer, ValueType, WrappedClass, WrappedEnum) from ..utils import find_method, py_as_int, same_signature from .formatters import (fmt_argument_as_cpp_type, fmt_argument_as_name, fmt_class_as_scoped_name, fmt_copying, fmt_enum_as_cpp_type, fmt_scoped_py_name, fmt_signature_as_cpp_declaration, fmt_signature_as_cpp_definition, fmt_signature_as_type_hint, fmt_value_list_as_cpp_expression) def output_code(spec, bindings, project, buildable): """ Output the C/C++ code and add it to the given buildable. """ module = spec.module py_debug = project.py_debug if spec.is_composite: source_name = os.path.join(buildable.build_dir, 'sip' + module.py_name + 'cmodule.c') with CompilationUnit(source_name, "Composite module code.", module, project, buildable, sip_api_file=False) as sf: _composite_module_code(sf, spec, py_debug) else: _module_code(spec, bindings, project, py_debug, buildable) def _internal_api_header(sf, spec, bindings, py_debug, name_cache_list): """ Generate the C++ internal module API header file and return its path name. """ module = spec.module module_name = spec.module.py_name # The include files. sf.write( f'''#ifndef _{module_name}API_H #define _{module_name}API_H ''') _declare_limited_api(sf, py_debug, module=module) _include_sip_h(sf, module) if _pyqt5(spec) or _pyqt6(spec): sf.write( ''' #include #include ''') # Define the qualifiers. qualifier_defines = [] _append_qualifier_defines(module, bindings, qualifier_defines) for imported_module in module.all_imports: _append_qualifier_defines(imported_module, bindings, qualifier_defines) if len(qualifier_defines) != 0: sf.write('\n/* These are the qualifiers that are enabled. */\n') for qualifier_define in qualifier_defines: sf.write(qualifier_define + '\n') sf.write('\n') # Generate references to (potentially) shared strings. no_intro = True for cached_name in name_cache_list: if not cached_name.used: continue if no_intro: sf.write( ''' /* * Convenient names to refer to various strings defined in this module. * Only the class names are part of the public API. */ ''') no_intro = False sf.write( f'''#define {_cached_name_ref(cached_name, as_nr=True)} {cached_name.offset} #define {_cached_name_ref(cached_name)} &sipStrings_{module_name}[{cached_name.offset}] ''') # These are common to all ABI versions. sf.write( f''' #define sipMalloc sipAPI_{module_name}->api_malloc #define sipFree sipAPI_{module_name}->api_free #define sipBuildResult sipAPI_{module_name}->api_build_result #define sipCallMethod sipAPI_{module_name}->api_call_method #define sipCallProcedureMethod sipAPI_{module_name}->api_call_procedure_method #define sipCallErrorHandler sipAPI_{module_name}->api_call_error_handler #define sipParseResultEx sipAPI_{module_name}->api_parse_result_ex #define sipParseResult sipAPI_{module_name}->api_parse_result #define sipParseArgs sipAPI_{module_name}->api_parse_args #define sipParseKwdArgs sipAPI_{module_name}->api_parse_kwd_args #define sipParsePair sipAPI_{module_name}->api_parse_pair #define sipInstanceDestroyed sipAPI_{module_name}->api_instance_destroyed #define sipInstanceDestroyedEx sipAPI_{module_name}->api_instance_destroyed_ex #define sipConvertFromSequenceIndex sipAPI_{module_name}->api_convert_from_sequence_index #define sipConvertFromSliceObject sipAPI_{module_name}->api_convert_from_slice_object #define sipConvertFromVoidPtr sipAPI_{module_name}->api_convert_from_void_ptr #define sipConvertToVoidPtr sipAPI_{module_name}->api_convert_to_void_ptr #define sipAddException sipAPI_{module_name}->api_add_exception #define sipNoFunction sipAPI_{module_name}->api_no_function #define sipNoMethod sipAPI_{module_name}->api_no_method #define sipAbstractMethod sipAPI_{module_name}->api_abstract_method #define sipBadClass sipAPI_{module_name}->api_bad_class #define sipBadCatcherResult sipAPI_{module_name}->api_bad_catcher_result #define sipBadCallableArg sipAPI_{module_name}->api_bad_callable_arg #define sipBadOperatorArg sipAPI_{module_name}->api_bad_operator_arg #define sipTrace sipAPI_{module_name}->api_trace #define sipTransferBack sipAPI_{module_name}->api_transfer_back #define sipTransferTo sipAPI_{module_name}->api_transfer_to #define sipSimpleWrapper_Type sipAPI_{module_name}->api_simplewrapper_type #define sipWrapper_Type sipAPI_{module_name}->api_wrapper_type #define sipWrapperType_Type sipAPI_{module_name}->api_wrappertype_type #define sipVoidPtr_Type sipAPI_{module_name}->api_voidptr_type #define sipGetPyObject sipAPI_{module_name}->api_get_pyobject #define sipGetAddress sipAPI_{module_name}->api_get_address #define sipGetMixinAddress sipAPI_{module_name}->api_get_mixin_address #define sipGetCppPtr sipAPI_{module_name}->api_get_cpp_ptr #define sipGetComplexCppPtr sipAPI_{module_name}->api_get_complex_cpp_ptr #define sipCallHook sipAPI_{module_name}->api_call_hook #define sipEndThread sipAPI_{module_name}->api_end_thread #define sipRaiseUnknownException sipAPI_{module_name}->api_raise_unknown_exception #define sipRaiseTypeException sipAPI_{module_name}->api_raise_type_exception #define sipBadLengthForSlice sipAPI_{module_name}->api_bad_length_for_slice #define sipAddTypeInstance sipAPI_{module_name}->api_add_type_instance #define sipPySlotExtend sipAPI_{module_name}->api_pyslot_extend #define sipAddDelayedDtor sipAPI_{module_name}->api_add_delayed_dtor #define sipCanConvertToType sipAPI_{module_name}->api_can_convert_to_type #define sipConvertToType sipAPI_{module_name}->api_convert_to_type #define sipForceConvertToType sipAPI_{module_name}->api_force_convert_to_type #define sipConvertToEnum sipAPI_{module_name}->api_convert_to_enum #define sipConvertToBool sipAPI_{module_name}->api_convert_to_bool #define sipReleaseType sipAPI_{module_name}->api_release_type #define sipConvertFromType sipAPI_{module_name}->api_convert_from_type #define sipConvertFromNewType sipAPI_{module_name}->api_convert_from_new_type #define sipConvertFromNewPyType sipAPI_{module_name}->api_convert_from_new_pytype #define sipConvertFromEnum sipAPI_{module_name}->api_convert_from_enum #define sipGetState sipAPI_{module_name}->api_get_state #define sipExportSymbol sipAPI_{module_name}->api_export_symbol #define sipImportSymbol sipAPI_{module_name}->api_import_symbol #define sipFindType sipAPI_{module_name}->api_find_type #define sipBytes_AsChar sipAPI_{module_name}->api_bytes_as_char #define sipBytes_AsString sipAPI_{module_name}->api_bytes_as_string #define sipString_AsASCIIChar sipAPI_{module_name}->api_string_as_ascii_char #define sipString_AsASCIIString sipAPI_{module_name}->api_string_as_ascii_string #define sipString_AsLatin1Char sipAPI_{module_name}->api_string_as_latin1_char #define sipString_AsLatin1String sipAPI_{module_name}->api_string_as_latin1_string #define sipString_AsUTF8Char sipAPI_{module_name}->api_string_as_utf8_char #define sipString_AsUTF8String sipAPI_{module_name}->api_string_as_utf8_string #define sipUnicode_AsWChar sipAPI_{module_name}->api_unicode_as_wchar #define sipUnicode_AsWString sipAPI_{module_name}->api_unicode_as_wstring #define sipConvertFromConstVoidPtr sipAPI_{module_name}->api_convert_from_const_void_ptr #define sipConvertFromVoidPtrAndSize sipAPI_{module_name}->api_convert_from_void_ptr_and_size #define sipConvertFromConstVoidPtrAndSize sipAPI_{module_name}->api_convert_from_const_void_ptr_and_size #define sipWrappedTypeName(wt) ((wt)->wt_td->td_cname) #define sipDeprecated sipAPI_{module_name}->api_deprecated #define sipGetReference sipAPI_{module_name}->api_get_reference #define sipKeepReference sipAPI_{module_name}->api_keep_reference #define sipRegisterProxyResolver sipAPI_{module_name}->api_register_proxy_resolver #define sipRegisterPyType sipAPI_{module_name}->api_register_py_type #define sipTypeFromPyTypeObject sipAPI_{module_name}->api_type_from_py_type_object #define sipTypeScope sipAPI_{module_name}->api_type_scope #define sipResolveTypedef sipAPI_{module_name}->api_resolve_typedef #define sipRegisterAttributeGetter sipAPI_{module_name}->api_register_attribute_getter #define sipEnableAutoconversion sipAPI_{module_name}->api_enable_autoconversion #define sipInitMixin sipAPI_{module_name}->api_init_mixin #define sipExportModule sipAPI_{module_name}->api_export_module #define sipInitModule sipAPI_{module_name}->api_init_module #define sipGetInterpreter sipAPI_{module_name}->api_get_interpreter #define sipSetTypeUserData sipAPI_{module_name}->api_set_type_user_data #define sipGetTypeUserData sipAPI_{module_name}->api_get_type_user_data #define sipPyTypeDict sipAPI_{module_name}->api_py_type_dict #define sipPyTypeName sipAPI_{module_name}->api_py_type_name #define sipGetCFunction sipAPI_{module_name}->api_get_c_function #define sipGetMethod sipAPI_{module_name}->api_get_method #define sipFromMethod sipAPI_{module_name}->api_from_method #define sipGetDate sipAPI_{module_name}->api_get_date #define sipFromDate sipAPI_{module_name}->api_from_date #define sipGetDateTime sipAPI_{module_name}->api_get_datetime #define sipFromDateTime sipAPI_{module_name}->api_from_datetime #define sipGetTime sipAPI_{module_name}->api_get_time #define sipFromTime sipAPI_{module_name}->api_from_time #define sipIsUserType sipAPI_{module_name}->api_is_user_type #define sipCheckPluginForType sipAPI_{module_name}->api_check_plugin_for_type #define sipUnicodeNew sipAPI_{module_name}->api_unicode_new #define sipUnicodeWrite sipAPI_{module_name}->api_unicode_write #define sipUnicodeData sipAPI_{module_name}->api_unicode_data #define sipGetBufferInfo sipAPI_{module_name}->api_get_buffer_info #define sipReleaseBufferInfo sipAPI_{module_name}->api_release_buffer_info #define sipIsOwnedByPython sipAPI_{module_name}->api_is_owned_by_python #define sipIsDerivedClass sipAPI_{module_name}->api_is_derived_class #define sipGetUserObject sipAPI_{module_name}->api_get_user_object #define sipSetUserObject sipAPI_{module_name}->api_set_user_object #define sipRegisterEventHandler sipAPI_{module_name}->api_register_event_handler #define sipConvertToArray sipAPI_{module_name}->api_convert_to_array #define sipConvertToTypedArray sipAPI_{module_name}->api_convert_to_typed_array #define sipEnableGC sipAPI_{module_name}->api_enable_gc #define sipPrintObject sipAPI_{module_name}->api_print_object #define sipLong_AsChar sipAPI_{module_name}->api_long_as_char #define sipLong_AsSignedChar sipAPI_{module_name}->api_long_as_signed_char #define sipLong_AsUnsignedChar sipAPI_{module_name}->api_long_as_unsigned_char #define sipLong_AsShort sipAPI_{module_name}->api_long_as_short #define sipLong_AsUnsignedShort sipAPI_{module_name}->api_long_as_unsigned_short #define sipLong_AsInt sipAPI_{module_name}->api_long_as_int #define sipLong_AsUnsignedInt sipAPI_{module_name}->api_long_as_unsigned_int #define sipLong_AsLong sipAPI_{module_name}->api_long_as_long #define sipLong_AsUnsignedLong sipAPI_{module_name}->api_long_as_unsigned_long #define sipLong_AsLongLong sipAPI_{module_name}->api_long_as_long_long #define sipLong_AsUnsignedLongLong sipAPI_{module_name}->api_long_as_unsigned_long_long #define sipLong_AsSizeT sipAPI_{module_name}->api_long_as_size_t #define sipVisitWrappers sipAPI_{module_name}->api_visit_wrappers #define sipRegisterExitNotifier sipAPI_{module_name}->api_register_exit_notifier ''') # These are dependent on the specific ABI version. if spec.abi_version >= (13, 0): # ABI v13.6 and later. if spec.abi_version >= (13, 6): sf.write( f'''#define sipPyTypeDictRef sipAPI_{module_name}->api_py_type_dict_ref ''') # ABI v13.1 and later. if spec.abi_version >= (13, 1): sf.write( f'''#define sipNextExceptionHandler sipAPI_{module_name}->api_next_exception_handler ''') # ABI v13.0 and later. */ sf.write( f'''#define sipIsEnumFlag sipAPI_{module_name}->api_is_enum_flag #define sipConvertToTypeUS sipAPI_{module_name}->api_convert_to_type_us #define sipForceConvertToTypeUS sipAPI_{module_name}->api_force_convert_to_type_us #define sipReleaseTypeUS sipAPI_{module_name}->api_release_type_us ''') else: # ABI v12.13 and later. if spec.abi_version >= (12, 13): sf.write( f'''#define sipPyTypeDictRef sipAPI_{module_name}->api_py_type_dict_ref ''') # ABI v12.9 and later. if spec.abi_version >= (12, 9): sf.write( f'''#define sipNextExceptionHandler sipAPI_{module_name}->api_next_exception_handler ''') # ABI v12.8 and earlier. sf.write( f'''#define sipSetNewUserTypeHandler sipAPI_{module_name}->api_set_new_user_type_handler #define sipGetFrame sipAPI_{module_name}->api_get_frame #define sipSetDestroyOnExit sipAPI_{module_name}->api_set_destroy_on_exit #define sipEnableOverflowChecking sipAPI_{module_name}->api_enable_overflow_checking #define sipIsAPIEnabled sipAPI_{module_name}->api_is_api_enabled #define sipClearAnySlotReference sipAPI_{module_name}->api_clear_any_slot_reference #define sipConnectRx sipAPI_{module_name}->api_connect_rx #define sipConvertRx sipAPI_{module_name}->api_convert_rx #define sipDisconnectRx sipAPI_{module_name}->api_disconnect_rx #define sipFreeSipslot sipAPI_{module_name}->api_free_sipslot #define sipInvokeSlot sipAPI_{module_name}->api_invoke_slot #define sipInvokeSlotEx sipAPI_{module_name}->api_invoke_slot_ex #define sipSameSlot sipAPI_{module_name}->api_same_slot #define sipSaveSlot sipAPI_{module_name}->api_save_slot #define sipVisitSlot sipAPI_{module_name}->api_visit_slot ''') if spec.abi_version >= (12, 8): # ABI v12.8 and later. sf.write( f'''#define sipIsPyMethod sipAPI_{module_name}->api_is_py_method_12_8 ''') else: # ABI v12.7 and earlier. sf.write( f'''#define sipIsPyMethod sipAPI_{module_name}->api_is_py_method ''') # The name strings. sf.write( f''' /* The strings used by this module. */ extern const char sipStrings_{module_name}[]; ''') _module_api(sf, spec, bindings) sf.write( f''' /* The SIP API, this module's API and the APIs of any imported modules. */ extern const sipAPIDef *sipAPI_{module_name}; extern sipExportedModuleDef sipModuleAPI_{module_name}; ''') if len(module.needed_types) != 0: sf.write(f'extern sipTypeDef *sipExportedTypes_{module_name}[];\n') for imported_module in module.all_imports: imported_module_name = imported_module.py_name _imported_module_api(sf, spec, imported_module) if len(imported_module.needed_types) != 0: sf.write(f'extern sipImportedTypeDef sipImportedTypes_{module_name}_{imported_module_name}[];\n') if imported_module.nr_virtual_error_handlers != 0: sf.write(f'extern sipImportedVirtErrorHandlerDef sipImportedVirtErrorHandlers_{module_name}_{imported_module_name}[];\n') if imported_module.nr_exceptions != 0: sf.write(f'extern sipImportedExceptionDef sipImportedExceptions_{module_name}_{imported_module_name}[];\n') if _pyqt5(spec) or _pyqt6(spec): sf.write( f''' typedef const QMetaObject *(*sip_qt_metaobject_func)(sipSimpleWrapper *, sipTypeDef *); extern sip_qt_metaobject_func sip_{module_name}_qt_metaobject; typedef int (*sip_qt_metacall_func)(sipSimpleWrapper *, sipTypeDef *, QMetaObject::Call, int, void **); extern sip_qt_metacall_func sip_{module_name}_qt_metacall; typedef bool (*sip_qt_metacast_func)(sipSimpleWrapper *, const sipTypeDef *, const char *, void **); extern sip_qt_metacast_func sip_{module_name}_qt_metacast; ''') # Handwritten code. sf.write_code(spec.exported_header_code) sf.write_code(module.module_header_code) # Make sure any header code needed by the default exception is included. if module.default_exception is not None: sf.write_code(module.default_exception.iface_file.type_header_code) # Note that we don't forward declare the virtual handlers. This is because # we would need to #include everything needed for their argument types. sf.write('\n#endif\n') def _make_part_name(buildable, module_name, part_nr, source_suffix): """ Return the filename of a source code part on the heap. """ return os.path.join(buildable.build_dir, f'sip{module_name}part{part_nr}{source_suffix}') def _composite_module_code(sf, spec, py_debug): """ Output the code for a composite module. """ module = spec.module _declare_limited_api(sf, py_debug) _include_sip_h(sf, module) sf.write( ''' static void sip_import_component_module(PyObject *d, const char *name) { PyObject *mod; PyErr_Clear(); mod = PyImport_ImportModule(name); /* * Note that we don't complain if the module can't be imported. This * is a favour to Linux distro packagers who like to split PyQt into * different sub-packages. */ if (mod) { PyDict_Merge(d, PyModule_GetDict(mod), 0); Py_DECREF(mod); } } ''') _module_docstring(sf, module) _module_init_start(sf, spec) _module_definition(sf, module) sf.write( ''' PyObject *sipModule, *sipModuleDict; if ((sipModule = PyModule_Create(&sip_module_def)) == SIP_NULLPTR) return SIP_NULLPTR; sipModuleDict = PyModule_GetDict(sipModule); ''') for mod in module.all_imports: sf.write( f' sip_import_component_module(sipModuleDict, "{mod.fq_py_name}");\n') sf.write( ''' PyErr_Clear(); return sipModule; } ''') def _module_code(spec, bindings, project, py_debug, buildable): """ Generate the C/C++ code for a module. """ module = spec.module module_name = module.py_name parts = bindings.concatenate source_suffix = bindings.source_suffix if source_suffix is None: source_suffix = '.c' if spec.c_bindings else '.cpp' # Calculate the number of files in each part. if parts: nr_files = 1 for iface_file in spec.iface_files: if iface_file.module is module and iface_file.type is not IfaceFileType.EXCEPTION: nr_files += 1 max_per_part = (nr_files + parts - 1) // parts files_in_part = 1 this_part = 0 source_name = _make_part_name(buildable, module_name, 0, source_suffix) else: source_name = os.path.join(buildable.build_dir, 'sip' + module_name + 'cmodule' + source_suffix) sf = CompilationUnit(source_name, "Module code.", module, project, buildable) # Include the library headers for types used by virtual handlers, module # level functions, module level variables, exceptions and Qt meta types. _used_includes(sf, module.used) sf.write_code(module.unit_postinclude_code) # If there should be a Qt support API then generate stubs values for the # optional parts. These should be undefined in %ModuleCode if a C++ # implementation is provided. if spec.abi_version < (13, 0) and _module_supports_qt(spec): sf.write( ''' #define sipQtCreateUniversalSignal 0 #define sipQtFindUniversalSignal 0 #define sipQtEmitSignal 0 #define sipQtConnectPySignal 0 #define sipQtDisconnectPySignal 0 ''') # Transform the name cache. name_cache_list = _name_cache_as_list(spec.name_cache) # Define the names. _name_cache(sf, spec, name_cache_list) # Generate the C++ code blocks. sf.write_code(module.module_code) # Generate any virtual handlers. for handler in spec.virtual_handlers: _virtual_handler(sf, spec, handler) # Generate any virtual error handlers. for virtual_error_handler in spec.virtual_error_handlers: if virtual_error_handler.module is module: self_name = _use_in_code(virtual_error_handler.code, 'sipPySelf') state_name = _use_in_code(virtual_error_handler.code, 'sipGILState') sf.write( f''' void sipVEH_{module_name}_{virtual_error_handler.name}(sipSimpleWrapper *{self_name}, sip_gilstate_t {state_name}) {{ ''') sf.write_code(virtual_error_handler.code) sf.write('}\n') # Generate the global functions. slot_extenders = False for member in module.global_functions: if member.py_slot is None: _ordinary_function(sf, spec, bindings, member) else: # Make sure that there is still an overload and we haven't moved # them all to classes. for overload in module.overloads: if overload.common is member: _py_slot(sf, spec, bindings, member) slot_extenders = True break # Generate the global functions for any hidden namespaces. for klass in spec.classes: if klass.iface_file.module is module and klass.is_hidden_namespace: for member in klass.members: if member.py_slot is None: _ordinary_function(sf, spec, bindings, member, scope=klass) # Generate any class specific __init__ or slot extenders. init_extenders = False for klass in module.proxies: if len(klass.ctors) != 0: _type_init(sf, spec, bindings, klass) init_extenders = True for member in klass.members: _py_slot(sf, spec, bindings, member, scope=klass) slot_extenders = True # Generate any __init__ extender table. if init_extenders: sf.write( ''' static sipInitExtenderDef initExtenders[] = { ''') first_field = '-1, ' if spec.abi_version < (13, 0) else '' for klass in module.proxies: if len(klass.ctors) != 0: klass_name = klass.iface_file.fq_cpp_name.as_word encoded_type = _encoded_type(module, klass) sf.write(f' {{{first_field}init_type_{klass_name}, {encoded_type}, SIP_NULLPTR}},\n') sf.write( f''' {{{first_field}SIP_NULLPTR, {{0, 0, 0}}, SIP_NULLPTR}} }}; ''') # Generate any slot extender table. if slot_extenders: sf.write( ''' static sipPySlotExtenderDef slotExtenders[] = {\n''') for member in module.global_functions: if member.py_slot is None: continue for overload in module.overloads: if overload.common is member: member_name = member.py_name slot_name = _get_slot_name(member.py_slot) sf.write( f' {{(void *)slot_{member_name}, {slot_name}, {{0, 0, 0}}}},\n') break for klass in module.proxies: for member in klass.members: klass_name = klass.iface_file.fq_cpp_name.as_word member_name = member.py_name slot_name = _get_slot_name(member.py_slot) encoded_type = _encoded_type(module, klass) sf.write(f' {{(void *)slot_{klass_name}_{member_name}, {slot_name}, {encoded_type}}},\n') sf.write( ''' {SIP_NULLPTR, (sipPySlotType)0, {0, 0, 0}} }; ''') # Generate the global access functions. _access_functions(sf, spec) # Generate any sub-class convertors. nr_subclass_convertors = _subclass_convertors(sf, spec, module) # Generate the external classes table if needed. has_external = False for klass in spec.classes: if not klass.external: continue if klass.iface_file.module is not module: continue if not has_external: sf.write( ''' /* This defines each external type declared in this module, */ static sipExternalTypeDef externalTypesTable[] = { ''') has_external = True type_nr = klass.iface_file.type_nr klass_py = klass.iface_file.fq_cpp_name.as_py sf.write(f' {{{type_nr}, "{klass_py}"}},\n') if has_external: sf.write( ''' {-1, SIP_NULLPTR} }; ''') # Generate any enum slot tables. for enum in spec.enums: if enum.module is not module or enum.fq_cpp_name is None: continue if len(enum.slots) == 0: continue for member in enum.slots: _py_slot(sf, spec, bindings, member, scope=enum) enum_name = enum.fq_cpp_name.as_word sf.write( f''' static sipPySlotDef slots_{enum_name}[] = {{ ''') for member in enum.slots: if member.py_slot is not None: member_name = member.py_name slot_name = _get_slot_name(member.py_slot) sf.write(f' {{(void *)slot_{enum_name}_{member_name}, {slot_name}}},\n') sf.write( ''' {SIP_NULLPTR, (sipPySlotType)0} }; ''') # Generate the enum type structures while recording the order in which they # are generated. Note that we go through the sorted table of needed types # rather than the unsorted list of all enums. needed_enums = [] for needed_type in module.needed_types: if needed_type.type is not ArgumentType.ENUM: continue enum = needed_type.definition scope_type_nr = -1 if enum.scope is None else enum.scope.iface_file.type_nr if len(needed_enums) == 0: sf.write('static sipEnumTypeDef enumTypes[] = {\n') cpp_name = _get_normalised_cached_name(enum.cached_fq_cpp_name) py_name = _get_normalised_cached_name(enum.py_name) if spec.abi_version >= (13, 0): base_type = 'SIP_ENUM_' + enum.base_type.name nr_members = len(enum.members) sf.write( f' {{{{SIP_NULLPTR, SIP_TYPE_ENUM, sipNameNr_{cpp_name}, SIP_NULLPTR, 0}}, {base_type}, sipNameNr_{py_name}, {scope_type_nr}, {nr_members}') else: sip_type = 'SIP_TYPE_SCOPED_ENUM' if enum.is_scoped else 'SIP_TYPE_ENUM' sf.write( f' {{{{-1, SIP_NULLPTR, SIP_NULLPTR, {sip_type}, sipNameNr_{cpp_name}, SIP_NULLPTR, 0}}, sipNameNr_{py_name}, {scope_type_nr}') if len(enum.slots) == 0: sf.write(', SIP_NULLPTR') else: sf.write(', slots_' + enum.fq_cpp_name.as_word) sf.write('},\n') needed_enums.append(enum) if len(needed_enums) != 0: sf.write('};\n') if spec.abi_version >= (13, 0): nr_enum_members = -1 else: nr_enum_members = _enum_member_table(sf, spec) # Generate the types table. if len(module.needed_types) != 0: _types_table(sf, module, needed_enums) # Generate the typedefs table. if module.nr_typedefs > 0: sf.write( ''' /* * These define each typedef in this module. */ static sipTypedefDef typedefsTable[] = { ''') for typedef in spec.typedefs: if typedef.module is not module: continue cpp_name = typedef.fq_cpp_name.cpp_stripped(STRIP_GLOBAL) sf.write(f' {{"{cpp_name}", "') # The default behaviour isn't right in a couple of cases. # TODO: is this still true? if typedef.type.type is ArgumentType.LONGLONG: sf.write('long long') elif typedef.type.type is ArgumentType.ULONGLONG: sf.write('unsigned long long') else: sf.write( fmt_argument_as_cpp_type(spec, typedef.type, strip=STRIP_GLOBAL, use_typename=False)) sf.write('"},\n') sf.write('};\n') # Generate the error handlers table. has_virtual_error_handlers = False for handler in spec.virtual_error_handlers: if handler.module is not module: continue if not has_virtual_error_handlers: has_virtual_error_handlers = True sf.write( ''' /* * This defines the virtual error handlers that this module implements and * can be used by other modules. */ static sipVirtErrorHandlerDef virtErrorHandlersTable[] = { ''') sf.write(f' {{"{handler.name}", sipVEH_{module_name}_{handler.name}}},\n') if has_virtual_error_handlers: sf.write( ''' {SIP_NULLPTR, SIP_NULLPTR} }; ''') # Generate the tables for things we are importing. for imported_module in module.all_imports: imported_module_name = imported_module.py_name if len(imported_module.needed_types) != 0: sf.write( f''' /* This defines the types that this module needs to import from {imported_module_name}. */ sipImportedTypeDef sipImportedTypes_{module_name}_{imported_module_name}[] = {{ ''') for needed_type in imported_module.needed_types: if needed_type.type is ArgumentType.MAPPED: type_name = needed_type.definition.cpp_name else: if needed_type.type is ArgumentType.CLASS: scoped_name = needed_type.definition.iface_file.fq_cpp_name else: scoped_name = needed_type.definition.fq_cpp_name type_name = scoped_name.cpp_stripped(STRIP_GLOBAL) sf.write(f' {{"{type_name}"}},\n') sf.write( ''' {SIP_NULLPTR} }; ''') if imported_module.nr_virtual_error_handlers > 0: sf.write( f''' /* * This defines the virtual error handlers that this module needs to import * from {imported_module_name}. */ sipImportedVirtErrorHandlerDef sipImportedVirtErrorHandlers_{module_name}_{imported_module_name}[] = {{ ''') # The handlers are unordered so search for each in turn. There # will probably be only one so speed isn't an issue. for i in range(imported_module.nr_virtual_error_handlers): for handler in spec.virtual_error_handlers: if handler.module is imported_module and handler.handler_nr == i: sf.write(f' {{"{handler.name}"}},\n') sf.write( ''' {SIP_NULLPTR} }; ''') if imported_module.nr_exceptions > 0: sf.write( f''' /* * This defines the exception objects that this module needs to import from * {imported_module_name}. */ sipImportedExceptionDef sipImportedExceptions_{module_name}_{imported_module_name}[] = {{ ''') # The exceptions are unordered so search for each in turn. There # will probably be very few so speed isn't an issue. for i in range(imported_module.nr_exceptions): for exception in spec.exceptions: if exception.iface_file.module is imported_module and exception.exception_nr == i: sf.write(f' {{"{exception.py_name}"}},\n') sf.write( ''' {SIP_NULLPTR} }; ''') if len(module.all_imports) != 0: sf.write( ''' /* This defines the modules that this module needs to import. */ static sipImportedModuleDef importsTable[] = { ''') for imported_module in module.all_imports: imported_module_name = imported_module.py_name types = handlers = exceptions = 'SIP_NULLPTR' if len(imported_module.needed_types) != 0: types = f'sipImportedTypes_{module_name}_{imported_module_name}' if imported_module.nr_virtual_error_handlers != 0: handlers = f'sipImportedVirtErrorHandlers_{module_name}_{imported_module_name}' if imported_module.nr_exceptions != 0: exceptions = f'sipImportedExceptions_{module_name}_{imported_module_name}' sf.write(f' {{"{imported_module.fq_py_name}", {types}, {handlers}, {exceptions}}},\n') sf.write( ''' {SIP_NULLPTR, SIP_NULLPTR, SIP_NULLPTR, SIP_NULLPTR} }; ''') # Generate the table of sub-class convertors if nr_subclass_convertors > 0: sf.write( ''' /* This defines the class sub-convertors that this module defines. */ static sipSubClassConvertorDef convertorsTable[] = { ''') for klass in spec.classes: if klass.iface_file.module is not module: continue if klass.convert_to_subclass_code is None: continue klass_name = klass.iface_file.fq_cpp_name.as_word encoded_type = _encoded_type(module, klass.subclass_base) sf.write(f' {{sipSubClass_{klass_name}, {encoded_type}, SIP_NULLPTR}},\n') sf.write( ''' {SIP_NULLPTR, {0, 0, 0}, SIP_NULLPTR} }; ''') # Generate any license information. if module.license is not None: license = module.license licensee = 'SIP_NULLPTR' if license.licensee is None else '"' + license.licensee + '"' timestamp = 'SIP_NULLPTR' if license.timestamp is None else '"' + license.timestamp + '"' signature = 'SIP_NULLPTR' if license.signature is None else '"' + license.signature + '"' sf.write( f''' /* Define the module's license. */ static sipLicenseDef module_license = {{ "{license.type}", {licensee}, {timestamp}, {signature} }}; ''') # Generate each instance table. is_inst_class = _class_instances(sf, spec) is_inst_voidp = _void_pointer_instances(sf, spec) is_inst_char = _char_instances(sf, spec) is_inst_string = _string_instances(sf, spec) is_inst_int = _int_instances(sf, spec) is_inst_long = _long_instances(sf, spec) is_inst_ulong = _unsigned_long_instances(sf, spec) is_inst_longlong = _long_long_instances(sf, spec) is_inst_ulonglong = _unsigned_long_long_instances(sf, spec) is_inst_double = _double_instances(sf, spec) # Generate any exceptions support. if bindings.exceptions: if module.nr_exceptions > 0: sf.write( f''' PyObject *sipExportedExceptions_{module_name}[{module.nr_exceptions + 1}]; ''') if _abi_has_next_exception_handler(spec): _exception_handler(sf, spec) # Generate any Qt support API. if spec.abi_version < (13, 0) and _module_supports_qt(spec): sf.write( f''' /* This defines the Qt support API. */ static sipQtAPI qtAPI = {{ &sipExportedTypes_{module_name}[{spec.pyqt_qobject.iface_file.type_nr}], sipQtCreateUniversalSignal, sipQtFindUniversalSignal, sipQtCreateUniversalSlot, sipQtDestroyUniversalSlot, sipQtFindSlot, sipQtConnect, sipQtDisconnect, sipQtSameSignalSlotName, sipQtFindSipslot, sipQtEmitSignal, sipQtConnectPySignal, sipQtDisconnectPySignal }}; ''') imports_table = _optional_ptr(len(module.all_imports) != 0, 'importsTable') exported_types = _optional_ptr(len(module.needed_types) != 0, 'sipExportedTypes_' + module_name) external_types = _optional_ptr(has_external, 'externalTypesTable') typedefs_table = _optional_ptr(module.nr_typedefs != 0, 'typedefsTable') sf.write( f''' /* This defines this module. */ sipExportedModuleDef sipModuleAPI_{module_name} = {{ SIP_NULLPTR, {spec.abi_version[1]}, sipNameNr_{_get_normalised_cached_name(module.fq_py_name)}, 0, sipStrings_{module_name}, {imports_table}, ''') if spec.abi_version < (13, 0): qt_api = _optional_ptr(_module_supports_qt(spec), '&qtAPI') sf.write(f' {qt_api},\n') sf.write( f''' {len(module.needed_types)}, {exported_types}, {external_types}, ''') if nr_enum_members >= 0: enum_members = _optional_ptr(nr_enum_members > 0, 'enummembers') sf.write( f''' {nr_enum_members}, {enum_members}, ''') veh_table = _optional_ptr(has_virtual_error_handlers, 'virtErrorHandlersTable') convertors = _optional_ptr(nr_subclass_convertors > 0, 'convertorsTable') type_instances = _optional_ptr(is_inst_class, 'typeInstances') void_ptr_instances = _optional_ptr(is_inst_voidp, 'voidPtrInstances') char_instances = _optional_ptr(is_inst_char, 'charInstances') string_instances = _optional_ptr(is_inst_string, 'stringInstances') int_instances = _optional_ptr(is_inst_int, 'intInstances') long_instances = _optional_ptr(is_inst_long, 'longInstances') unsigned_long_instances = _optional_ptr(is_inst_ulong, 'unsignedLongInstances') long_long_instances = _optional_ptr(is_inst_longlong, 'longLongInstances') unsigned_long_long_instances = _optional_ptr(is_inst_ulonglong, 'unsignedLongLongInstances') double_instances = _optional_ptr(is_inst_double, 'doubleInstances') module_license = _optional_ptr(module.license is not None, '&module_license') exported_exceptions = _optional_ptr(module.nr_exceptions > 0, 'sipExportedExceptions_' + module_name) slot_extender_table = _optional_ptr(slot_extenders, 'slotExtenders') init_extender_table = _optional_ptr(init_extenders, 'initExtenders') delayed_dtors = _optional_ptr(module.has_delayed_dtors, 'sipDelayedDtors') sf.write( f''' {module.nr_typedefs}, {typedefs_table}, {veh_table}, {convertors}, {{{type_instances}, {void_ptr_instances}, {char_instances}, {string_instances}, {int_instances}, {long_instances}, {unsigned_long_instances}, {long_long_instances}, {unsigned_long_long_instances}, {double_instances}}}, {module_license}, {exported_exceptions}, {slot_extender_table}, {init_extender_table}, {delayed_dtors}, SIP_NULLPTR, ''') if spec.abi_version < (13, 0): # The unused version support. sf.write( ''' SIP_NULLPTR, SIP_NULLPTR, ''') exception_handler = _optional_ptr( (_abi_has_next_exception_handler(spec) and bindings.exceptions and module.nr_exceptions > 0), 'sipExceptionHandler_' + module_name) sf.write( f''' {exception_handler}, }}; ''') _module_docstring(sf, module) # Generate the storage for the external API pointers. sf.write( f''' /* The SIP API and the APIs of any imported modules. */ const sipAPIDef *sipAPI_{module_name}; ''') if _pyqt5(spec) or _pyqt6(spec): sf.write( f''' sip_qt_metaobject_func sip_{module_name}_qt_metaobject; sip_qt_metacall_func sip_{module_name}_qt_metacall; sip_qt_metacast_func sip_{module_name}_qt_metacast; ''') # Generate the Python module initialisation function. _module_init_start(sf, spec) # Generate the global functions. sf.write(' static PyMethodDef sip_methods[] = {\n') _global_function_table_entries(sf, spec, bindings, module.global_functions) # Generate the global functions for any hidden namespaces. for klass in spec.classes: if klass.iface_file.module is module and klass.is_hidden_namespace: _global_function_table_entries(sf, spec, bindings, klass.members) sf.write( ''' {SIP_NULLPTR, SIP_NULLPTR, 0, SIP_NULLPTR} }; ''') _module_definition(sf, module, method_table='sip_methods') sf.write('\n PyObject *sipModule, *sipModuleDict;\n') if spec.sip_module: sf.write(' PyObject *sip_sipmod, *sip_capiobj;\n\n') # Generate any pre-initialisation code. sf.write_code(module.preinitialisation_code) sf.write( ''' /* Initialise the module and get it's dictionary. */ if ((sipModule = PyModule_Create(&sip_module_def)) == SIP_NULLPTR) return SIP_NULLPTR; sipModuleDict = PyModule_GetDict(sipModule); ''') _sip_api(sf, spec) # Generate any initialisation code. sf.write_code(module.initialisation_code) abi_major, abi_minor = spec.abi_version sf.write( f''' /* Export the module and publish it's API. */ if (sipExportModule(&sipModuleAPI_{module_name}, {abi_major}, {abi_minor}, 0) < 0) {{ Py_DECREF(sipModule); return SIP_NULLPTR; }} ''') if _pyqt5(spec) or _pyqt6(spec): # Import the helpers. sf.write( f''' sip_{module_name}_qt_metaobject = (sip_qt_metaobject_func)sipImportSymbol("qtcore_qt_metaobject"); sip_{module_name}_qt_metacall = (sip_qt_metacall_func)sipImportSymbol("qtcore_qt_metacall"); sip_{module_name}_qt_metacast = (sip_qt_metacast_func)sipImportSymbol("qtcore_qt_metacast"); if (!sip_{module_name}_qt_metacast) Py_FatalError("Unable to import qtcore_qt_metacast"); ''') sf.write( f''' /* Initialise the module now all its dependencies have been set up. */ if (sipInitModule(&sipModuleAPI_{module_name}, sipModuleDict) < 0) {{ Py_DECREF(sipModule); return SIP_NULLPTR; }} ''') _types_inline(sf, spec) _py_objects(sf, spec) # Create any exception objects. for exception in spec.exceptions: if exception.iface_file.module is not module: continue if exception.exception_nr < 0: continue if exception.builtin_base_exception is not None: exception_type = 'PyExc_' + exception.builtin_base_exception else: exception_type = 'sipException_' + exception.defined_base_exception.iface_file.fq_cpp_name.as_word sf.write( f''' if ((sipExportedExceptions_{module_name}[{exception.exception_nr}] = PyErr_NewException( "{module_name}.{exception.py_name}", {exception_type}, SIP_NULLPTR)) == SIP_NULLPTR || PyDict_SetItemString(sipModuleDict, "{exception.py_name}", sipExportedExceptions_{module_name}[{exception.exception_nr}]) < 0) {{ Py_DECREF(sipModule); return SIP_NULLPTR; }} ''') if module.nr_exceptions > 0: sf.write( f''' sipExportedExceptions_{module_name}[{module.nr_exceptions}] = SIP_NULLPTR; ''') # Generate the enum meta-type registrations for PyQt6 so that they can be # used in queued connections. if _pyqt6(spec): for enum in spec.enums: if enum.module is not module or enum.fq_cpp_name is None: continue if enum.is_protected: continue if isinstance(enum.scope, WrappedClass) and enum.scope.pyqt_no_qmetaobject: continue sf.write(f' qMetaTypeId<{enum.fq_cpp_name.as_cpp}>();\n') # Generate any post-initialisation code. */ sf.write_code(module.postinitialisation_code) sf.write( ''' return sipModule; } ''') # Generate the interface source files. for iface_file in spec.iface_files: if iface_file.module is module and iface_file.type is not IfaceFileType.EXCEPTION: need_postinc = False use_sf = None if parts: if files_in_part == max_per_part: # Close the old part. sf.close() # Create a new one. files_in_part = 1 this_part += 1 source_name = _make_part_name(buildable, module_name, this_part, source_suffix) sf = CompilationUnit(source_name, "Module code.", module, project, buildable) need_postinc = True else: files_in_part += 1 if iface_file.file_extension is None: # The interface file should use this source file rather # than create one of its own. use_sf = sf _iface_file_cpp(spec, bindings, project, buildable, py_debug, iface_file, need_postinc, source_suffix, use_sf) sf.close() header_name = os.path.join(buildable.build_dir, f'sipAPI{module_name}.h') with SourceFile(header_name, "Internal module API header file.", module, project, buildable.headers) as sf: _internal_api_header(sf, spec, bindings, py_debug, name_cache_list) def _name_cache_as_list(name_cache): """ Return a name cache as a correctly ordered list of CachedName objects. """ name_cache_list = [] # Create the list sorted first by descending name length and then # alphabetical order. for k in sorted(name_cache.keys(), reverse=True): name_cache_list.extend(sorted(name_cache[k], key=lambda k: k.name)) # Set the offset into the string pool for every used name. offset = 0 for cached_name in name_cache_list: if not cached_name.used: continue name_len = len(cached_name.name) # See if the tail of a previous used name could be used instead. for prev_name in name_cache_list: prev_name_len = len(prev_name.name) if prev_name_len <= name_len: break if not prev_name.used or prev_name.is_substring: continue if prev_name.name.endswith(cached_name.name): cached_name.is_substring = True cached_name.offset = prev_name.offset + prev_name_len - name_len; break if not cached_name.is_substring: cached_name.offset = offset offset += name_len + 1 return name_cache_list def _name_cache(sf, spec, name_cache_list): """ Generate the name cache definition. """ sf.write( f''' /* Define the strings used by this module. */ const char sipStrings_{spec.module.py_name}[] = {{ ''') for name in name_cache_list: if not name.used or name.is_substring: continue sf.write(' ') for ch in name.name: sf.write(f"'{ch}', ") sf.write('0,\n') sf.write('};\n') def _types_table(sf, module, needed_enums): """ Generate the types table for a module. """ module_name = module.py_name sf.write( f''' /* * This defines each type in this module. */ sipTypeDef *sipExportedTypes_{module_name}[] = {{ ''') for needed_type in module.needed_types: if needed_type.type is ArgumentType.CLASS: klass = needed_type.definition if klass.external: sf.write(' 0,\n') elif not klass.is_hidden_namespace: sf.write(f' &sipTypeDef_{module_name}_{klass.iface_file.fq_cpp_name.as_word}.ctd_base,\n') elif needed_type.type is ArgumentType.MAPPED: mapped_type = needed_type.definition sf.write(f' &sipTypeDef_{module_name}_{mapped_type.iface_file.fq_cpp_name.as_word}.mtd_base,\n') elif needed_type.type is ArgumentType.ENUM: enum = needed_type.definition enum_index = needed_enums.index(enum) sf.write(f' &enumTypes[{enum_index}].etd_base,\n') sf.write('};\n') def _sip_api(sf, spec): """ Generate the code to get the sip API. """ sip_module_name = spec.sip_module module_name = spec.module.py_name if sip_module_name: # Note that we don't use PyCapsule_Import() because it doesn't handle # package.module.attribute. sf.write( f''' /* Get the SIP module's API. */ if ((sip_sipmod = PyImport_ImportModule("{sip_module_name}")) == SIP_NULLPTR) {{ Py_DECREF(sipModule); return SIP_NULLPTR; }} sip_capiobj = PyDict_GetItemString(PyModule_GetDict(sip_sipmod), "_C_API"); Py_DECREF(sip_sipmod); if (sip_capiobj == SIP_NULLPTR || !PyCapsule_CheckExact(sip_capiobj)) {{ PyErr_SetString(PyExc_AttributeError, "{sip_module_name}._C_API is missing or has the wrong type"); Py_DECREF(sipModule); return SIP_NULLPTR; }} ''') if spec.c_bindings: c_api = f'(const sipAPIDef *)PyCapsule_GetPointer(sip_capiobj, "{sip_module_name}._C_API")' else: c_api = f'reinterpret_cast(PyCapsule_GetPointer(sip_capiobj, "{sip_module_name}._C_API"))' sf.write( f''' sipAPI_{module_name} = {c_api}; if (sipAPI_{module_name} == SIP_NULLPTR) {{ Py_DECREF(sipModule); return SIP_NULLPTR; }} ''') else: # If there is no sip module name then we are getting the API from a # non-shared sip module. sf.write( f''' if ((sipAPI_{module_name} = sip_init_library(sipModuleDict)) == SIP_NULLPTR) return SIP_NULLPTR; ''') def _module_init_start(sf, spec, generate_c=True): """ Generate the start of the Python module initialisation function. """ if spec.is_composite or spec.c_bindings: extern_c = '' arg_type = 'void' else: extern_c = 'extern "C" ' arg_type = '' module_name = spec.module.py_name sf.write( f''' /* The Python module initialisation function. */ #if defined(SIP_STATIC_MODULE) {extern_c}PyObject *PyInit_{module_name}({arg_type}) #else PyMODINIT_FUNC PyInit_{module_name}({arg_type}) #endif {{ ''') def _module_definition(sf, module, method_table='SIP_NULLPTR'): """ Generate the module definition structure. """ if module.docstring is None: docstring_ref = 'SIP_NULLPTR' else: docstring_ref = f'doc_mod_{module.py_name}' sf.write( f''' static PyModuleDef sip_module_def = {{ PyModuleDef_HEAD_INIT, "{module.fq_py_name}", {docstring_ref}, -1, {method_table}, SIP_NULLPTR, SIP_NULLPTR, SIP_NULLPTR, SIP_NULLPTR }}; ''') def _subclass_convertors(sf, spec, module): """ Generate all the sub-class convertors for a module and return the number of them. """ nr_subclass_convertors = 0 for klass in spec.classes: if klass.iface_file.module is not module: continue if klass.convert_to_subclass_code is None: continue sf.write( ''' /* Convert to a sub-class if possible. */ ''') klass_name = klass.iface_file.fq_cpp_name.as_word base_cpp = klass.subclass_base.iface_file.fq_cpp_name.as_cpp if not spec.c_bindings: sf.write( f'extern "C" {{static const sipTypeDef *sipSubClass_{klass_name}(void **);}}\n') # Allow the deprecated use of sipClass rather than sipType. if _is_used_in_code(klass.convert_to_subclass_code, 'sipClass'): decl = 'sipWrapperType *sipClass' result = '(sipClass ? sipClass->wt_td : 0)' else: decl = 'const sipTypeDef *sipType' result = 'sipType' sf.write( f'''static const sipTypeDef *sipSubClass_{klass_name}(void **sipCppRet) {{ {base_cpp} *sipCpp = reinterpret_cast<{base_cpp} *>(*sipCppRet); {decl}; ''') sf.write_code(klass.convert_to_subclass_code) sf.write( f''' return {result}; }} ''') nr_subclass_convertors += 1 return nr_subclass_convertors def _encoded_type(module, klass, last=False): """ Return the structure representing an encoded type. """ klass_module = klass.iface_file.module fields = [str(klass.iface_file.type_nr)] if klass_module is module: fields.append('255') else: for module_nr, imported_module in enumerate(module.all_imports): if imported_module is klass_module: fields.append(str(module_nr)) break fields.append(str(int(last))) return '{' + ', '.join(fields) + '}' def _ordinary_function(sf, spec, bindings, member, scope=None): """ Generate an ordinary function. """ member_name = member.py_name.name if scope is None: overloads = spec.module.overloads py_scope = None py_scope_prefix = '' else: overloads = scope.overloads py_scope = _py_scope(scope) py_scope_prefix = '' if py_scope is None else py_scope.iface_file.fq_cpp_name.as_word + '_' sf.write('\n\n') # Generate the docstrings. if _has_member_docstring(bindings, member, overloads): sf.write(f'PyDoc_STRVAR(doc_{py_scope_prefix}{member_name}, "') has_auto_docstring = _member_docstring(sf, spec, bindings, member, overloads) sf.write('");\n\n') else: has_auto_docstring = False if member.no_arg_parser or member.allow_keyword_args: kw_fw_decl = ', PyObject *' kw_decl = ', PyObject *sipKwds' else: kw_fw_decl = kw_decl = '' sip_self_unused = False if py_scope is None: if not spec.c_bindings: sf.write(f'extern "C" {{static PyObject *func_{member_name}(PyObject *, PyObject *{kw_fw_decl});}}\n') sip_self = '' else: sip_self = 'sipSelf' sip_self_unused = True; sf.write(f'static PyObject *func_{member_name}(PyObject *{sip_self}, PyObject *sipArgs{kw_decl})\n') else: if not spec.c_bindings: sf.write(f'extern "C" {{static PyObject *meth_{py_scope_prefix}{member_name}(PyObject *, PyObject *{kw_fw_decl});}}\n') sf.write(f'static PyObject *meth_{py_scope_prefix}{member_name}(PyObject *, PyObject *sipArgs{kw_decl})\n') sf.write('{\n') need_intro = True for overload in overloads: if overload.common is not member: continue if member.no_arg_parser: sf.write_code(overload.method_code) break if need_intro: sf.write(' PyObject *sipParseErr = SIP_NULLPTR;\n') if sip_self_unused: sf.write( ''' (void)sipSelf; ''') need_intro = False _function_body(sf, spec, bindings, scope, overload) if not need_intro: sf.write( f''' /* Raise an exception if the arguments couldn't be parsed. */ sipNoFunction(sipParseErr, {_cached_name_ref(member.py_name)}, ''') if has_auto_docstring: sf.write(f'doc_{py_scope_prefix}{member_name}') else: sf.write('SIP_NULLPTR') sf.write('''); return SIP_NULLPTR; ''') sf.write('}\n') def _enum_member_table(sf, spec, scope=None): """ Generate the table of enum members for a scope. Return the number of them. """ enum_members = [] for enum in spec.enums: if enum.module is not spec.module: continue enum_py_scope = _py_scope(enum.scope) if isinstance(scope, WrappedClass): # The scope is a class. if enum_py_scope is not scope or (enum.is_protected and not scope.has_shadow): continue elif scope is not None: # The scope is a mapped type. if enum.scope != scope: continue elif enum_py_scope is not None or isinstance(enum.scope, MappedType) or enum.fq_cpp_name is None: continue enum_members.extend(enum.members) nr_members = len(enum_members) if nr_members == 0: return 0 enum_members.sort(key=lambda v: v.scope.type_nr) enum_members.sort(key=lambda v: v.py_name.name) if _py_scope(scope) is None: sf.write( ''' /* These are the enum members of all global enums. */ static sipEnumMemberDef enummembers[] = { ''') else: sf.write( f''' static sipEnumMemberDef enummembers_{scope.iface_file.fq_cpp_name.as_word}[] = {{ ''') for enum_member in enum_members: sf.write(f' {{{_cached_name_ref(enum_member.py_name)}, ') sf.write(_enum_member(spec, enum_member)) sf.write(f', {enum_member.scope.type_nr}}},\n') sf.write('};\n') return nr_members def _access_functions(sf, spec, scope=None): """ Generate the access functions for the variables. """ for variable in _variables_in_scope(spec, scope, check_handler=False): if variable.access_code is None: continue cpp_name = variable.fq_cpp_name.as_word sf.write('\n\n/* Access function. */\n') if not spec.c_bindings: sf.write(f'extern "C" {{static void *access_{cpp_name}();}}\n') sf.write(f'static void *access_{cpp_name}()\n{{\n') sf.write_code(variable.access_code) sf.write('}\n') # The types that are implemented as PyObject*. _PY_OBJECT_TYPES = (ArgumentType.PYOBJECT, ArgumentType.PYTUPLE, ArgumentType.PYLIST, ArgumentType.PYDICT, ArgumentType.PYCALLABLE, ArgumentType.PYSLICE, ArgumentType.PYTYPE, ArgumentType.PYBUFFER, ArgumentType.PYENUM) def _py_objects(sf, spec): """ Generate the inline code to add a set of Python objects to a module dictionary. """ # Note that we should add these via a table (like int, etc) but that will # require a major API version change so this will do for now. no_intro = True for variable in spec.variables: if variable.module is not spec.module: continue if variable.type.type not in _PY_OBJECT_TYPES: continue if variable.needs_handler: continue if no_intro: sf.write('\n /* Define the Python objects wrapped as such. */\n') no_intro = False py_name = _cached_name_ref(variable.py_name) cpp_name = variable.fq_cpp_name.as_cpp sf.write(f' PyDict_SetItemString(sipModuleDict, {py_name}, {cpp_name});\n') def _types_inline(sf, spec): """ Generate the inline code to add a set of generated type instances to a dictionary. """ no_intro = True for variable in spec.variables: if variable.module is not spec.module: continue if variable.type.type not in (ArgumentType.CLASS, ArgumentType.MAPPED, ArgumentType.ENUM): continue if variable.needs_handler: continue # Skip classes that don't need inline code. if spec.c_bindings or variable.access_code is not None or len(variable.type.derefs) != 0: continue if no_intro: sf.write( ''' /* * Define the class, mapped type and enum instances that have to be * added inline. */ ''') no_intro = False if _py_scope(variable.scope) is None: dict_name = 'sipModuleDict' else: dict_name = f'(PyObject *)sipTypeAsPyTypeObject({_gto_name(variable.scope)})' py_name = _cached_name_ref(variable.py_name) if variable.type.is_const: type_name = fmt_argument_as_cpp_type(spec, variable.type, plain=True, no_derefs=True) ptr = f'const_cast<{type_name} *>(&{variable.fq_cpp_name.as_cpp})' else: ptr = '&' + variable.fq_cpp_name.as_cpp sf.write(f' sipAddTypeInstance({dict_name}, {py_name}, {ptr}, {_gto_name(variable.type.definition)});\n') def _class_instances(sf, spec, scope=None): """ Generate the code to add a set of class instances to a dictionary. Return True if there was at least one. """ instances = [] for variable in _variables_in_scope(spec, scope): if variable.type.type is not ArgumentType.CLASS and (variable.type.type is not ArgumentType.ENUM or variable.type.definition.fq_cpp_name is None): continue # Skip ordinary C++ class instances which need to be done with inline # code rather than through a static table. This is because C++ does # not guarantee the order in which the table and the instance will be # created. So far this has only been seen to be a problem when # statically linking SIP generated modules on Windows. if not spec.c_bindings and variable.access_code is None and len(variable.type.derefs) == 0: continue ti_name = _cached_name_ref(variable.py_name) ti_ptr = '&' + variable.fq_cpp_name.as_cpp ti_type = '&' + _gto_name(variable.type.definition) ti_flags = '0' if variable.type.type is ArgumentType.CLASS: if variable.access_code is not None: ti_ptr = '(void *)access_' + variable.fq_cpp_name.as_word ti_flags = 'SIP_ACCFUNC|SIP_NOT_IN_MAP' elif len(variable.type.derefs) != 0: # This may be a bit heavy handed. if variable.type.is_const: ti_ptr = '(void *)' + ti_ptr ti_flags = 'SIP_INDIRECT' else: ti_ptr = _const_cast(spec, variable.type, ti_ptr) instances.append((ti_name, ti_ptr, ti_type, ti_flags)) return _write_instances_table(sf, scope, instances, '''/* Define the class and enum instances to be added to this {dict_type} dictionary. */ static sipTypeInstanceDef typeInstances{suffix}[]''') def _void_pointer_instances(sf, spec, scope=None): """ Generate the code to add a set of void pointers to a dictionary. Return True if there was at least one. """ instances = [] for variable in _variables_in_scope(spec, scope): if variable.type.type not in (ArgumentType.VOID, ArgumentType.STRUCT, ArgumentType.UNION): continue vi_name = _cached_name_ref(variable.py_name) vi_val = _const_cast(spec, variable.type, variable.fq_cpp_name.cpp_stripped(STRIP_GLOBAL)) instances.append((vi_name, vi_val)) return _write_instances_table(sf, scope, instances, '''/* Define the void pointers to be added to this {dict_type} dictionary. */ "static sipVoidPtrInstanceDef voidPtrInstances{suffix}[]''') def _char_instances(sf, spec, scope=None): """ Generate the code to add a set of characters to a dictionary. Return True if there was at least one. """ instances = [] for variable in _variables_in_scope(spec, scope): if variable.type.type not in (ArgumentType.ASCII_STRING, ArgumentType.LATIN1_STRING, ArgumentType.UTF8_STRING, ArgumentType.SSTRING, ArgumentType.USTRING, ArgumentType.STRING) or len(variable.type.derefs) != 0: continue ci_name = _cached_name_ref(variable.py_name) ci_val = variable.fq_cpp_name.cpp_stripped(STRIP_GLOBAL) ci_encoding = "'" + _get_encoding(variable.type) + "'" instances.append((ci_name, ci_val, ci_encoding)) return _write_instances_table(sf, scope, instances, '''/* Define the chars to be added to this {dict_type} dictionary. */ static sipCharInstanceDef charInstances{suffix}[]''') def _string_instances(sf, spec, scope=None): """ Generate the code to add a set of strings to a dictionary. Return True if there is at least one. """ instances = [] for variable in _variables_in_scope(spec, scope): if (variable.type.type not in (ArgumentType.ASCII_STRING, ArgumentType.LATIN1_STRING, ArgumentType.UTF8_STRING, ArgumentType.SSTRING, ArgumentType.USTRING, ArgumentType.STRING) or len(variable.type.derefs) == 0) and variable.type.type is not ArgumentType.WSTRING: continue si_name = _cached_name_ref(variable.py_name) si_val = variable.fq_cpp_name.cpp_stripped(STRIP_GLOBAL) # This is the hack for handling wchar_t and wchar_t*. encoding = _get_encoding(variable.type) if encoding == 'w': si_val = '(const char *)&' + si_val elif encoding == 'W': si_val = '(const char *)' + si_val si_encoding = "'" + encoding + "'" instances.append((si_name, si_val, si_encoding)) return _write_instances_table(sf, scope, instances, '''/* Define the strings to be added to this {dict_type} dictionary. */ static sipStringInstanceDef stringInstances{suffix}[]''') def _int_instances(sf, spec, scope=None): """ Generate the code to add a set of ints. Return True if there was at least one. """ instances = [] if spec.abi_version >= (13, 0): # Named enum members are handled as int variables but must be placed at # the start of the table. Note we use the sorted table of needed types # rather than the unsorted table of all enums. for type in spec.module.needed_types: if type.type is not ArgumentType.ENUM: continue enum = type.definition if _py_scope(enum.scope) is not scope or enum.module is not spec.module: continue for enum_member in enum.members: ii_name = _cached_name_ref(enum_member.py_name) ii_val = _enum_member(spec, enum_member) instances.append((ii_name, ii_val)) # Handle int variables. for variable in _variables_in_scope(spec, scope): if variable.type.type not in (ArgumentType.ENUM, ArgumentType.BYTE, ArgumentType.SBYTE, ArgumentType.UBYTE, ArgumentType.USHORT, ArgumentType.SHORT, ArgumentType.CINT, ArgumentType.INT, ArgumentType.BOOL, ArgumentType.CBOOL): continue # Named enums are handled elsewhere. if variable.type.type is ArgumentType.ENUM and variable.type.definition.fq_cpp_name is not None: continue ii_name = _cached_name_ref(variable.py_name) ii_val = variable.fq_cpp_name.cpp_stripped(STRIP_GLOBAL) instances.append((ii_name, ii_val)) # Anonymous enum members are handled as int variables. if spec.abi_version >= (13, 0) or scope is None: for enum in spec.enums: if _py_scope(enum.scope) is not scope or enum.module is not spec.module: continue if enum.fq_cpp_name is not None: continue for enum_member in enum.members: ii_name = _cached_name_ref(enum_member.py_name) ii_val = _enum_member(spec, enum_member) instances.append((ii_name, ii_val)) return _write_instances_table(sf, scope, instances, '''/* Define the enum members and ints to be added to this {dict_type}. */ static sipIntInstanceDef intInstances{suffix}[]''') def _long_instances(sf, spec, scope=None): """ Generate the code to add a set of longs to a dictionary. Return True if there was at least one. """ return _write_int_instances(sf, spec, scope, ArgumentType.LONG, 'long') def _unsigned_long_instances(sf, spec, scope=None): """ Generate the code to add a set of unsigned longs to a dictionary. Return True if there was at least one. """ return _write_int_instances(sf, spec, scope, ArgumentType.ULONG, 'unsigned long') def _long_long_instances(sf, spec, scope=None): """ Generate the code to add a set of long longs to a dictionary. Return True if there was at least one. """ return _write_int_instances(sf, spec, scope, ArgumentType.LONGLONG, 'long long') def _unsigned_long_long_instances(sf, spec, scope=None): """ Generate the code to add a set of unsigned long longs to a dictionary. Return True if there was at least one. """ return _write_int_instances(sf, spec, scope, ArgumentType.ULONGLONG, 'unsigned long long') def _write_int_instances(sf, spec, scope, target_type, type_name): """ Generate the code to add a set of a particular type to a dictionary. Return True if there was at least one. """ instances = [] for variable in _variables_in_scope(spec, scope): variable_type = variable.type.type # We treat unsigned and size_t as unsigned long as we don't (currently # anyway) generate a separate table for them. if variable_type in (ArgumentType.UINT, ArgumentType.SIZE) and target_type is ArgumentType.ULONG: variable_type = ArgumentType.ULONG if variable_type is not target_type: continue ii_name = _cached_name_ref(variable.py_name) ii_val = variable.fq_cpp_name.cpp_stripped(STRIP_GLOBAL) instances.append((ii_name, ii_val)) table_type_name = type_name.title().replace(' ', '') table_name = table_type_name[0].lower() + table_type_name[1:] declaration_template = f'''/* Define the {type_name}s to be added to this {{dict_type}} dictionary. */ static sip{table_type_name}InstanceDef {table_name}Instances{{suffix}}[]''' return _write_instances_table(sf, scope, instances, declaration_template) def _double_instances(sf, spec, scope=None): """ Generate the code to add a set of doubles to a dictionary. Return True if there was at least one. """ instances = [] for variable in _variables_in_scope(spec, scope): if variable.type.type not in (ArgumentType.FLOAT, ArgumentType.CFLOAT, ArgumentType.DOUBLE, ArgumentType.CDOUBLE): continue di_name = _cached_name_ref(variable.py_name) di_val = variable.fq_cpp_name.cpp_stripped(STRIP_GLOBAL) instances.append((di_name, di_val)) return _write_instances_table(sf, scope, instances, '''/* Define the doubles to be added to this {dict_type} dictionary. */ static sipDoubleInstanceDef doubleInstances{suffix}[]''') def _empty_iface_file(spec, iface_file): """ See if an interface file has any content. """ for klass in spec.classes: if klass.iface_file is iface_file and not klass.is_hidden_namespace and not klass.is_protected and not klass.external: return False for mapped_type in spec.mapped_types: if mapped_type.iface_file is iface_file: return False return True def _iface_file_cpp(spec, bindings, project, buildable, py_debug, iface_file, need_postinc, source_suffix, sf): """ Generate the C/C++ code for an interface. """ # Check that there will be something in the file so that we don't get # warning messages from ranlib. if _empty_iface_file(spec, iface_file): return if sf is None: source_name = os.path.join(buildable.build_dir, 'sip' + iface_file.module.py_name) for part in iface_file.fq_cpp_name: source_name += part if iface_file.file_extension is not None: source_suffix = iface_file.file_extension source_name += source_suffix sf = CompilationUnit(source_name, "Interface wrapper code.", iface_file.module, project, buildable) need_postinc = True sf.write('\n') sf.write_code(iface_file.type_header_code) _used_includes(sf, iface_file.used) if need_postinc: sf.write_code(iface_file.module.unit_postinclude_code) for klass in spec.classes: # Protected classes must be generated in the interface file of the # enclosing scope. if klass.is_protected: continue if klass.external: continue if klass.iface_file is iface_file: _class_cpp(sf, spec, bindings, klass, py_debug) # Generate any enclosed protected classes. for proto_klass in spec.classes: if proto_klass.is_protected and proto_klass.scope is klass: _class_cpp(sf, spec, bindings, proto_klass, py_debug) for mapped_type in spec.mapped_types: if mapped_type.iface_file is iface_file: _mapped_type_cpp(sf, spec, bindings, mapped_type) def _mapped_type_cpp(sf, spec, bindings, mapped_type): """ Generate the C++ code for a mapped type version. """ mapped_type_name = mapped_type.iface_file.fq_cpp_name.as_word mapped_type_type = fmt_argument_as_cpp_type(spec, mapped_type.type, plain=True, no_derefs=True) sf.write_code(mapped_type.type_code) if not mapped_type.no_release: # Generate the assignment helper. Note that the source pointer is not # const. This is to allow the source instance to be modified as a # consequence of the assignment, eg. if it is implementing some sort of # reference counting scheme. if not mapped_type.no_assignment_operator: sf.write('\n\n') if not spec.c_bindings: sf.write(f'extern "C" {{static void assign_{mapped_type_name}(void *, Py_ssize_t, void *);}}\n') sf.write(f'static void assign_{mapped_type_name}(void *sipDst, Py_ssize_t sipDstIdx, void *sipSrc)\n{{\n') if spec.c_bindings: sf.write(f' (({mapped_type_type} *)sipDst)[sipDstIdx] = *(({mapped_type_type} *)sipSrc);\n') else: sf.write(f' reinterpret_cast<{mapped_type_type} *>(sipDst)[sipDstIdx] = *reinterpret_cast<{mapped_type_type} *>(sipSrc);\n') sf.write('}\n') # Generate the array allocation helper. if not mapped_type.no_default_ctor: sf.write('\n\n') if not spec.c_bindings: sf.write(f'extern "C" {{static void *array_{mapped_type_name}(Py_ssize_t);}}\n') sf.write(f'static void *array_{mapped_type_name}(Py_ssize_t sipNrElem)\n{{\n') if spec.c_bindings: sf.write(f' return sipMalloc(sizeof ({mapped_type_type}) * sipNrElem);\n') else: sf.write(f' return new {mapped_type_type}[sipNrElem];\n') sf.write('}\n') # Generate the copy helper. if not mapped_type.no_copy_ctor: sf.write('\n\n') if not spec.c_bindings: sf.write(f'extern "C" {{static void *copy_{mapped_type_name}(const void *, Py_ssize_t);}}\n') sf.write(f'static void *copy_{mapped_type_name}(const void *sipSrc, Py_ssize_t sipSrcIdx)\n{{\n') if spec.c_bindings: sf.write( f''' {mapped_type_type} *sipPtr = sipMalloc(sizeof ({mapped_type_type})); *sipPtr = ((const {mapped_type_type} *)sipSrc)[sipSrcIdx]; return sipPtr; ''') else: sf.write(f' return new {mapped_type_type}(reinterpret_cast(sipSrc)[sipSrcIdx]);\n') sf.write('}\n') sf.write('\n\n/* Call the mapped type\'s destructor. */\n') need_state = _is_used_in_code(mapped_type.release_code, 'sipState') if not spec.c_bindings: arg_3 = ', void *' if spec.abi_version >= (13, 0) else '' sf.write(f'extern "C" {{static void release_{mapped_type_name}(void *, int{arg_3});}}\n') arg_2 = ' sipState' if spec.c_bindings or need_state else '' sf.write(f'static void release_{mapped_type_name}(void *sipCppV, int{arg_2}') if spec.abi_version >= (13, 0): user_state = _use_in_code(mapped_type.release_code, 'sipUserState') sf.write(', void *' + user_state) sf.write(f')\n{{\n {_mapped_type_from_void(spec, mapped_type_type)};\n') if bindings.release_gil: sf.write(' Py_BEGIN_ALLOW_THREADS\n') if mapped_type.release_code is not None: sf.write_code(mapped_type.release_code) elif spec.c_bindings: sf.write(' sipFree(sipCpp);\n') else: sf.write(' delete sipCpp;\n') if bindings.release_gil: sf.write(' Py_END_ALLOW_THREADS\n') sf.write('}\n\n') _convert_to_definitions(sf, spec, mapped_type) # Generate the from type convertor. if mapped_type.convert_from_type_code is not None: sf.write('\n\n') if not spec.c_bindings: sf.write(f'extern "C" {{static PyObject *convertFrom_{mapped_type_name}(void *, PyObject *);}}\n') xfer = _use_in_code(mapped_type.convert_from_type_code, 'sipTransferObj', spec=spec) sf.write( f'''static PyObject *convertFrom_{mapped_type_name}(void *sipCppV, PyObject *{xfer}) {{ {_mapped_type_from_void(spec, mapped_type_type)}; ''') sf.write_code(mapped_type.convert_from_type_code) sf.write('}\n') # Generate the static methods. for member in mapped_type.members: _ordinary_function(sf, spec, bindings, member, scope=mapped_type) cod_nrmethods = _mapped_type_method_table(sf, bindings, spec, mapped_type) id_int = 'SIP_NULLPTR' if spec.abi_version >= (13, 0): if _int_instances(sf, spec, scope=mapped_type): id_int = 'intInstances_' + mapped_type_name needs_namespace = False else: cod_nrenummembers = _enum_member_table(sf, spec, scope=mapped_type) has_ints = False needs_namespace = (cod_nrenummembers > 0) if cod_nrmethods > 0: needs_namespace = True if _pyqt6(spec) and mapped_type.pyqt_flags != 0: sf.write(f'\n\nstatic pyqt6MappedTypePluginDef plugin_{mapped_type_name} = {{{mapped_type.pyqt_flags}}};\n') td_plugin_data = '&plugin_' + mapped_type_name else: td_plugin_data = 'SIP_NULLPTR' sf.write( f''' sipMappedTypeDef sipTypeDef_{mapped_type.iface_file.module.py_name}_{mapped_type_name} = {{ {{ ''') if spec.abi_version < (13, 0): sf.write( ''' -1, SIP_NULLPTR, ''') flags = [] if mapped_type.handles_none: flags.append('SIP_TYPE_ALLOW_NONE') if mapped_type.needs_user_state: flags.append('SIP_TYPE_USER_STATE') flags.append('SIP_TYPE_MAPPED') td_flags = '|'.join(flags) td_cname = _cached_name_ref(mapped_type.cpp_name, as_nr=True) cod_name = _cached_name_ref(mapped_type.py_name, as_nr=True) if needs_namespace else '-1' cod_methods = 'SIP_NULLPTR' if cod_nrmethods == 0 else 'methods_' + mapped_type_name sf.write( f''' SIP_NULLPTR, {td_flags}, {td_cname}, SIP_NULLPTR, {td_plugin_data}, }}, {{ {cod_name}, {{0, 0, 1}}, {cod_nrmethods}, {cod_methods}, ''') if spec.abi_version < (13, 0): cod_enummembers = 'SIP_NULLPTR' if cod_nrenummembers == 0 else 'enummembers_' + mapped_type_name sf.write( f''' {cod_nrenummembers}, {cod_enummembers}, ''') mtd_assign = 'SIP_NULLPTR' if mapped_type.no_assignment_operator else 'assign_' + mapped_type_name mtd_array = 'SIP_NULLPTR' if mapped_type.no_default_ctor else 'array_' + mapped_type_name mtd_copy = 'SIP_NULLPTR' if mapped_type.no_copy_ctor else 'copy_' + mapped_type_name mtd_release = 'SIP_NULLPTR' if mapped_type.no_release else 'release_' + mapped_type_name mtd_cto = 'SIP_NULLPTR' if mapped_type.convert_to_type_code is None else 'convertTo_' + mapped_type_name mtd_cfrom = 'SIP_NULLPTR' if mapped_type.convert_from_type_code is None else 'convertFrom_' + mapped_type_name sf.write( f''' 0, SIP_NULLPTR, {{SIP_NULLPTR, SIP_NULLPTR, SIP_NULLPTR, SIP_NULLPTR, {id_int}, SIP_NULLPTR, SIP_NULLPTR, SIP_NULLPTR, SIP_NULLPTR, SIP_NULLPTR}} }}, {mtd_assign}, {mtd_array}, {mtd_copy}, {mtd_release}, {mtd_cto}, {mtd_cfrom} }}; ''') def _class_cpp(sf, spec, bindings, klass, py_debug): """ Generate the C++ code for a class. """ sf.write_code(klass.type_code) _class_functions(sf, spec, bindings, klass, py_debug) _access_functions(sf, spec, scope=klass) if klass.iface_file.type is not IfaceFileType.NAMESPACE: _convert_to_definitions(sf, spec, klass) # Generate the optional from type convertor. if klass.convert_from_type_code is not None: sf.write('\n\n') name = klass.iface_file.fq_cpp_name.as_word xfer = _use_in_code(klass.convert_from_type_code, 'sipTransferObj', spec=spec) if not spec.c_bindings: sf.write(f'extern "C" {{static PyObject *convertFrom_{name}(void *, PyObject *);}}\n') sf.write( f'''static PyObject *convertFrom_{name}(void *sipCppV, PyObject *{xfer}) {{ {_class_from_void(spec, klass)}; ''') sf.write_code(klass.convert_from_type_code) sf.write('}\n') _type_definition(sf, spec, bindings, klass, py_debug) def _get_function_table(members): """ Return a sorted list of relevant functions for a namespace. """ return sorted(members, key=lambda m: m.py_name.name) def _get_method_table(klass): """ Return a sorted list of relevant methods (either lazy or non-lazy) for a class. """ # Only provide an entry point if there is at least one overload that is # defined in this class and is a non-abstract function or slot. We allow # private (even though we don't actually generate code) because we need to # intercept the name before it reaches a more public version further up the # class hierarchy. We add the ctor and any variable handlers as special # entries. members = [] for visible_member in klass.visible_members: if visible_member.member.py_slot is not None: continue need_member = False for overload in visible_member.scope.overloads: # Skip protected methods if we don't have the means to handle them. if overload.access_specifier is AccessSpecifier.PROTECTED and not klass.has_shadow: continue if not _skip_overload(overload, visible_member.member, klass, visible_member.scope): need_member = True if need_member: members.append(visible_member.member) return _get_function_table(members) def _mapped_type_method_table(sf, spec, bindings, mapped_type): """ Generate the sorted table of static methods for a mapped type and return the number of entries. """ members = _get_function_table(mapped_type.members) return _py_method_table(sf, spec, bindings, members, mapped_type) def _class_method_table(sf, spec, bindings, klass): """ Generate the sorted table of methods for a class and return the number of entries. """ if klass.iface_file.type is IfaceFileType.NAMESPACE: members = _get_function_table(klass.members) else: members = _get_method_table(klass) return _py_method_table(sf, spec, bindings, members, klass) def _py_method_table(sf, spec, bindings, members, scope): """ Generate a Python method table for a class or mapped type and return the number of entries. """ scope_name = scope.iface_file.fq_cpp_name.as_word no_intro = True for member_nr, member in enumerate(members): # Save the index in the table. member.member_nr = member_nr py_name = member.py_name cached_py_name = _cached_name_ref(py_name) comma = '' if member is members[-1] else ',' if member.no_arg_parser or member.allow_keyword_args: cast = 'SIP_MLMETH_CAST(' cast_suffix = ')' flags = '|METH_KEYWORDS' else: cast = '' cast_suffix = '' flags = '' if _has_member_docstring(bindings, member, scope.overloads): docstring = f'doc_{scope_name}_{py_name.name}' else: docstring = 'SIP_NULLPTR' if no_intro: sf.write( f''' static PyMethodDef methods_{scope_name}[] = {{ ''') no_intro = False sf.write(f' {{{cached_py_name}, {cast}meth_{scope_name}_{py_name.name}{cast_suffix}, METH_VARARGS{flags}, {docstring}}}{comma}\n') if not no_intro: sf.write('};\n') return len(members) def _convert_to_definitions(sf, spec, scope): """ Generate the "to type" convertor definitions. """ convert_to_type_code = scope.convert_to_type_code if convert_to_type_code is None: return scope_type = Argument( ArgumentType.CLASS if isinstance(scope, WrappedClass) else ArgumentType.MAPPED, definition=scope) # Sometimes type convertors are just stubs that set the error flag, so # check if we actually need everything so that we can avoid compiler # warnings. sip_py = _use_in_code(convert_to_type_code, 'sipPy', spec=spec) sip_cpp_ptr = _use_in_code(convert_to_type_code, 'sipCppPtr', spec=spec) sip_is_err = _use_in_code(convert_to_type_code, 'sipIsErr', spec=spec) xfer = _use_in_code(convert_to_type_code, 'sipTransferObj', spec=spec) if spec.abi_version >= (13, 0): need_us_arg = True need_us_val = (spec.c_bindings or _type_needs_user_state(scope_type)) else: need_us_arg = False need_us_val = False scope_name = scope.iface_file.fq_cpp_name.as_word sf.write('\n\n') if not spec.c_bindings: sf.write(f'extern "C" {{static int convertTo_{scope_name}(PyObject *, void **, int *, PyObject *') if need_us_arg: sf.write(', void **') sf.write(');}\n') sip_cpp_ptr_v = sip_cpp_ptr if sip_cpp_ptr_v != '': sip_cpp_ptr_v += 'V' sf.write(f'static int convertTo_{scope_name}(PyObject *{sip_py}, void **{sip_cpp_ptr_v}, int *{sip_is_err}, PyObject *{xfer}') if need_us_arg: sf.write(', void **') if need_us_val: sf.write('sipUserStatePtr') sf.write(')\n{\n') if sip_cpp_ptr != '': type_s = fmt_argument_as_cpp_type(spec, scope_type, plain=True, no_derefs=True) if spec.c_bindings: cast_value = f'({type_s} **)sipCppPtrV' else: cast_value = f'reinterpret_cast<{type_s} **>(sipCppPtrV)' sf.write(f' {type_s} **sipCppPtr = {cast_value};\n\n') sf.write_code(convert_to_type_code) sf.write('}\n') def _variable_getter(sf, spec, variable): """ Generate a variable getter. """ variable_type = variable.type.type first_arg = 'sipSelf' if spec.c_bindings or not variable.is_static else '' last_arg = _use_in_code(variable.get_code, 'sipPyType', spec=spec) needs_new = (variable_type in (ArgumentType.CLASS, ArgumentType.MAPPED) and len(variable.type.derefs) == 0 and variable.type.is_const) # If the variable is itself a non-const instance of a wrapped class then # two things must happen. Firstly, the getter must return the same Python # object each time - it must not re-wrap the the instance. This is because # the Python object can contain important state information that must not # be lost (specifically references to other Python objects that it owns). # Therefore the Python object wrapping the containing class must cache a # reference to the Python object wrapping the variable. Secondly, the # Python object wrapping the containing class must not be garbage collected # before the Python object wrapping the variable is (because the latter # references memory, ie. the variable itself, that is managed by the # former). Therefore the Python object wrapping the variable must keep a # reference to the Python object wrapping the containing class (but only if # the latter is non-static). var_key = self_key = 0 if variable_type is ArgumentType.CLASS and len(variable.type.derefs) == 0 and not variable.type.is_const: var_key = variable.type.definition.iface_file.module.next_key variable.type.definition.iface_file.module.next_key -= 1 if not variable.is_static: self_key = variable.module.next_key variable.module.next_key -= 1 second_arg = 'sipPySelf' if spec.c_bindings or var_key < 0 else '' variable_as_word = variable.fq_cpp_name.as_word sf.write('\n\n') if not spec.c_bindings: sf.write(f'extern "C" {{static PyObject *varget_{variable_as_word}(void *, PyObject *, PyObject *);}}\n') sf.write( f'''static PyObject *varget_{variable_as_word}(void *{first_arg}, PyObject *{second_arg}, PyObject *{last_arg}) {{ ''') if variable.get_code is not None: sip_py_decl = 'PyObject *sipPy' elif var_key < 0: if variable.is_static: sip_py_decl = 'static PyObject *sipPy = SIP_NULLPTR' else: sip_py_decl = 'PyObject *sipPy' else: sip_py_decl = None if sip_py_decl is not None: sf.write(' ' + sip_py_decl + ';\n') if variable.get_code is None: value_decl = _get_named_value_decl(spec, variable.scope, variable.type, 'sipVal') sf.write(f' {value_decl};\n') if not variable.is_static: scope_s = _scoped_class_name(spec, variable.scope) if spec.c_bindings: sip_self = f'({scope_s} *)sipSelf' else: sip_self = f'reinterpret_cast<{scope_s} *>(sipSelf)' sf.write(f' {scope_s} *sipCpp = {sip_self};\n') sf.write('\n') # Handle any handwritten getter. if variable.get_code is not None: sf.write_code(variable.get_code) sf.write( ''' return sipPy; } ''') return # Get any previously wrapped cached object. if var_key < 0: if variable.is_static: sf.write( ''' if (sipPy) { Py_INCREF(sipPy); return sipPy; } ''') else: sf.write( f''' sipPy = sipGetReference(sipPySelf, {self_key}); if (sipPy) return sipPy; ''') variable_type_s = fmt_argument_as_cpp_type(spec, variable.type, plain=True, no_derefs=True) if needs_new: if spec.c_bindings: sf.write(' *sipVal = ') else: sf.write(f' sipVal = new {variable_type_s}(') else: sf.write(' sipVal = ') if variable_type in (ArgumentType.CLASS, ArgumentType.MAPPED) and len(variable.type.derefs) == 0: sf.write('&') sf.write(_variable_member(variable)) if needs_new and not spec.c_bindings: sf.write(')') sf.write(';\n\n') if variable_type in (ArgumentType.CLASS, ArgumentType.MAPPED): prefix_s = 'sipPy =' if var_key < 0 else 'return' new_s = 'New' if needs_new else '' sip_val_s = _const_cast(spec, variable.type, 'sipVal') sf.write(f' {prefix_s} sipConvertFrom{new_s}Type({sip_val_s}, {_gto_name(variable.type.definition)}, SIP_NULLPTR);\n') if var_key < 0: if variable.is_static: ref_code = 'Py_INCREF(sipPy)' else: ref_code = f'sipKeepReference(sipPySelf, {self_key}, sipPy)' sf.write( f''' if (sipPy) {{ sipKeepReference(sipPy, {var_key}, sipPySelf); {ref_code}; }} return sipPy; ''') elif variable_type in (ArgumentType.BOOL, ArgumentType.CBOOL): sf.write(' return PyBool_FromLong(sipVal);\n') elif variable_type is ArgumentType.ASCII_STRING: if len(variable.type.derefs) == 0: sf.write(' return PyUnicode_DecodeASCII(&sipVal, 1, SIP_NULLPTR);\n') else: sf.write( ''' if (sipVal == SIP_NULLPTR) { Py_INCREF(Py_None); return Py_None; } return PyUnicode_DecodeASCII(sipVal, strlen(sipVal), SIP_NULLPTR); ''') elif variable_type is ArgumentType.LATIN1_STRING: if len(variable.type.derefs) == 0: sf.write(' return PyUnicode_DecodeLatin1(&sipVal, 1, SIP_NULLPTR);\n') else: sf.write( ''' if (sipVal == SIP_NULLPTR) { Py_INCREF(Py_None); return Py_None; } return PyUnicode_DecodeLatin1(sipVal, strlen(sipVal), SIP_NULLPTR); ''') elif variable_type is ArgumentType.UTF8_STRING: if len(variable.type.derefs) == 0: sf.write(' return PyUnicode_FromStringAndSize(&sipVal, 1);\n') else: sf.write( ''' if (sipVal == SIP_NULLPTR) { Py_INCREF(Py_None); return Py_None; } return PyUnicode_FromString(sipVal); ''') elif variable_type in (ArgumentType.SSTRING, ArgumentType.USTRING, ArgumentType.STRING): cast_s = '' if variable_type is ArgumentType.STRING else '(char *)' if len(variable.type.derefs) == 0: sf.write(f' return PyBytes_FromStringAndSize({cast_s}&sipVal, 1);\n') else: sf.write( f''' if (sipVal == SIP_NULLPTR) {{ Py_INCREF(Py_None); return Py_None; }} return PyBytes_FromString({cast_s}sipVal); ''') elif variable_type is ArgumentType.WSTRING: if len(variable.type.derefs) == 0: sf.write(' return PyUnicode_FromWideChar(&sipVal, 1);\n') else: sf.write( ''' if (sipVal == SIP_NULLPTR) { Py_INCREF(Py_None); return Py_None; } return PyUnicode_FromWideChar(sipVal, (Py_ssize_t)wcslen(sipVal)); ''') elif variable_type in (ArgumentType.FLOAT, ArgumentType.CFLOAT): sf.write(' return PyFloat_FromDouble((double)sipVal);\n') elif variable_type in (ArgumentType.DOUBLE, ArgumentType.CDOUBLE): sf.write(' return PyFloat_FromDouble(sipVal);\n') elif variable_type is ArgumentType.ENUM: if variable.type.definition.fq_cpp_name is None: sf.write(' return PyLong_FromLong(sipVal);\n') else: sip_val_s = 'sipVal' if spec.c_bindings else 'static_cast(sipVal)' sf.write(f' return sipConvertFromEnum({sip_val_s}, {_gto_name(variable.type.definition)});\n') elif variable_type in (ArgumentType.BYTE, ArgumentType.SBYTE, ArgumentType.SHORT, ArgumentType.INT, ArgumentType.CINT): sf.write(' return PyLong_FromLong(sipVal);\n') elif variable_type is ArgumentType.LONG: sf.write(' return PyLong_FromLong(sipVal);\n') elif variable_type in (ArgumentType.UBYTE, ArgumentType.USHORT): sf.write(' return PyLong_FromUnsignedLong(sipVal);\n') elif variable_type in (ArgumentType.UINT, ArgumentType.ULONG, ArgumentType.SIZE): sf.write(' return PyLong_FromUnsignedLong(sipVal);\n') elif variable_type is ArgumentType.LONGLONG: sf.write(' return PyLong_FromLongLong(sipVal);\n') elif variable_type is ArgumentType.ULONGLONG: sf.write(' return PyLong_FromUnsignedLongLong(sipVal);\n') elif variable_type in (ArgumentType.STRUCT, ArgumentType.UNION, ArgumentType.VOID): const_s = 'Const' if variable.type.is_const else '' cast_s = _get_void_ptr_cast(variable.type) sf.write(f' return sipConvertFrom{const_s}VoidPtr({cast_s}sipVal);\n') elif variable_type is ArgumentType.CAPSULE: cast_s = _get_void_ptr_cast(variable.type) sf.write(f' return PyCapsule_New({cast_s}sipVal, "{variable.type.definition.as_cpp}", SIP_NULLPTR);\n') elif variable_type in (ArgumentType.PYOBJECT, ArgumentType.PYTUPLE, ArgumentType.PYLIST, ArgumentType.PYDICT, ArgumentType.PYCALLABLE, ArgumentType.PYSLICE, ArgumentType.PYTYPE, ArgumentType.PYBUFFER, ArgumentType.PYENUM): sf.write( ''' Py_XINCREF(sipVal); return sipVal; ''') sf.write('}\n') def _variable_setter(sf, spec, variable): """ Generate a variable setter. """ variable_type = variable.type.type # We need to keep a reference to the original Python object if it is # providing the memory that the C/C++ variable is pointing to. keep = _keep_py_reference(variable.type) need_sip_cpp = (spec.c_bindings or variable.set_code is None or _is_used_in_code(variable.set_code, 'sipCpp')) first_arg = 'sipSelf' if spec.c_bindings or not variable.is_static else '' if not need_sip_cpp: first_arg = '' last_arg = 'sipPySelf' if spec.c_bindings or (not variable.is_static and keep) else '' sip_py = 'sipPy' if spec.c_bindings or variable.set_code is None or _is_used_in_code(variable.set_code, 'sipPy') else '' variable_as_word = variable.fq_cpp_name.as_word sf.write('\n\n') if not spec.c_bindings: sf.write(f'extern "C" {{static int varset_{variable_as_word}(void *, PyObject *, PyObject *);}}\n') sf.write( f'''static int varset_{variable_as_word}(void *{first_arg}, PyObject *{sip_py}, PyObject *{last_arg}) {{ ''') if variable.set_code is None: if variable_type is ArgumentType.BOOL: value_decl = 'int sipVal' else: value_decl = _get_named_value_decl(spec, variable.scope, variable.type, 'sipVal') sf.write(f' {value_decl};\n') if not variable.is_static and need_sip_cpp: scope_s = _scoped_class_name(spec, variable.scope) if spec.c_bindings: statement = f'({scope_s} *)sipSelf' else: statement = f'reinterpret_cast<{scope_s} *>(sipSelf)' sf.write(f' {scope_s} *sipCpp = {statement};\n\n') # Handle any handwritten setter. if variable.set_code is not None: sf.write(' int sipErr = 0;\n\n') sf.write_code(variable.set_code) sf.write( ''' return (sipErr ? -1 : 0); } ''') return has_state = False if variable_type in (ArgumentType.CLASS, ArgumentType.MAPPED): sf.write(' int sipIsErr = 0;\n') if len(variable.type.derefs) == 0: convert_to_type_code = variable.type.definition.convert_to_type_code if variable_type is ArgumentType.MAPPED and variable.type.definition.no_release: convert_to_type_code = None if convert_to_type_code is not None: has_state = True sf.write(' int sipValState;\n') if _type_needs_user_state(variable.type): sf.write(' void *sipValUserState;\n') sf.write(f' sipVal = {_variable_to_cpp(spec, variable, has_state)};\n') deref = '' if variable_type in (ArgumentType.CLASS, ArgumentType.MAPPED): if len(variable.type.derefs) == 0: deref = '*' error_test = 'sipIsErr' elif variable_type is ArgumentType.BOOL: error_test = 'sipVal < 0' else: error_test = 'PyErr_Occurred() != SIP_NULLPTR' sf.write( f''' if ({error_test}) return -1; ''') member = _variable_member(variable) if variable_type in (ArgumentType.PYOBJECT, ArgumentType.PYTUPLE, ArgumentType.PYLIST, ArgumentType.PYDICT, ArgumentType.PYCALLABLE, ArgumentType.PYSLICE, ArgumentType.PYTYPE, ArgumentType.PYBUFFER, ArgumentType.PYENUM): sf.write( f''' Py_XDECREF({member}); Py_INCREF(sipVal); ''') value = deref + 'sipVal' if variable_type is ArgumentType.BOOL: if spec.c_bindings: value = '(bool)' + value else: value = f'static_cast({value})' sf.write(f' {member} = {value};\n') # Note that wchar_t * leaks here. if has_state: suffix = _user_state_suffix(spec, variable.type) sf.write( f''' sipReleaseType{suffix}(sipVal, {_gto_name(variable.type.definition)}, sipValState''') if _type_needs_user_state(variable.type): sf.write(', sipValUserState') sf.write(');\n') # Generate the code to keep the object alive while we use its data. if keep: if variable.is_static: sf.write( ''' static PyObject *sipKeep = SIP_NULLPTR; Py_XDECREF(sipKeep); sipKeep = sipPy; Py_INCREF(sipKeep); ''') else: key = variable.module.next_key variable.module.next_key -= 1 sf.write( f''' sipKeepReference(sipPySelf, {key}, sipPy); ''') sf.write( ''' return 0; } ''') def _variable_member(variable): """ Return the member variable of a class. """ if variable.is_static: scope = variable.scope.iface_file.fq_cpp_name.as_cpp + '::' else: scope = 'sipCpp->' return scope + variable.fq_cpp_name.base_name def _variable_to_cpp(spec, variable, has_state): """ Return the statement to convert a Python variable to C/C++. """ type_s = fmt_argument_as_cpp_type(spec, variable.type, plain=True, no_derefs=True) variable_type = variable.type.type if variable_type in (ArgumentType.CLASS, ArgumentType.MAPPED): if spec.c_bindings: statement = f'({type_s} *)' cast_tail = '' else: statement = f'reinterpret_cast<{type_s} *>(' cast_tail = ')' # Note that we don't support /Transfer/ but could do. We could also # support /Constrained/ (so long as we also supported it for all # types). suffix = _user_state_suffix(spec, variable.type) flags = '0' if len(variable.type.derefs) != 0 else 'SIP_NOT_NONE' state_ptr = '&sipValState' if has_state else 'SIP_NULLPTR' statement += f'sipForceConvertToType{suffix}(sipPy, {_gto_name(variable.type.definition)}, SIP_NULLPTR, {flags}, {state_ptr}' if _type_needs_user_state(variable.type): statement += ', &sipValUserState' statement += ', &sipIsErr)' + cast_tail elif variable_type is ArgumentType.ENUM: statement = f'({type_s})sipConvertToEnum(sipPy, {_gto_name(variable.type.definition)})' elif variable_type is ArgumentType.SSTRING: if len(variable.type.derefs) == 0: statement = '(signed char)sipBytes_AsChar(sipPy)' elif variable.type.is_const: statement = '(const signed char *)sipBytes_AsString(sipPy)' else: statement = '(signed char *)sipBytes_AsString(sipPy)' elif variable_type is ArgumentType.USTRING: if len(variable.type.derefs) == 0: statement = '(unsigned char)sipBytes_AsChar(sipPy)' elif variable.type.is_const: statement = '(const unsigned char *)sipBytes_AsString(sipPy)' else: statement = '(unsigned char *)sipBytes_AsString(sipPy)' elif variable_type is ArgumentType.ASCII_STRING: if len(variable.type.derefs) == 0: statement = 'sipString_AsASCIIChar(sipPy)' elif variable.type.is_const: statement = 'sipString_AsASCIIString(&sipPy)' else: statement = '(char *)sipString_AsASCIIString(&sipPy)' elif variable_type is ArgumentType.LATIN1_STRING: if len(variable.type.derefs) == 0: statement = 'sipString_AsLatin1Char(sipPy)' elif variable.type.is_const: statement = 'sipString_AsLatin1String(&sipPy)' else: statement = '(char *)sipString_AsLatin1String(&sipPy)' elif variable_type is ArgumentType.UTF8_STRING: if len(variable.type.derefs) == 0: statement = 'sipString_AsUTF8Char(sipPy)' elif variable.type.is_const: statement = 'sipString_AsUTF8String(&sipPy)' else: statement = '(char *)sipString_AsUTF8String(&sipPy)' elif variable_type is ArgumentType.STRING: if len(variable.type.derefs) == 0: statement = 'sipBytes_AsChar(sipPy)' elif variable.type.is_const: statement = 'sipBytes_AsString(sipPy)' else: statement = '(char *)sipBytes_AsString(sipPy)' elif variable_type is ArgumentType.WSTRING: if len(variable.type.derefs) == 0: statement = 'sipUnicode_AsWChar(sipPy)' else: statement = 'sipUnicode_AsWString(sipPy)' elif variable_type in (ArgumentType.FLOAT, ArgumentType.CFLOAT): statement = '(float)PyFloat_AsDouble(sipPy)' elif variable_type in (ArgumentType.DOUBLE, ArgumentType.CDOUBLE): statement = 'PyFloat_AsDouble(sipPy)' elif variable_type in (ArgumentType.BOOL, ArgumentType.CBOOL): statement = 'sipConvertToBool(sipPy)' elif variable_type is ArgumentType.BYTE: statement = 'sipLong_AsChar(sipPy)' elif variable_type is ArgumentType.SBYTE: statement = 'sipLong_AsSignedChar(sipPy)' elif variable_type is ArgumentType.UBYTE: statement = 'sipLong_AsUnsignedChar(sipPy)' elif variable_type is ArgumentType.USHORT: statement = 'sipLong_AsUnsignedShort(sipPy)' elif variable_type is ArgumentType.SHORT: statement = 'sipLong_AsShort(sipPy)' elif variable_type is ArgumentType.UINT: statement = 'sipLong_AsUnsignedInt(sipPy)' elif variable_type is ArgumentType.SIZE: statement = 'sipLong_AsSizeT(sipPy)' elif variable_type in (ArgumentType.INT, ArgumentType.CINT): statement = 'sipLong_AsInt(sipPy)' elif variable_type is ArgumentType.ULONG: statement = 'sipLong_AsUnsignedLong(sipPy)' elif variable_type is ArgumentType.LONG: statement = 'sipLong_AsLong(sipPy)' elif variable_type is ArgumentType.ULONGLONG: statement = 'sipLong_AsUnsignedLongLong(sipPy)' elif variable_type is ArgumentType.LONGLONG: statement = 'sipLong_AsLongLong(sipPy)' elif variable_type in (ArgumentType.STRUCT, ArgumentType.UNION): statement = f'({type_s} *)sipConvertToVoidPtr(sipPy)' elif variable_type is ArgumentType.VOID: statement = 'sipConvertToVoidPtr(sipPy)' elif variable_type is ArgumentType.CAPSULE: statement = f'PyCapsule_GetPointer(sipPy, "{variable.type.definition.as_cpp}")' else: # These are just the PyObject types. statement = 'sipPy' return statement def _py_slot(sf, spec, bindings, member, scope=None): """ Generate a Python slot handler for either a class, an enum or an extender. """ if scope is None: prefix = '' py_name = None fq_cpp_name = None overloads = spec.module.overloads elif isinstance(scope, WrappedEnum): prefix = 'Type' py_name = scope.py_name fq_cpp_name = scope.fq_cpp_name overloads = scope.overloads else: prefix = 'Type' py_name = scope.py_name fq_cpp_name = scope.iface_file.fq_cpp_name overloads = scope.overloads if is_void_return_slot(member.py_slot) or is_int_return_slot(member.py_slot): ret_type = 'int ' ret_value = '-1' elif is_ssize_return_slot(member.py_slot): ret_type = 'Py_ssize_t ' ret_value = '0' elif is_hash_return_slot(member.py_slot): if spec.abi_version >= (13, 0): ret_type = 'Py_hash_t ' ret_value = '0' else: ret_type = 'long ' ret_value = '0L' else: ret_type = 'PyObject *' ret_value = 'SIP_NULLPTR' has_args = True if is_int_arg_slot(member.py_slot): has_args = False arg_str = 'PyObject *sipSelf, int a0' decl_arg_str = 'PyObject *, int' elif member.py_slot is PySlot.CALL: if spec.c_bindings or member.allow_keyword_args or member.no_arg_parser: arg_str = 'PyObject *sipSelf, PyObject *sipArgs, PyObject *sipKwds' else: arg_str = 'PyObject *sipSelf, PyObject *sipArgs, PyObject *' decl_arg_str = 'PyObject *, PyObject *, PyObject *' elif is_multi_arg_slot(member.py_slot): arg_str = 'PyObject *sipSelf, PyObject *sipArgs' decl_arg_str = 'PyObject *, PyObject *' elif is_zero_arg_slot(member.py_slot): has_args = False arg_str = 'PyObject *sipSelf' decl_arg_str = 'PyObject *' elif is_number_slot(member.py_slot): arg_str = 'PyObject *sipArg0, PyObject *sipArg1' decl_arg_str = 'PyObject *, PyObject *' elif member.py_slot is PySlot.SETATTR: arg_str = 'PyObject *sipSelf, PyObject *sipName, PyObject *sipValue' decl_arg_str = 'PyObject *, PyObject *, PyObject *' else: arg_str = 'PyObject *sipSelf, PyObject *sipArg' decl_arg_str = 'PyObject *, PyObject *' sf.write('\n\n') slot_decl = f'static {ret_type}slot_' if fq_cpp_name is not None: slot_decl += fq_cpp_name.as_word + '_' if not spec.c_bindings: sf.write(f'extern "C" {{{slot_decl}{member.py_name.name}({decl_arg_str});}}\n') sf.write(f'{slot_decl}{member.py_name.name}({arg_str})\n{{\n') if member.py_slot is PySlot.CALL and member.no_arg_parser: for overload in overloads: if overload.common is member: sf.write_code(overload.method_code) else: if is_inplace_number_slot(member.py_slot): sf.write( f''' if (!PyObject_TypeCheck(sipSelf, sipTypeAsPyTypeObject(sip{prefix}_{fq_cpp_name.as_word}))) {{ Py_INCREF(Py_NotImplemented); return Py_NotImplemented; }} ''') if not is_number_slot(member.py_slot): as_cpp = fq_cpp_name.as_cpp gto_name = _gto_name(scope) if isinstance(scope, WrappedClass): sf.write( f''' {as_cpp} *sipCpp = reinterpret_cast<{as_cpp} *>(sipGetCppPtr((sipSimpleWrapper *)sipSelf, {gto_name})); if (!sipCpp) ''') else: sf.write( f''' {as_cpp} sipCpp = static_cast<{as_cpp}>(sipConvertToEnum(sipSelf, {gto_name})); if (PyErr_Occurred()) ''') sf.write(f' return {ret_value};\n\n') if has_args: sf.write(' PyObject *sipParseErr = SIP_NULLPTR;\n') for overload in overloads: if overload.common is member and overload.is_abstract: sf.write(' PyObject *sipOrigSelf = sipSelf;\n') break scope_not_enum = not isinstance(scope, WrappedEnum) for overload in overloads: if overload.common is member: dereferenced = scope_not_enum and not overload.dont_deref_self _function_body(sf, spec, bindings, scope, overload, dereferenced=dereferenced) if has_args: if member.py_slot in (PySlot.CONCAT, PySlot.ICONCAT, PySlot.REPEAT, PySlot.IREPEAT): sf.write( f''' /* Raise an exception if the argument couldn't be parsed. */ sipBadOperatorArg(sipSelf, sipArg, {_get_slot_name(member.py_slot)}); return SIP_NULLPTR; ''') else: if is_rich_compare_slot(member.py_slot): sf.write( ''' Py_XDECREF(sipParseErr); ''') elif is_number_slot(member.py_slot) or is_inplace_number_slot(member.py_slot): sf.write( ''' Py_XDECREF(sipParseErr); if (sipParseErr == Py_None) return SIP_NULLPTR; ''') if is_number_slot(member.py_slot) or is_rich_compare_slot(member.py_slot): # We can only extend class slots. */ if not isinstance(scope, WrappedClass): sf.write( ''' PyErr_Clear(); Py_INCREF(Py_NotImplemented); return Py_NotImplemented; ''') elif is_number_slot(member.py_slot): sf.write( f''' return sipPySlotExtend(&sipModuleAPI_{spec.module.py_name}, {_get_slot_name(member.py_slot)}, SIP_NULLPTR, sipArg0, sipArg1); ''') else: sf.write( f''' return sipPySlotExtend(&sipModuleAPI_{spec.module.py_name}, {_get_slot_name(member.py_slot)}, {_gto_name(scope)}, sipSelf, sipArg); ''') elif is_inplace_number_slot(member.py_slot): sf.write( ''' PyErr_Clear(); Py_INCREF(Py_NotImplemented); return Py_NotImplemented; ''') else: member_name = '(sipValue != SIP_NULLPTR ? sipName___setattr__ : sipName___delattr__)' if member.py_slot is PySlot.SETATTR else _cached_name_ref(member.py_name) sf.write( f''' sipNoMethod(sipParseErr, {_cached_name_ref(py_name)}, {member_name}, SIP_NULLPTR); return {ret_value}; ''') else: sf.write( ''' return 0; ''') sf.write('}\n') def _class_functions(sf, spec, bindings, klass, py_debug): """ Generate the member functions for a class. """ as_word = klass.iface_file.fq_cpp_name.as_word scope_s = _scoped_class_name(spec, klass) # Any shadow code. if klass.has_shadow: if not klass.export_derived: _shadow_class_declaration(sf, spec, bindings, klass) _shadow_code(sf, spec, bindings, klass) # The member functions. for visible_member in klass.visible_members: if visible_member.member.py_slot is None: _member_function(sf, spec, bindings, klass, visible_member.member, visible_member.scope) # The slot functions. for member in klass.members: if klass.iface_file.type is IfaceFileType.NAMESPACE: _ordinary_function(sf, spec, bindings, member, scope=klass) elif member.py_slot is not None: _py_slot(sf, spec, bindings, member, scope=klass) # The cast function. if len(klass.superclasses) != 0: sf.write( f''' /* Cast a pointer to a type somewhere in its inheritance hierarchy. */ extern "C" {{static void *cast_{as_word}(void *, const sipTypeDef *);}} static void *cast_{as_word}(void *sipCppV, const sipTypeDef *targetType) {{ {_class_from_void(spec, klass)}; if (targetType == {_gto_name(klass)}) return sipCppV; ''') for superclass in klass.superclasses: sc_fq_cpp_name = superclass.iface_file.fq_cpp_name sc_scope_s = _scoped_class_name(spec, superclass) sc_gto_name = _gto_name(superclass) if len(superclass.superclasses) != 0: # Delegate to the super-class's cast function. This will # handle virtual and non-virtual diamonds. sf.write( f''' sipCppV = ((const sipClassTypeDef *){sc_gto_name})->ctd_cast(static_cast<{sc_scope_s} *>(sipCpp), targetType); if (sipCppV) return sipCppV; ''') else: # The super-class is a base class and so doesn't have a cast # function. It also means that a simple check will do instead. sf.write( f''' if (targetType == {sc_gto_name}) return static_cast<{sc_scope_s} *>(sipCpp); ''') sf.write( ''' return SIP_NULLPTR; } ''') if klass.iface_file.type is not IfaceFileType.NAMESPACE and not spec.c_bindings: # Generate the release function without compiler warnings. need_state = False need_ptr = need_cast_ptr = _is_used_in_code(klass.dealloc_code, 'sipCpp') public_dtor = klass.dtor is AccessSpecifier.PUBLIC if klass.can_create or public_dtor: if (_pyqt5(spec) or _pyqt6(spec)) and klass.is_qobject and public_dtor: need_ptr = need_cast_ptr = True elif klass.has_shadow: need_ptr = need_state = True elif public_dtor: need_ptr = True sf.write('\n\n/* Call the instance\'s destructor. */\n') sip_cpp_v = 'sipCppV' if spec.c_bindings or need_ptr else '' sip_state = ' sipState' if spec.c_bindings or need_state else '' if not spec.c_bindings: sf.write(f'extern "C" {{static void release_{as_word}(void *, int);}}\n') sf.write(f'static void release_{as_word}(void *{sip_cpp_v}, int{sip_state})\n{{\n') if need_cast_ptr: sf.write(f' {_class_from_void(spec, klass)};\n\n') if len(klass.dealloc_code) != 0: sf.write_code(klass.dealloc_code) sf.write('\n') if klass.can_create or public_dtor: release_gil = _release_gil(klass.dtor_gil_action, bindings) # If there is an explicit public dtor then assume there is some way # to call it which we haven't worked out (because we don't fully # understand C++). if release_gil: sf.write(' Py_BEGIN_ALLOW_THREADS\n\n') if (_pyqt5(spec) or _pyqt6(spec)) and klass.is_qobject and public_dtor: # QObjects should only be deleted in the threads that they # belong to. sf.write( ''' if (QThread::currentThread() == sipCpp->thread()) delete sipCpp; else sipCpp->deleteLater(); ''') elif klass.has_shadow: sf.write( f''' if (sipState & SIP_DERIVED_CLASS) delete reinterpret_cast(sipCppV); ''') if public_dtor: sf.write( f''' else delete reinterpret_cast<{_scoped_class_name(spec, klass)} *>(sipCppV); ''') elif public_dtor: sf.write( f''' delete reinterpret_cast<{_scoped_class_name(spec, klass)} *>(sipCppV); ''') if release_gil: sf.write('\n Py_END_ALLOW_THREADS\n') sf.write('}\n') # The traverse function. if klass.gc_traverse_code is not None: sf.write('\n\n') if not spec.c_bindings: sf.write(f'extern "C" {{static int traverse_{as_word}(void *, visitproc, void *);}}\n') sf.write( f'''static int traverse_{as_word}(void *sipCppV, visitproc sipVisit, void *sipArg) {{ {_class_from_void(spec, klass)}; int sipRes; ''') sf.write_code(klass.gc_traverse_code) sf.write( ''' return sipRes; } ''') # The clear function. if klass.gc_clear_code is not None: sf.write('\n\n') if not spec.c_bindings: sf.write(f'extern "C" {{static int clear_{as_word}(void *);}}\n') sf.write( f'''static int clear_{as_word}(void *sipCppV) {{ {_class_from_void(spec, klass)}; int sipRes; ''') sf.write_code(klass.gc_clear_code) sf.write( ''' return sipRes; } ''') # The buffer interface functions. if klass.bi_get_buffer_code is not None: code = klass.bi_get_buffer_code need_cpp = _is_used_in_code(code, 'sipCpp') sip_self = _arg_name(spec, 'sipSelf', code) sip_cpp_v = 'sipCppV' if spec.c_bindings or need_cpp else '' sf.write('\n\n') if not py_debug and spec.module.use_limited_api: if not spec.c_bindings: sf.write(f'extern "C" {{static int getbuffer_{as_word}(PyObject *, void *, sipBufferDef *);}}\n') sf.write(f'static int getbuffer_{as_word}(PyObject *{sip_self}, void *{sip_cpp_v}, sipBufferDef *sipBuffer)\n') else: if not spec.c_bindings: sf.write(f'extern "C" {{static int getbuffer_{as_word}(PyObject *, void *, Py_buffer *, int);}}\n') sip_flags = _arg_name(spec, 'sipFlags', code) sf.write(f'static int getbuffer_{as_word}(PyObject *{sip_self}, void *{sip_cpp_v}, Py_buffer *sipBuffer, int {sip_flags})\n') sf.write('{\n') if need_cpp: sf.write(f' {_class_from_void(spec, klass)};\n') sf.write(' int sipRes;\n\n') sf.write_code(code) sf.write('\n return sipRes;\n}\n') if klass.bi_release_buffer_code is not None: code = klass.bi_release_buffer_code need_cpp = _is_used_in_code(code, 'sipCpp') sip_self = _arg_name(spec, 'sipSelf', code) sip_cpp_v = 'sipCppV' if spec.c_bindings or need_cpp else '' sf.write('\n\n') if not py_debug and spec.module.use_limited_api: if not spec.c_bindings: sf.write(f'extern "C" {{static void releasebuffer_{as_word}(PyObject *, void *);}}\n') sf.write(f'static void releasebuffer_{as_word}(PyObject *{sip_self}, void *{sip_cpp_v})\n') else: if not spec.c_bindings: sf.write(f'extern "C" {{static void releasebuffer_{as_word}(PyObject *, void *, Py_buffer *);}}\n') sip_buffer = _arg_name(spec, 'sipBuffer', code) sf.write(f'static void releasebuffer_{as_word}(PyObject *{sip_self}, void *{sip_cpp_v}, Py_buffer *{sip_buffer})\n') sf.write('{\n') if need_cpp: sf.write(f' {_class_from_void(spec, klass)};\n') sf.write_code(code) sf.write('}\n') # The pickle function. if klass.pickle_code is not None: sf.write('\n\n') if not spec.c_bindings: sf.write(f'extern "C" {{static PyObject *pickle_{as_word}(void *);}}\n') sf.write( f'''static PyObject *pickle_{as_word}(void *sipCppV) {{ {_class_from_void(spec, klass)}; PyObject *sipRes; ''') sf.write_code(klass.pickle_code) sf.write('\n return sipRes;\n}\n') # The finalisation function. if klass.finalisation_code is not None: code = klass.finalisation_code need_cpp = _is_used_in_code(code, 'sipCpp') sip_self = _arg_name(spec, 'sipSelf', code) sip_cpp_v = 'sipCppV' if spec.c_bindings or need_cpp else '' sip_kwds = _arg_name(spec, 'sipKwds', code) sip_unused = _arg_name(spec, 'sipUnused', code) sf.write('\n\n') if not spec.c_bindings: sf.write(f'extern "C" {{static int final_{as_word}(PyObject *, void *, PyObject *, PyObject **);}}\n') sf.write( f'''static int final_{as_word}(PyObject *{sip_self}, void *{sip_cpp_v}, PyObject *{sip_kwds}, PyObject **{sip_unused}) {{ ''') if need_cpp: sf.write(f' {_class_from_void(spec, klass)};\n\n') sf.write_code(code) sf.write('}\n') # The mixin initialisation function. if klass.mixin: sf.write('\n\n') if not spec.c_bindings: sf.write(f'extern "C" {{static int mixin_{as_word}(PyObject *, PyObject *, PyObject *);}}\n') sf.write( f'''static int mixin_{as_word}(PyObject *sipSelf, PyObject *sipArgs, PyObject *sipKwds) {{ return sipInitMixin(sipSelf, sipArgs, sipKwds, (sipClassTypeDef *)&sipTypeDef_{spec.module.py_name}_{as_word}); }} ''') # The array allocation helpers. if spec.c_bindings or klass.needs_array_helper: sf.write('\n\n') if not spec.c_bindings: sf.write(f'extern "C" {{static void *array_{as_word}(Py_ssize_t);}}\n') sf.write(f'static void *array_{as_word}(Py_ssize_t sipNrElem)\n{{\n') if spec.c_bindings: sf.write(f' return sipMalloc(sizeof ({scope_s}) * sipNrElem);\n') else: sf.write(f' return new {scope_s}[sipNrElem];\n') sf.write('}\n') if _abi_supports_array(spec): sf.write('\n\n') if not spec.c_bindings: sf.write(f'extern "C" {{static void array_delete_{as_word}(void *);}}\n') sf.write(f'static void array_delete_{as_word}(void *sipCpp)\n{{\n') if spec.c_bindings: sf.write(' sipFree(sipCpp);\n') else: sf.write(f' delete[] reinterpret_cast<{scope_s} *>(sipCpp);\n') sf.write('}\n') # The copy and assignment helpers. if spec.c_bindings or klass.needs_copy_helper: # The assignment helper. We assume that there will be a valid # assigment operator if there is a a copy ctor. Note that the source # pointer is not const. This is to allow the source instance to be # modified as a consequence of the assignment, eg. if it is # implementing some sort of reference counting scheme. sf.write('\n\n') if not spec.c_bindings: sf.write(f'extern "C" {{static void assign_{as_word}(void *, Py_ssize_t, void *);}}\n') sf.write(f'static void assign_{as_word}(void *sipDst, Py_ssize_t sipDstIdx, void *sipSrc)\n{{\n') if spec.c_bindings: sf.write(f' (({scope_s} *)sipDst)[sipDstIdx] = *(({scope_s} *)sipSrc);\n') else: sf.write(f' reinterpret_cast<{scope_s} *>(sipDst)[sipDstIdx] = *reinterpret_cast<{scope_s} *>(sipSrc);\n') sf.write('}\n') # The copy helper. sf.write('\n\n') if not spec.c_bindings: sf.write(f'extern "C" {{static void *copy_{as_word}(const void *, Py_ssize_t);}}\n') sf.write(f'static void *copy_{as_word}(const void *sipSrc, Py_ssize_t sipSrcIdx)\n{{\n') if spec.c_bindings: sf.write( f''' {scope_s} *sipPtr = sipMalloc(sizeof ({scope_s})); *sipPtr = ((const {scope_s} *)sipSrc)[sipSrcIdx]; return sipPtr; ''') else: sf.write( f''' return new {scope_s}(reinterpret_cast(sipSrc)[sipSrcIdx]); ''') sf.write('}\n') # The dealloc function. if _need_dealloc(spec, bindings, klass): sf.write('\n\n') if not spec.c_bindings: sf.write(f'extern "C" {{static void dealloc_{as_word}(sipSimpleWrapper *);}}\n') sf.write(f'static void dealloc_{as_word}(sipSimpleWrapper *sipSelf)\n{{\n') if bindings.tracing: sf.write(f' sipTrace(SIP_TRACE_DEALLOCS, "dealloc_{as_word}()\\n");\n\n') # Disable the virtual handlers. if klass.has_shadow: sf.write( f''' if (sipIsDerivedClass(sipSelf)) reinterpret_cast(sipGetAddress(sipSelf))->sipPySelf = SIP_NULLPTR; ''') if spec.c_bindings or klass.dtor is AccessSpecifier.PUBLIC or (klass.has_shadow and klass.dtor is AccessSpecifier.PROTECTED): sf.write(' if (sipIsOwnedByPython(sipSelf))\n {\n') if klass.delay_dtor: sf.write(' sipAddDelayedDtor(sipSelf);\n') elif spec.c_bindings: if klass.dealloc_code: sf.write(klass.dealloc_code) sf.write(' sipFree(sipGetAddress(sipSelf));\n') else: flag = 'sipIsDerivedClass(sipSelf)' if klass.has_shadow else '0' sf.write(f' release_{as_word}(sipGetAddress(sipSelf), {flag});\n') sf.write(' }\n') sf.write('}\n') # The type initialisation function. if klass.can_create: _type_init(sf, spec, bindings, klass) def _shadow_code(sf, spec, bindings, klass): """ Generate the shadow (derived) class code. """ klass_name = klass.iface_file.fq_cpp_name.as_word klass_cpp_name = klass.iface_file.fq_cpp_name.as_cpp # Generate the wrapper class constructors. nr_virtuals = _count_virtual_overloads(spec, klass) for ctor in _unique_class_ctors(spec, klass): throw_specifier = _throw_specifier(bindings, ctor.throw_args) protected_call_args = _protected_call_args(spec, ctor.cpp_signature) args = fmt_signature_as_cpp_definition(spec, ctor.cpp_signature, scope=klass.iface_file) sf.write(f'\nsip{klass_name}::sip{klass_name}({args}){throw_specifier}: {_scoped_class_name(spec, klass)}({protected_call_args}), sipPySelf(SIP_NULLPTR)\n{{\n') if bindings.tracing: args = fmt_signature_as_cpp_declaration(spec, ctor.cpp_signature, scope=klass.iface_file) sf.write(f' sipTrace(SIP_TRACE_CTORS, "sip{klass_name}::sip{klass_name}({args}){throw_specifier} (this=0x%%08x)\\n", this);\n\n') if nr_virtuals > 0: sf.write(' memset(sipPyMethods, 0, sizeof (sipPyMethods));\n') sf.write('}\n') # The destructor. if klass.dtor is not AccessSpecifier.PRIVATE: throw_specifier = _throw_specifier(bindings, klass.dtor_throw_args) sf.write(f'\nsip{klass_name}::~sip{klass_name}(){throw_specifier}\n{{\n') if bindings.tracing: sf.write(f' sipTrace(SIP_TRACE_DTORS, "sip{klass_name}::~sip{klass_name}(){throw_specifier} (this=0x%%08x)\\n", this);\n\n') if klass.dtor_virtual_catcher_code is not None: sf.write_code(klass.dtor_virtual_catcher_code) sf.write(' sipInstanceDestroyedEx(&sipPySelf);\n}\n') # The meta methods if required. if (_pyqt5(spec) or _pyqt6(spec)) and klass.is_qobject: module_name = spec.module.py_name gto_name = _gto_name(klass) if not klass.pyqt_no_qmetaobject: sf.write( f''' const QMetaObject *sip{klass_name}::metaObject() const {{ if (sipGetInterpreter()) return QObject::d_ptr->metaObject ? QObject::d_ptr->dynamicMetaObject() : sip_{module_name}_qt_metaobject(sipPySelf, {gto_name}); return {klass_cpp_name}::metaObject(); }} ''') sf.write( f''' int sip{klass_name}::qt_metacall(QMetaObject::Call _c, int _id, void **_a) {{ _id = {klass_cpp_name}::qt_metacall(_c, _id, _a); if (_id >= 0) {{ SIP_BLOCK_THREADS _id = sip_{module_name}_qt_metacall(sipPySelf, {gto_name}, _c, _id, _a); SIP_UNBLOCK_THREADS }} return _id; }} void *sip{klass_name}::qt_metacast(const char *_clname) {{ void *sipCpp; return (sip_{module_name}_qt_metacast(sipPySelf, {gto_name}, _clname, &sipCpp) ? sipCpp : {klass_cpp_name}::qt_metacast(_clname)); }} ''') # Generate the virtual catchers. for virt_nr, virtual_overload in enumerate(_unique_class_virtual_overloads(spec, klass)): _virtual_catcher(sf, spec, bindings, klass, virtual_overload, virt_nr) # Generate the wrapper around each protected member function. _protected_definitions(sf, spec, klass) def _protected_enums(sf, spec, klass): """ Generate the protected enums for a class. """ for enum in spec.enums: if not enum.is_protected: continue # See if the class defining the enum is in our class hierachy. for mro_klass in klass.mro: if mro_klass is enum.scope: break else: continue sf.write( ''' /* Expose this protected enum. */ enum''') if enum.fq_cpp_name is not None: sf.write(' sip' + enum.fq_cpp_name.base_name) sf.write(' {') eol = '\n' scope_cpp_name = enum.scope.iface_file.fq_cpp_name.as_cpp for member in enum.members: member_cpp_name = member.cpp_name sf.write(f'{eol} {member_cpp_name} = {scope_cpp_name}::{member_cpp_name}') eol = ',\n' sf.write('\n };\n') def _virtual_catcher(sf, spec, bindings, klass, virtual_overload, virt_nr): """ Generate the catcher for a virtual function. """ overload = virtual_overload.overload result = overload.cpp_signature.result result_type = fmt_argument_as_cpp_type(spec, result, scope=klass.iface_file, make_public=True) klass_name = klass.iface_file.fq_cpp_name.as_word overload_cpp_name = _overload_cpp_name(overload) throw_specifier = _throw_specifier(bindings, overload.throw_args) const = ' const' if overload.is_const else '' protection_state = set() _remove_protections(overload.cpp_signature, protection_state) args = fmt_signature_as_cpp_definition(spec, overload.cpp_signature, scope=klass.iface_file) sf.write(f'\n{result_type} sip{klass_name}::{overload_cpp_name}({args}){const}{throw_specifier}\n{{\n') if bindings.tracing: args = fmt_signature_as_cpp_declaration(spec, overload.cpp_signature, scope=klass.iface_file) sf.write(f' sipTrace(SIP_TRACE_CATCHERS, "{result_type} sip{klass_name}::{overload_cpp_name}({args}){const}{throw_specifier} (this=0x%%08x)\\n", this);\n\n') _restore_protections(protection_state) sf.write( ''' sip_gilstate_t sipGILState; PyObject *sipMeth; ''') if overload.is_const: const_cast_char = 'const_cast(' const_cast_sw = 'const_cast(' const_cast_tail = ')' else: const_cast_char = '' const_cast_sw = '' const_cast_tail = '' abi_12_8_arg = f'{const_cast_sw}&sipPySelf{const_cast_tail}, ' if spec.abi_version >= (12, 8) else '' klass_py_name_ref = _cached_name_ref(klass.py_name) if overload.is_abstract else 'SIP_NULLPTR' member_py_name_ref = _cached_name_ref(overload.common.py_name) sf.write(f' sipMeth = sipIsPyMethod(&sipGILState, {const_cast_char}&sipPyMethods[{virt_nr}]{const_cast_tail}, {abi_12_8_arg}{klass_py_name_ref}, {member_py_name_ref});\n') # The rest of the common code. if result is not None and result.type is ArgumentType.VOID and len(result.derefs) == 0: result = None sf.write('\n if (!sipMeth)\n') if overload.virtual_call_code is not None: sf.write(' {\n') if result is not None: sip_res = fmt_argument_as_cpp_type(spec, result, name='sipRes', scope=klass.iface_file) sf.write(f' {sip_res};\n') sf.write('\n') sf.write_code(overload.virtual_call_code) sip_res = ' sipRes' if result is not None else '' sf.write( f''' return{sip_res}; }} ''') elif overload.is_abstract: _default_instance_return(sf, spec, result) else: if result is None: sf.write(' {\n ') else: sf.write(' return ') args = [] for arg_nr, arg in enumerate(overload.cpp_signature.args): args.append(fmt_argument_as_name(spec, arg, arg_nr)) args = ', '.join(args) sf.write(f'{klass.iface_file.fq_cpp_name.as_cpp}::{overload_cpp_name}({args});\n') if result is None: # Note that we should also generate this if the function returns a # value, but we are lazy and this is all that is needed by PyQt. if overload.new_thread: sf.write(' sipEndThread();\n') sf.write(' return;\n }\n') sf.write('\n') _virtual_handler_call(sf, spec, klass, virtual_overload, result) sf.write('}\n') def _virtual_handler_call(sf, spec, klass, virtual_overload, result): """ Generate a call to a single virtual handler. """ module = spec.module overload = virtual_overload.overload handler = virtual_overload.handler module_name = module.py_name protection_state = _fake_protected_args(handler.cpp_signature) result_type = fmt_argument_as_cpp_type(spec, overload.cpp_signature.result, scope=klass.iface_file) sf.write(f' extern {result_type} sipVH_{module_name}_{handler.handler_nr}(sip_gilstate_t, sipVirtErrorHandlerFunc, sipSimpleWrapper *, PyObject *') if len(handler.cpp_signature.args) > 0: sf.write(', ' + fmt_signature_as_cpp_declaration(spec, handler.cpp_signature, scope=klass.iface_file)) _restore_protected_args(protection_state) # Add extra arguments for all the references we need to keep. args_keep = False result_keep = False saved_keys = {} if result is not None and _keep_py_reference(result): result_keep = True saved_keys[result] = result.key result.key = module.next_key module.next_key -= 1 sf.write(', int') for arg in overload.cpp_signature.args: if arg.is_out and _keep_py_reference(arg): args_keep = True saved_keys[arg] = arg.key arg.key = module.next_key module.next_key -= 1 sf.write(', int') sf.write(');\n\n ') trailing = '' if not overload.new_thread and result is not None: sf.write('return ') if result.type is ArgumentType.ENUM and result.definition.is_protected: protection_state = set() _remove_protection(result, protection_state) enum_type = fmt_enum_as_cpp_type(result.definition) sf.write(f'static_cast<{enum_type}>(') trailing = ')' _restore_protections(protection_state) error_handler = handler.virtual_error_handler if error_handler is None: error_handler_ref = '0' elif error_handler.module is module: error_handler_ref = f'sipVEH_{module_name}_{error_handler.name}' else: error_handler_ref = f'sipImportedVirtErrorHandlers_{module_name}_{error_handler.module.py_name}[{error_handler.handler_nr}].iveh_handler' sf.write(f'sipVH_{module_name}_{handler.handler_nr}(sipGILState, {error_handler_ref}, sipPySelf, sipMeth') for arg_nr, arg in enumerate(overload.cpp_signature.args): prefix = '' if arg.type is ArgumentType.CLASS and arg.definition.is_protected: if arg.is_reference or len(arg.derefs) == 0: prefix = '&' elif arg.type is ArgumentType.ENUM and arg.definition.is_protected: prefix = '(' + fmt_enum_as_cpp_type(arg.definition) + ')' arg_name = fmt_argument_as_name(spec, arg, arg_nr) sf.write(f', {prefix}{arg_name}') # Pass the keys to maintain the kept references. if result_keep: sf.write(', ' + str(result.key)) if args_keep: for arg in overload.cpp_signature.args: if arg.is_out and _keep_py_reference(arg): sf.write(', ' + str(arg.key)) for type, key in saved_keys.items(): type.key = key sf.write(f'){trailing};\n') if overload.new_thread: sf.write('\n sipEndThread();\n') def _cast_zero(spec, arg): """ Return a cast to zero. """ if arg.type is ArgumentType.ENUM: enum = arg.definition enum_type = fmt_enum_as_cpp_type(enum) if len(enum.members) == 0: return f'({enum_type})0' if enum.is_scoped: scope = enum_type elif enum.scope is not None: scope = _enum_class_scope(spec, enum) else: scope = '' return scope + '::' + enum.members[0].cpp_name if arg.type in (ArgumentType.PYOBJECT, ArgumentType.PYTUPLE, ArgumentType.PYLIST, ArgumentType.PYDICT, ArgumentType.PYCALLABLE, ArgumentType.PYSLICE, ArgumentType.PYTYPE, ArgumentType.PYBUFFER, ArgumentType.PYENUM, ArgumentType.ELLIPSIS): return 'SIP_NULLPTR' return '0' def _default_instance_return(sf, spec, result): """ Generate a statement to return the default instance of a type typically on error (ie. when there is nothing sensible to return). """ # Handle the trivial case. if result is None: sf.write(' return;\n') return result_type_plain = fmt_argument_as_cpp_type(spec, result, plain=True, no_derefs=True) # Handle any %InstanceCode. if len(result.derefs) == 0 and result.type in (ArgumentType.CLASS, ArgumentType.MAPPED): instance_code = result.definition.instance_code else: instance_code = None if instance_code is not None: sf.write( f''' {{ static {result_type_plain} *sipCpp = SIP_NULLPTR; if (!sipCpp) {{ ''') sf.write_code(instance_code) sf.write( ''' } return *sipCpp; } ''') return sf.write(' return ') if result.type is ArgumentType.MAPPED and len(result.derefs) == 0: # We don't know anything about the mapped type so we just hope is has a # default ctor. if result.is_reference: sf.write('*new ') sf.write(result_type_plain + '()') elif result.type is ArgumentType.CLASS and len(result.derefs) == 0: # If we don't have a suitable ctor then the generated code will issue # an error message. ctor = result.definition.default_ctor if ctor is not None and ctor.access_specifier is AccessSpecifier.PUBLIC and ctor.cpp_signature is not None: # If this is a badly designed class. We can only generate correct # code by leaking memory. if result.is_reference: sf.write('*new ') sf.write(result_type_plain) sf.write(_call_default_ctor(spec, ctor)) else: raise UserException( result.definition.iface_file.fq_cpp_name.as_cpp + " must have a default constructor") else: sf.write(_cast_zero(spec, result)) sf.write(';\n') def _call_default_ctor(spec, ctor): """ Return the call to a default ctor. """ args = [] for arg in ctor.cpp_signature.args: if arg.default_value is not None: break # Do what we can to provide type information to the compiler. if arg.type is ArgumentType.CLASS and len(arg.derefs) > 0 and not arg.is_reference: class_type = fmt_argument_as_cpp_type(spec, arg) arg_s = f'static_cast<{class_type}>(0)' elif arg.type is ArgumentType.ENUM: enum_type = fmt_enum_as_cpp_type(arg.definition) arg_s = f'static_cast<{enum_type}>(0)' elif arg.type in (ArgumentType.FLOAT, ArgumentType.CFLOAT): arg_s = '0.0F' elif arg.type in (ArgumentType.DOUBLE, ArgumentType.CDOUBLE): arg_s = '0.0' elif arg.type in (ArgumentType.UINT, ArgumentType.SIZE): arg_s = '0U' elif arg.type in (ArgumentType.LONG, ArgumentType.LONGLONG): arg_s = '0L' elif arg.type in (ArgumentType.ULONG, ArgumentType.ULONGLONG): arg_s = '0UL' elif arg.type in (ArgumentType.ASCII_STRING, ArgumentType.LATIN1_STRING, ArgumentType.UTF8_STRING, ArgumentType.USTRING, ArgumentType.SSTRING, ArgumentType.STRING) and len(arg.derefs) == 0: arg_s = "'\\0'" elif arg.type is ArgumentType.WSTRING and len(arg.derefs) == 0: arg_s = "L'\\0'" else: arg_s = '0' args.append(arg_s) return '(' + ', '.join(args) + ')' def _protected_declarations(sf, spec, klass): """ Generate the declarations of the protected wrapper functions for a class. """ no_intro = True for visible_member in klass.visible_members: if visible_member.member.py_slot is not None: continue for overload in visible_member.scope.overloads: if overload.common is not visible_member.member or overload.access_specifier is not AccessSpecifier.PROTECTED: continue # Check we haven't already handled this signature (eg. if we have # specified the same method with different Python names. if _is_duplicate_protected(spec, klass, overload): continue if no_intro: sf.write( ''' /* * There is a public method for every protected method visible from * this class. */ ''') no_intro = False sf.write(' ') if overload.is_static: sf.write('static ') result_type = fmt_argument_as_cpp_type(spec, overload.cpp_signature.result, scope=klass.iface_file) if not overload.is_static and not overload.is_abstract and (overload.is_virtual or overload.is_virtual_reimplementation): sf.write(f'{result_type} sipProtectVirt_{overload.cpp_name}(bool') if len(overload.cpp_signature.args) > 0: sf.write(', ') else: sf.write(f'{result_type} sipProtect_{overload.cpp_name}(') args = fmt_signature_as_cpp_declaration(spec, overload.cpp_signature, scope=klass.iface_file) const_s = ' const' if overload.is_const else '' sf.write(f'{args}){const_s};\n') def _protected_definitions(sf, spec, klass): """ Generate the definitions of the protected wrapper functions for a class. """ klass_name = klass.iface_file.fq_cpp_name.as_word for visible_member in klass.visible_members: if visible_member.member.py_slot is not None: continue for overload in visible_member.scope.overloads: if overload.common is not visible_member.member or overload.access_specifier is not AccessSpecifier.PROTECTED: continue # Check we haven't already handled this signature (eg. if we have # specified the same method with different Python names. if _is_duplicate_protected(spec, klass, overload): continue overload_name = overload.cpp_name result = overload.cpp_signature.result result_type = fmt_argument_as_cpp_type(spec, result, scope=klass.iface_file) sf.write('\n') if not overload.is_static and not overload.is_abstract and (overload.is_virtual or overload.is_virtual_reimplementation): sf.write(f'{result_type} sip{klass_name}::sipProtectVirt_{overload_name}(bool sipSelfWasArg') if len(overload.cpp_signature.args) > 0: sf.write(', ') else: sf.write(f'{result_type} sip{klass_name}::sipProtect_{overload_name}(') args = fmt_signature_as_cpp_definition(spec, overload.cpp_signature, scope=klass.iface_file) const_s = ' const' if overload.is_const else '' sf.write(f'{args}){const_s}\n{{\n') closing_parens = ')' if result.type is ArgumentType.VOID and len(result.derefs) == 0: sf.write(' ') else: sf.write(' return ') if result.type is ArgumentType.CLASS and result.definition.is_protected: scope_s = _scoped_class_name(spec, klass) sf.write(f'static_cast<{scope_s} *>(') closing_parens += ')' elif result.type is ArgumentType.ENUM and result.definition.is_protected: # One or two older compilers can't handle a static_cast # here so we revert to a C-style cast. sf.write('(' + fmt_enum_as_cpp_type(result.definition) + ')') protected_call_args = _protected_call_args(spec, overload.cpp_signature) if not overload.is_abstract: visible_scope_s = _scoped_class_name(spec, visible_member.scope) if overload.is_virtual or overload.is_virtual_reimplementation: sf.write(f'(sipSelfWasArg ? {visible_scope_s}::{overload_name}({protected_call_args}) : ') closing_parens += ')' else: sf.write(visible_scope_s + '::') sf.write(f'{overload_name}({protected_call_args}{closing_parens};\n}}\n') def _is_duplicate_protected(spec, klass, target_overload): """ Return True if a protected method is a duplicate. """ for visible_member in klass.visible_members: if visible_member.member.py_slot is not None: continue for overload in visible_member.scope.overloads: if overload.common is not visible_member.member or overload.access_specifier is not AccessSpecifier.PROTECTED: continue if overload is target_overload: return False if overload.cpp_name == target_overload.cpp_name and same_signature(spec, overload.cpp_signature, target_overload.cpp_signature): return True # We should never get here. return False def _protected_call_args(spec, signature): """ Return the arguments for a call to a protected method. """ args = [] for arg_nr, arg in enumerate(signature.args): if arg.type is ArgumentType.ENUM and arg.definition.is_protected: cast_s = '(' + arg.definition.fq_cpp_name.as_cpp + ')' else: cast_s = '' args.append(cast_s + fmt_argument_as_name(spec, arg, arg_nr)) return ', '.join(args) def _virtual_handler(sf, spec, handler): """ Generate the function that does most of the work to handle a particular virtual function. """ module = spec.module result = handler.cpp_signature.result result_decl = fmt_argument_as_cpp_type(spec, result) result_is_returned = (result.type is not ArgumentType.VOID or len(result.derefs) != 0) result_is_reference = False result_instance_code = None if result_is_returned: # If we are returning a reference to an instance then we take care to # handle Python errors but still return a valid C++ instance. if result.type in (ArgumentType.CLASS, ArgumentType.MAPPED) and len(result.derefs) == 0: if result.is_reference: result_is_reference = True else: result_instance_code = result.definition.instance_code sf.write( f''' {result_decl} sipVH_{module.py_name}_{handler.handler_nr}(sip_gilstate_t sipGILState, sipVirtErrorHandlerFunc sipErrorHandler, sipSimpleWrapper *sipPySelf, PyObject *sipMethod''') if len(handler.cpp_signature.args) > 0: sf.write(', ' + fmt_signature_as_cpp_definition(spec, handler.cpp_signature)) # Define the extra arguments for kept references. if result_is_returned and _keep_py_reference(result): sf.write(', int') if handler.virtual_catcher_code is None or _is_used_in_code(handler.virtual_catcher_code, 'sipResKey'): sf.write(' sipResKey') for arg_nr, arg in enumerate(handler.cpp_signature.args): if arg.is_out and _keep_py_reference(arg): arg_name = fmt_argument_as_name(spec, arg, arg_nr) sf.write(f', int {arg_name}Key') sf.write(')\n{\n') if result_is_returned: result_plain_decl = fmt_argument_as_cpp_type(spec, result, plain=True) if result_instance_code is not None: sf.write( f''' static {result_plain_decl} *sipCpp = SIP_NULLPTR; if (!sipCpp) {{ ''') sf.write_code(result_instance_code) sf.write(' }\n\n') sf.write(' ') # wchar_t * return values are always on the heap. To reduce memory # leaks we keep the last result around until we have a new one. This # means that ownership of the return value stays with the function # returning it - which is consistent with how other types work, even # though it may not be what's required in all cases. Note that we # should do this in the code that calls the handler instead of here (as # we do with strings) so that it doesn't get shared between all # callers. if result.type is ArgumentType.WSTRING and len(result.derefs) == 1: sf.write('static ') sf.write(result_plain_decl) sf.write(' {}sipRes'.format('*' if result_is_reference else '')) sipres_value = '' if result.type in (ArgumentType.CLASS, ArgumentType.MAPPED, ArgumentType.TEMPLATE) and len(result.derefs) == 0: if result_instance_code is not None: sipres_value = ' = *sipCpp' elif result.type is ArgumentType.CLASS: ctor = result.definition.default_ctor if ctor is not None and ctor.access_specifier is AccessSpecifier.PUBLIC and ctor.cpp_signature is not None and len(ctor.cpp_signature.args) > 0 and ctor.cpp_signature.args[0].default_value is None: sipres_value = _call_default_ctor(spec, ctor) elif result.type is ArgumentType.ENUM and result.definition.is_protected: # Currently SIP generates the virtual handlers before any shadow # classes which means that the compiler doesn't know about the # handling of protected enums. Therefore we can only initialise to # 0. sipres_value = ' = 0' else: # We initialise the result to try and suppress a compiler warning. sipres_value = ' = ' + _cast_zero(spec, result) sf.write(sipres_value + ';\n') if result.type is ArgumentType.WSTRING and len(result.derefs) == 1: free_arg = 'const_cast(sipRes)' if result.is_const else 'sipRes' sf.write( f''' if (sipRes) {{ // Return any previous result to the heap. sipFree({free_arg}); sipRes = SIP_NULLPTR; }} ''') if handler.virtual_catcher_code is not None: error_flag = _need_error_flag(handler.virtual_catcher_code) old_error_flag = _need_old_error_flag(handler.virtual_catcher_code) if error_flag: sf.write(' sipErrorState sipError = sipErrorNone;\n') elif old_error_flag: sf.write(' int sipIsErr = 0;\n') sf.write('\n') sf.write_code(handler.virtual_catcher_code) sf.write( ''' Py_DECREF(sipMethod); ''') if error_flag or old_error_flag: error_test = 'sipError != sipErrorNone' if error_flag else 'sipIsErr' sf.write( f''' if ({error_test}) sipCallErrorHandler(sipErrorHandler, sipPySelf, sipGILState); ''') sf.write( ''' SIP_RELEASE_GIL(sipGILState) ''') if result_is_returned: sf.write( ''' return sipRes; ''') sf.write('}\n') return # See how many values we expect. nr_values = 1 if result_is_returned else 0 for arg in handler.py_signature.args: if arg.is_out: nr_values += 1 # Call the method. if nr_values == 0: sf.write( ' sipCallProcedureMethod(sipGILState, sipErrorHandler, sipPySelf, sipMethod, ') else: sf.write( ' PyObject *sipResObj = sipCallMethod(SIP_NULLPTR, sipMethod, ') sf.write(_tuple_builder(spec, handler.py_signature)) if nr_values == 0: sf.write('''); } ''') return # Generate the call to sipParseResultEx(). params = ['sipGILState', 'sipErrorHandler', 'sipPySelf', 'sipMethod', 'sipResObj'] # Build the format string. fmt = '"' if nr_values == 0: fmt += 'Z' else: if nr_values > 1: fmt += '(' if result_is_returned: fmt += _get_parse_result_format(result, spec, result_is_reference=result_is_reference, transfer_result=handler.transfer_result) for arg in handler.py_signature.args: if arg.is_out: fmt += _get_parse_result_format(arg, spec) if nr_values > 1: fmt += ')' fmt += '"' params.append(fmt) # Add the destination pointers. if result_is_returned: _add_parse_result_extra_params(params, spec, module, result) params.append('&sipRes') for arg_nr, arg in enumerate(handler.py_signature.args): if arg.is_out: _add_parse_result_extra_params(params, spec, module, arg, arg_nr) arg_ref = '&' if arg.is_reference else '' arg_name = fmt_argument_as_name(spec, arg, arg_nr) params.append(arg_ref + arg_name) params = ', '.join(params) return_code = 'int sipRc = ' if result_is_reference or handler.abort_on_exception else '' sf.write(f'''); {return_code}sipParseResultEx({params}); ''') if result_is_returned: if result_is_reference or handler.abort_on_exception: sf.write( ''' if (sipRc < 0) ''') if handler.abort_on_exception: sf.write(' abort();\n') else: _default_instance_return(sf, spec, result) result_ref = '*' if result_is_reference else '' sf.write( f''' return {result_ref}sipRes; ''') sf.write('}\n') def _add_parse_result_extra_params(params, spec, module, arg, arg_nr=-1): """ Add any extra parameters needed by sipParseResultEx() for a particular type to a list. """ if arg.type in (ArgumentType.CLASS, ArgumentType.MAPPED, ArgumentType.ENUM): params.append(_gto_name(arg.definition)) elif arg.type is ArgumentType.PYTUPLE: params.append('&PyTuple_Type') elif arg.type is ArgumentType.PYLIST: params.append('&PyList_Type') elif arg.type is ArgumentType.PYDICT: params.append('&PyDict_Type') elif arg.type is ArgumentType.PYSLICE: params.append('&PySlice_Type') elif arg.type is ArgumentType.PYTYPE: params.append('&PyType_Type') elif arg.type is ArgumentType.CAPSULE: params.append('"' + arg.definition.as_cpp + '"') elif _keep_py_reference(arg): if arg_nr < 0: params.append('sipResKey') else: params.append(fmt_argument_as_name(spec, arg, arg_nr) + 'Key') def _get_parse_result_format(arg, spec, result_is_reference=False, transfer_result=False): """ Return the format characters used by sipParseResultEx() for a particular type. """ nr_derefs = len(arg.derefs) no_derefs = (nr_derefs == 0) if arg.type in (ArgumentType.MAPPED, ArgumentType.FAKE_VOID, ArgumentType.CLASS): f = 0x00 if nr_derefs == 0: f |= 0x01 if not result_is_reference: f |= 0x04 elif nr_derefs == 1: if arg.is_out: f |= 0x04 elif arg.disallow_none: f |= 0x01 if transfer_result: f |= 0x02 return 'H' + str(f) if arg.type in (ArgumentType.BOOL, ArgumentType.CBOOL): return 'b' if arg.type is ArgumentType.ASCII_STRING: return 'aA' if no_derefs else 'AA' if arg.type is ArgumentType.LATIN1_STRING: return 'aL' if no_derefs else 'AL' if arg.type is ArgumentType.UTF8_STRING: return 'a8' if no_derefs else 'A8' if arg.type in (ArgumentType.SSTRING, ArgumentType.USTRING, ArgumentType.STRING): return 'c' if no_derefs else 'B' if arg.type is ArgumentType.WSTRING: return 'w' if no_derefs else 'x' if arg.type is ArgumentType.ENUM: return 'F' if arg.definition.fq_cpp_name is not None else 'e' if arg.type is ArgumentType.BYTE: return 'I' if _abi_has_working_char_conversion(spec) else 'L' if arg.type is ArgumentType.SBYTE: return 'L' if arg.type is ArgumentType.UBYTE: return 'M' if arg.type is ArgumentType.USHORT: return 't' if arg.type is ArgumentType.SHORT: return 'h' if arg.type in (ArgumentType.INT, ArgumentType.CINT): return 'i' if arg.type is ArgumentType.UINT: return 'u' if arg.type is ArgumentType.SIZE: return '=' if arg.type is ArgumentType.LONG: return 'l' if arg.type is ArgumentType.ULONG: return 'm' if arg.type is ArgumentType.LONGLONG: return 'n' if arg.type is ArgumentType.ULONGLONG: return 'o' if arg.type in (ArgumentType.STRUCT, ArgumentType.UNION, ArgumentType.VOID): return 'V' if arg.type is ArgumentType.CAPSULE: return 'z' if arg.type in (ArgumentType.FLOAT, ArgumentType.CFLOAT): return 'f' if arg.type in (ArgumentType.DOUBLE, ArgumentType.CDOUBLE): return 'd' if arg.type is ArgumentType.PYOBJECT: return 'O' if arg.type in (ArgumentType.PYTUPLE, ArgumentType.PYLIST, ArgumentType.PYDICT, ArgumentType.SLICE, ArgumentType.PYTYPE): return 'N' if arg.allow_none else 'T' if arg.type is ArgumentType.PYBUFFER: return '$' if arg.allow_none else '!' if arg.type is ArgumentType.PYENUM: return '^' if arg.allow_none else '&' # We should never get here. return ' ' def _tuple_builder(spec, signature): """ Return the code to build a tuple of Python arguments. """ array_len_arg_nr = -1 format_s = '"' for arg_nr, arg in enumerate(signature.args): if not arg.is_in: continue format_ch = '' nr_derefs = len(arg.derefs) not_a_pointer = (nr_derefs == 0 or (nr_derefs == 1 and arg.is_out)) if arg.type in (ArgumentType.ASCII_STRING, ArgumentType.LATIN1_STRING, ArgumentType.UTF8_STRING): format_ch = 'a' if not_a_pointer else 'A' elif arg.type in (ArgumentType.SSTRING, ArgumentType.USTRING, ArgumentType.STRING): if not_a_pointer: format_ch = 'c' elif arg.array is ArrayArgument.ARRAY: format_ch = 'g' else: format_ch = 's' elif arg.type is ArgumentType.WSTRING: if not_a_pointer: format_ch = 'w' elif arg.array is ArrayArgument.ARRAY: format_ch = 'G' else: format_ch = 'x' elif arg.type in (ArgumentType.BOOL, ArgumentType.CBOOL): format_ch = 'b' elif arg.type is ArgumentType.ENUM: format_ch = 'e' if arg.definition.fq_cpp_name is None else 'F' elif arg.type is ArgumentType.CINT: format_ch = 'i' elif arg.type is ArgumentType.UINT: if arg.array is ArrayArgument.ARRAY_SIZE: array_len_arg_nr = arg_nr else: format_ch = 'u' elif arg.type is ArgumentType.INT: if arg.array is ArrayArgument.ARRAY_SIZE: array_len_arg_nr = arg_nr else: format_ch = 'i' elif arg.type is ArgumentType.SIZE: if arg.array is ArrayArgument.ARRAY_SIZE: array_len_arg_nr = arg_nr else: format_ch = '=' elif arg.type in (ArgumentType.BYTE, ArgumentType.SBYTE): if arg.array is ArrayArgument.ARRAY_SIZE: array_len_arg_nr = arg_nr else: # Note that this is the correct thing to do even if char is # unsigned. format_ch = 'L' elif arg.type is ArgumentType.UBYTE: if arg.array is ArrayArgument.ARRAY_SIZE: array_len_arg_nr = arg_nr else: format_ch = 'M' elif arg.type is ArgumentType.USHORT: if arg.array is ArrayArgument.ARRAY_SIZE: array_len_arg_nr = arg_nr else: format_ch = 't' elif arg.type is ArgumentType.SHORT: if arg.array is ArrayArgument.ARRAY_SIZE: array_len_arg_nr = arg_nr else: format_ch = 'h' elif arg.type is ArgumentType.LONG: if arg.array is ArrayArgument.ARRAY_SIZE: array_len_arg_nr = arg_nr else: format_ch = 'l' elif arg.type is ArgumentType.ULONG: if arg.array is ArrayArgument.ARRAY_SIZE: array_len_arg_nr = arg_nr else: format_ch = 'm' elif arg.type is ArgumentType.LONGLONG: if arg.array is ArrayArgument.ARRAY_SIZE: array_len_arg_nr = arg_nr else: format_ch = 'n' elif arg.type is ArgumentType.ULONGLONG: if arg.array is ArrayArgument.ARRAY_SIZE: array_len_arg_nr = arg_nr else: format_ch = 'o' elif arg.type in (ArgumentType.STRUCT, ArgumentType.UNION, ArgumentType.VOID): format_ch = 'V' elif arg.type is ArgumentType.CAPSULE: format_ch = 'z' elif arg.type in (ArgumentType.FLOAT, ArgumentType.CFLOAT): format_ch = 'f' elif arg.type in (ArgumentType.DOUBLE, ArgumentType.CDOUBLE): format_ch = 'd' elif arg.type in (ArgumentType.MAPPED, ArgumentType.CLASS): if arg.array is ArrayArgument.ARRAY: format_ch = 'r' else: format_ch = 'N' if _needs_heap_copy(arg) else 'D' elif arg.type is ArgumentType.FAKE_VOID: format_ch = 'D' elif arg.type in (ArgumentType.PYOBJECT, ArgumentType.PYTUPLE, ArgumentType.PYLIST, ArgumentType.PYDICT, ArgumentType.PYCALLABLE, ArgumentType.PYSLICE, ArgumentType.PYTYPE, ArgumentType.PYBUFFER, ArgumentType.PYENUM): format_ch = 'S' format_s += format_ch format_s += '"' args = [format_s] for arg_nr, arg in enumerate(signature.args): if not arg.is_in: continue nr_derefs = len(arg.derefs) if arg.type in (ArgumentType.ASCII_STRING, ArgumentType.LATIN1_STRING, ArgumentType.UTF8_STRING, ArgumentType.SSTRING, ArgumentType.USTRING, ArgumentType.STRING, ArgumentType.WSTRING): if not (nr_derefs == 0 or (nr_derefs == 1 and arg.is_out)): nr_derefs -= 1 elif arg.type in (ArgumentType.MAPPED, ArgumentType.CLASS, ArgumentType.FAKE_VOID): if nr_derefs > 0: nr_derefs -= 1 elif arg.type in (ArgumentType.STRUCT, ArgumentType.UNION, ArgumentType.VOID): nr_derefs -= 1 if arg.type in (ArgumentType.MAPPED, ArgumentType.CLASS, ArgumentType.FAKE_VOID): prefix = '' ref = '' needs_copy = _needs_heap_copy(arg) if needs_copy: prefix = 'new ' + fmt_argument_as_cpp_type(spec, arg, plain=True, no_derefs=True) + '(' else: if arg.is_const: prefix = 'const_cast<' + fmt_argument_as_cpp_type(spec, arg, plain=True, no_derefs=True, use_typename=False) + ' *>(' if len(arg.derefs) == 0: ref = '&' else: ref = '*' * nr_derefs suffix = '' if prefix == '' else ')' arg_ref = prefix + ref + fmt_argument_as_name(spec, arg, arg_nr) + suffix args.append(arg_ref) if arg.array is ArrayArgument.ARRAY: array_len_arg_name = fmt_argument_as_name(spec, signature.args[array_len_arg_nr], array_len_arg_nr) args.append('(Py_ssize_t)' + array_len_arg_name) args.append(_gto_name(arg.definition)) if arg.array is not ArrayArgument.ARRAY: args.append('SIP_NULLPTR') elif arg.type is ArgumentType.CAPSULE: args.append('"' + arg.definition.as_cpp + '"') else: if arg.array is not ArrayArgument.ARRAY_SIZE: args.append( '*' * nr_derefs + fmt_argument_as_name(spec, arg, arg_nr)) if arg.array is ArrayArgument.ARRAY: array_len_arg_name = fmt_argument_as_name(spec, signature.args[array_len_arg_nr], array_len_arg_nr) args.append('(Py_ssize_t)' + array_len_arg_name) elif arg.type is ArgumentType.ENUM and arg.definition.fq_cpp_name is not None: args.append(_gto_name(arg.definition)) return ', '.join(args) def _used_includes(sf, used): """ Generate the library header #include directives required by either a class or a module. """ sf.write('\n') for iface_file in used: sf.write_code(iface_file.type_header_code) def _module_api(sf, spec, bindings): """ Generate the API details for a module. """ module = spec.module module_name = module.py_name for klass in spec.classes: if klass.iface_file.module is module: _class_api(sf, spec, klass) if klass.export_derived: sf.write_code(klass.iface_file.type_header_code) _shadow_class_declaration(sf, spec, bindings, klass) for mapped_type in spec.mapped_types: if mapped_type.iface_file.module is module: _mapped_type_api(sf, spec, mapped_type) no_exceptions = True for exception in spec.exceptions: if exception.iface_file.module is module and exception.exception_nr >= 0: if no_exceptions: sf.write( f''' /* The exceptions defined in this module. */ extern PyObject *sipExportedExceptions_{module_name}[]; ''') no_exceptions = False sf.write(f'#define sipException_{exception.iface_file.fq_cpp_name.as_word} sipExportedExceptions_{module_name}[{exception.exception_nr}]\n') _enum_macros(sf, spec) for virtual_error_handler in spec.virtual_error_handlers: if virtual_error_handler.module is module: sf.write(f'\nvoid sipVEH_{module_name}_{virtual_error_handler.name}(sipSimpleWrapper *, sip_gilstate_t);\n') def _imported_module_api(sf, spec, imported_module): """ Generate the API details for an imported module. """ module_name = spec.module.py_name for klass in spec.classes: iface_file = klass.iface_file if iface_file.module is imported_module: if iface_file.needed: gto_name = _gto_name(klass) if iface_file.type is IfaceFileType.NAMESPACE: sf.write(f'\n#if !defined({gto_name})') sf.write(f'\n#define {gto_name} sipImportedTypes_{module_name}_{iface_file.module.py_name}[{iface_file.type_nr}].it_td\n') if iface_file.type is IfaceFileType.NAMESPACE: sf.write('#endif\n') _enum_macros(sf, spec, scope=klass, imported_module=imported_module) for mapped_type in spec.mapped_types: iface_file = mapped_type.iface_file if iface_file.module is imported_module: if iface_file.needed: sf.write(f'\n#define {_gto_name(mapped_type)} sipImportedTypes_{module_name}_{iface_file.module.py_name}[{iface_file.type_nr}].it_td\n') _enum_macros(sf, spec, scope=mapped_type, imported_module=imported_module) for exception in spec.exceptions: iface_file = exception.iface_file if iface_file.module is imported_module and exception.exception_nr >= 0: sf.write(f'\n#define sipException_{iface_file.fq_cpp_name.as_word} sipImportedExceptions_{module_name}_{iface_file.module.py_name}[{exception.exception_nr}].iexc_object\n') _enum_macros(sf, spec, imported_module=imported_module) def _mapped_type_api(sf, spec, mapped_type): """ Generate the API details for a mapped type. """ iface_file = mapped_type.iface_file module_name = spec.module.py_name mapped_type_name = iface_file.fq_cpp_name.as_word sf.write( f''' #define {_gto_name(mapped_type)} sipExportedTypes_{module_name}[{iface_file.type_nr}] extern sipMappedTypeDef sipTypeDef_{module_name}_{mapped_type_name}; ''') _enum_macros(sf, spec, scope=mapped_type) def _class_api(sf, spec, klass): """ Generate the C++ API for a class. """ iface_file = klass.iface_file module_name = spec.module.py_name sf.write('\n') if klass.real_class is None and not klass.is_hidden_namespace: sf.write(f'#define {_gto_name(klass)} sipExportedTypes_{module_name}[{iface_file.type_nr}]\n') _enum_macros(sf, spec, scope=klass) if not klass.external and not klass.is_hidden_namespace: klass_name = iface_file.fq_cpp_name.as_word sf.write(f'\nextern sipClassTypeDef sipTypeDef_{module_name}_{klass_name};\n') def _enum_macros(sf, spec, scope=None, imported_module=None): """ Generate the type macros for enums. """ for enum in spec.enums: if enum.fq_cpp_name is None: continue # Continue unless the scopes match. if scope is not None: if enum.scope is not scope: continue elif enum.scope is not None: continue value = None if imported_module is None: if enum.module is spec.module: value = f'sipExportedTypes_{spec.module.py_name}[{enum.type_nr}]' elif enum.module is imported_module and enum.needed: value = f'sipImportedTypes_{spec.module.py_name}_{enum.module.py_name}[{enum.type_nr}].it_td' if value is not None: sf.write(f'\n#define {_gto_name(enum)} {value}\n') def _shadow_class_declaration(sf, spec, bindings, klass): """ Generate the shadow class declaration. """ klass_name = klass.iface_file.fq_cpp_name.as_word sf.write( f''' class sip{klass_name} : public {_scoped_class_name(spec, klass)} {{ public: ''') # Define a shadow class for any protected classes we have. for protected_klass in spec.classes: if not protected_klass.is_protected: continue # See if the class defining the class is in our class hierachy. for mro in klass.mro: if mro is protected_klass.scope: break else: continue protected_klass_base_name = protected_klass.iface_file.fq_cpp_name.base_name sf.write( f''' class sip{protected_klass_base_name} : public {protected_klass_base_name} {{ public: ''') _protected_enums(sf, spec, protected_klass) sf.write(' };\n\n') # The constructor declarations. for ctor in _unique_class_ctors(spec, klass): args = fmt_signature_as_cpp_declaration(spec, ctor.cpp_signature, scope=klass.iface_file) throw_specifier = _throw_specifier(bindings, ctor.throw_args) sf.write(f' sip{klass_name}({args}){throw_specifier};\n') # The destructor. if klass.dtor is not AccessSpecifier.PRIVATE: virtual_s = 'virtual ' if len(klass.virtual_overloads) != 0 else '' throw_specifier = _throw_specifier(bindings, klass.dtor_throw_args) sf.write(f' {virtual_s}~sip{klass_name}(){throw_specifier};\n') # The metacall methods if required. if (_pyqt5(spec) or _pyqt6(spec)) and klass.is_qobject: sf.write( ''' int qt_metacall(QMetaObject::Call, int, void **) SIP_OVERRIDE; void *qt_metacast(const char *) SIP_OVERRIDE; ''') if not klass.pyqt_no_qmetaobject: sf.write(' const QMetaObject *metaObject() const SIP_OVERRIDE;\n') # The exposure of protected enums. _protected_enums(sf, spec, klass) # The wrapper around each protected member function. _protected_declarations(sf, spec, klass) # The catcher around each virtual function in the hierarchy. for virt_nr, virtual_overload in enumerate(_unique_class_virtual_overloads(spec, klass)): if virt_nr == 0: sf.write( ''' /* * There is a protected method for every virtual method visible from * this class. */ protected: ''') sf.write(' ') _overload_decl(sf, spec, bindings, klass, virtual_overload.overload) sf.write(';\n') sf.write( ''' public: sipSimpleWrapper *sipPySelf; ''') # The private declarations. sf.write( f''' private: sip{klass_name}(const sip{klass_name} &); sip{klass_name} &operator = (const sip{klass_name} &); ''') nr_virtual_overloads = _count_virtual_overloads(spec, klass) if nr_virtual_overloads > 0: sf.write(f'\n char sipPyMethods[{nr_virtual_overloads}];\n') sf.write('};\n') def _overload_decl(sf, spec, bindings, klass, overload): """ Generate the C++ declaration for an overload. """ cpp_signature = overload.cpp_signature # Counter the handling of protected enums by the argument formatter. protection_state = set() _remove_protections(cpp_signature, protection_state) result_type = fmt_argument_as_cpp_type(spec, cpp_signature.result, scope=klass.iface_file) args = [] for arg in cpp_signature.args: args.append( fmt_argument_as_cpp_type(spec, arg, scope=klass.iface_file)) args = ', '.join(args) const_s = ' const' if overload.is_const else '' throw_specifier = _throw_specifier(bindings, overload.throw_args) sf.write(f'{result_type} {_overload_cpp_name(overload)}({args}){const_s}{throw_specifier} SIP_OVERRIDE') _restore_protections(protection_state) def _call_args(sf, spec, cpp_signature, py_signature): """ Generate typed arguments for a call. """ for arg_nr, arg in enumerate(cpp_signature.args): if arg_nr > 0: sf.write(', ') # See if the argument needs dereferencing or it's address taking. indirection = '' nr_derefs = len(arg.derefs) if arg.type in (ArgumentType.ASCII_STRING, ArgumentType.LATIN1_STRING, ArgumentType.UTF8_STRING, ArgumentType.SSTRING, ArgumentType.USTRING, ArgumentType.STRING, ArgumentType.WSTRING): if nr_derefs > (0 if arg.is_out else 1) and not arg.is_reference: indirection = '&' elif arg.type in (ArgumentType.CLASS, ArgumentType.MAPPED): if nr_derefs == 2: indirection = '&' elif nr_derefs == 0: indirection = '*' elif arg.type in (ArgumentType.STRUCT, ArgumentType.UNION, ArgumentType.VOID): if nr_derefs == 2: indirection = '&' else: if nr_derefs == 1: indirection = '&' # See if we need to cast a Python void * to the correct C/C++ pointer # type. Note that we assume that the arguments correspond and are just # different types. need_cast = False if py_signature is not cpp_signature and len(py_signature.args) == len(cpp_signature.args): py_arg = py_signature.args[arg_nr] VOID_TYPES = (ArgumentType.VOID, ArgumentType.CAPSULE) if py_arg.type in VOID_TYPES and arg.type not in VOID_TYPES and len(py_arg.derefs) == nr_derefs: need_cast = True arg_name = fmt_argument_as_name(spec, arg, arg_nr) arg_cpp_type_name = fmt_argument_as_cpp_type(spec, arg, plain=True, no_derefs=True) if need_cast: if spec.c_bindings: sf.write(f'({arg_cpp_type_name} *){arg_name}') else: sf.write(f'reinterpret_cast<{arg_cpp_type_name} *>({arg_name})') else: sf.write(indirection) if arg.array is ArrayArgument.ARRAY_SIZE: sf.write(f'({arg_cpp_type_name})') sf.write(arg_name) def _get_named_value_decl(spec, scope, type, name): """ Return the declaration of a named variable to hold a C++ value. """ saved_derefs = type.derefs saved_is_const = type.is_const saved_is_reference = type.is_reference if len(type.derefs) == 0: if type.type in (ArgumentType.CLASS, ArgumentType.MAPPED): type.derefs = [False] else: type.is_const = False type.is_reference = False named_value_decl = fmt_argument_as_cpp_type(spec, type, name=name, scope=scope.iface_file if isinstance(scope, (WrappedClass, MappedType)) else None) type.derefs = saved_derefs type.is_const = saved_is_const type.is_reference = saved_is_reference return named_value_decl def _argument_variable(sf, spec, scope, arg, arg_nr): """ Generate the definition of an argument variable and any supporting variables. """ scope_iface_file = scope.iface_file if isinstance(scope, (WrappedClass, MappedType)) else None arg_name = fmt_argument_as_name(spec, arg, arg_nr) supporting_default_value = ' = 0' if arg.default_value is not None else '' nr_derefs = len(arg.derefs) if arg.is_in and arg.default_value is not None and arg.type in (ArgumentType.CLASS, ArgumentType.MAPPED) and (nr_derefs == 0 or arg.is_reference): arg_cpp_type = fmt_argument_as_cpp_type(spec, arg, scope=scope_iface_file) # Generate something to hold the default value as it cannot be assigned # straight away. expression = fmt_value_list_as_cpp_expression(spec, arg.default_value) sf.write(f' {arg_cpp_type} {arg_name}def = {expression};\n') # Adjust the type so we have the type that will really handle it. saved_derefs = arg.derefs saved_type = arg.type saved_is_reference = arg.is_reference saved_is_const = arg.is_const if arg.type in (ArgumentType.ASCII_STRING, ArgumentType.LATIN1_STRING, ArgumentType.UTF8_STRING, ArgumentType.SSTRING, ArgumentType.USTRING, ArgumentType.STRING, ArgumentType.WSTRING): if not arg.is_reference: if nr_derefs == 2: arg.derefs = arg.derefs[0:1] elif nr_derefs == 1 and arg.is_out: arg.derefs = [] elif arg.type in (ArgumentType.CLASS, ArgumentType.MAPPED, ArgumentType.STRUCT, ArgumentType.UNION, ArgumentType.VOID): arg.derefs = [arg.derefs[0] if len(arg.derefs) != 0 else False] else: arg.derefs = [] # Array sizes are always Py_ssize_t. if arg.array is ArrayArgument.ARRAY_SIZE: arg.type = ArgumentType.SSIZE arg.is_reference = False if len(arg.derefs) == 0: arg.is_const = False modified_arg_cpp_type = fmt_argument_as_cpp_type(spec, arg, scope=scope_iface_file) sf.write(f' {modified_arg_cpp_type} {arg_name}') # Restore the argument to its original state. arg.derefs = saved_derefs arg.type = saved_type arg.is_reference = saved_is_reference arg.is_const = saved_is_const # Generate any default value. if arg.is_in and arg.default_value is not None: sf.write(' = ') if arg.type in (ArgumentType.CLASS, ArgumentType.MAPPED) and (nr_derefs == 0 or arg.is_reference): sf.write(f'&{arg_name}def') else: sf.write(fmt_value_list_as_cpp_expression(spec, arg.default_value)) sf.write(';\n') # Some types have supporting variables. if arg.is_in: if arg.get_wrapper: sf.write(f' PyObject *{arg_name}Wrapper{supporting_default_value};\n') elif arg.key is not None: sf.write(f' PyObject *{arg_name}Keep{supporting_default_value};\n') if arg.type is ArgumentType.CLASS: if arg.array is ArrayArgument.ARRAY and _abi_supports_array(spec): if _abi_supports_array(spec): sf.write(f' int {arg_name}IsTemp = 0;\n') else: if arg.definition.convert_to_type_code is not None and not arg.is_constrained: sf.write(f' int {arg_name}State = 0;\n') if _type_needs_user_state(arg): sf.write(f' void *{arg_name}UserState = SIP_NULLPTR;\n') elif arg.type is ArgumentType.MAPPED: if not arg.definition.no_release and not arg.is_constrained: sf.write(f' int {arg_name}State = 0;\n') if _type_needs_user_state(arg): sf.write(f' void *{arg_name}UserState = SIP_NULLPTR;\n') elif arg.type in (ArgumentType.ASCII_STRING, ArgumentType.LATIN1_STRING, ArgumentType.UTF8_STRING): if arg.key is None and nr_derefs == 1: sf.write(f' PyObject *{arg_name}Keep{supporting_default_value};\n') def _type_definition(sf, spec, bindings, klass, py_debug): """ Generate the type structure that contains all the information needed by the meta-type. A sub-set of this is used to extend namespaces. """ module = spec.module klass_name = klass.iface_file.fq_cpp_name.as_word # The super-types table. if len(klass.superclasses) != 0: encoded_types = [] for superclass in klass.superclasses: last = superclass is klass.superclasses[-1] encoded_types.append(_encoded_type(module, superclass, last=last)) encoded_types = ', '.join(encoded_types) sf.write( f''' /* Define this type's super-types. */ static sipEncodedTypeDef supers_{klass_name}[] = {{{encoded_types}}}; ''') # The slots table. is_slots = False for member in klass.members: if member.py_slot is None: continue if not is_slots: sf.write( f''' /* Define this type's Python slots. */ static sipPySlotDef slots_{klass_name}[] = {{ ''') is_slots = True slot_name = _get_slot_name(member.py_slot) member_name = member.py_name sf.write(f' {{(void *)slot_{klass_name}_{member_name}, {slot_name}}},\n') if is_slots: sf.write(' {0, (sipPySlotType)0}\n};\n') # The attributes tables. nr_methods = _class_method_table(sf, spec, bindings, klass) if spec.abi_version >= (13, 0): nr_enum_members = -1 else: nr_enum_members = _enum_member_table(sf, spec, scope=klass) # The property and variable handlers. nr_variables = 0 if klass.has_variable_handlers: for variable in spec.variables: if variable.scope is klass and variable.needs_handler: nr_variables += 1 _variable_getter(sf, spec, variable) if _can_set_variable(variable): _variable_setter(sf, spec, variable) # Generate any property docstrings. for prop in klass.properties: nr_variables += 1 if prop.docstring is not None: docstring = _docstring_text(prop.docstring) sf.write(f'\nPyDoc_STRVAR(doc_{klass_name}_{prop.name}, "{docstring}");\n') # The variables table. if nr_variables != 0: sf.write(f'\nsipVariableDef variables_{klass_name}[] = {{\n') for prop in klass.properties: fields = ['PropertyVariable', _cached_name_ref(prop.name)] getter_nr = find_method(klass, prop.getter).member_nr fields.append(f'&methods_{klass_name}[{getter_nr}]') if prop.setter is None: fields.append('SIP_NULLPTR') else: setter_nr = find_method(klass, prop.setter).member_nr fields.append(f'&methods_{klass_name}[{setter_nr}]') # We don't support a deleter yet. fields.append('SIP_NULLPTR') if prop.docstring is None: fields.append('SIP_NULLPTR') else: fields.append(f'doc_{klass_name}_{prop.name}') fields = ', '.join(fields) sf.write(f' {{{fields}}},\n') if klass.has_variable_handlers: for variable in spec.variables: if variable.scope is klass and variable.needs_handler: variable_name = variable.fq_cpp_name.as_word fields = [] fields.append('ClassVariable' if variable.is_static else 'InstanceVariable') fields.append(_cached_name_ref(variable.py_name)) fields.append('(PyMethodDef *)varget_' + variable_name) if _can_set_variable(variable): fields.append('(PyMethodDef *)varset_' + variable_name) else: fields.append('SIP_NULLPTR') fields.append('SIP_NULLPTR') fields.append('SIP_NULLPTR') fields = ', '.join(fields) sf.write(f' {{{fields}}},\n') if nr_variables != 0: sf.write('};\n') # Generate each instance table. is_inst_class = _class_instances(sf, spec, scope=klass) is_inst_voidp = _void_pointer_instances(sf, spec, scope=klass) is_inst_char = _char_instances(sf, spec, scope=klass) is_inst_string = _string_instances(sf, spec, scope=klass) is_inst_int = _int_instances(sf, spec, scope=klass) is_inst_long = _long_instances(sf, spec, scope=klass) is_inst_ulong = _unsigned_long_instances(sf, spec, scope=klass) is_inst_longlong = _long_long_instances(sf, spec, scope=klass) is_inst_ulonglong = _unsigned_long_long_instances(sf, spec, scope=klass) is_inst_double = _double_instances(sf, spec, scope=klass) # Generate the docstrings. if _has_class_docstring(bindings, klass): docstring_ref = 'doc_' + klass_name sf.write(f'\nPyDoc_STRVAR({docstring_ref}, "') _class_docstring(sf, spec, bindings, klass) sf.write('");\n') else: docstring_ref = 'SIP_NULLPTR' # Generate any plugin-specific data structures. plugin_ref = 'SIP_NULLPTR' if _pyqt5(spec) or _pyqt6(spec): if _pyqt_class_plugin(sf, spec, bindings, klass): plugin_ref = '&plugin_' + klass_name # The type definition structure itself. base_fields = [] container_fields = [] class_fields = [] if spec.abi_version < (13, 0): base_fields.append('-1') base_fields.append('SIP_NULLPTR') base_fields.append('SIP_NULLPTR') flags = [] if klass.is_abstract: flags.append('SIP_TYPE_ABSTRACT') if klass.subclass_base is not None: flags.append('SIP_TYPE_SCC') if klass.handles_none: flags.append('SIP_TYPE_ALLOW_NONE') if klass.has_nonlazy_method: flags.append('SIP_TYPE_NONLAZY') if module.call_super_init: flags.append('SIP_TYPE_SUPER_INIT') if not py_debug and module.use_limited_api: flags.append('SIP_TYPE_LIMITED_API') flags.append('SIP_TYPE_NAMESPACE' if klass.iface_file.type is IfaceFileType.NAMESPACE else 'SIP_TYPE_CLASS') if len(flags) == 0: flags.append('0') base_fields.append('|'.join(flags)) base_fields.append(_cached_name_ref(klass.iface_file.cpp_name, as_nr=True)) base_fields.append('SIP_NULLPTR') base_fields.append(plugin_ref) container_fields.append(_cached_name_ref(klass.py_name, as_nr=True) if klass.real_class is None else '-1') if klass.real_class is not None: encoded_type = _encoded_type(module, klass.real_class) elif _py_scope(klass.scope) is not None: encoded_type = _encoded_type(module, klass.scope) else: encoded_type = '{0, 0, 1}' container_fields.append(encoded_type) if nr_methods == 0: container_fields.append('0, SIP_NULLPTR') else: container_fields.append(str(nr_methods) + ', methods_' + klass_name) if nr_enum_members == 0: container_fields.append('0, SIP_NULLPTR') elif nr_enum_members > 0: container_fields.append(str(nr_enum_members) + ', enummembers_' + klass_name) if nr_variables == 0: container_fields.append('0, SIP_NULLPTR') else: container_fields.append(str(nr_variables) + ', variables_' + klass_name) instances = [] instances.append( _class_object_ref(is_inst_class, 'typeInstances', klass_name)) instances.append( _class_object_ref(is_inst_voidp, 'voidPtrInstances', klass_name)) instances.append( _class_object_ref(is_inst_char, 'charInstances', klass_name)) instances.append( _class_object_ref(is_inst_string, 'stringInstances', klass_name)) instances.append( _class_object_ref(is_inst_int, 'intInstances', klass_name)) instances.append( _class_object_ref(is_inst_long, 'longInstances', klass_name)) instances.append( _class_object_ref(is_inst_ulong, 'unsignedLongInstances', klass_name)) instances.append( _class_object_ref(is_inst_longlong, 'longLongInstances', klass_name)) instances.append( _class_object_ref(is_inst_ulonglong, 'unsignedLongLongInstances', klass_name)) instances.append( _class_object_ref(is_inst_double, 'doubleInstances', klass_name)) container_fields.append('{' + ', '.join(instances) + '}') class_fields.append(docstring_ref) class_fields.append(_cached_name_ref(klass.metatype, as_nr=True) if klass.metatype is not None else '-1') class_fields.append(_cached_name_ref(klass.supertype, as_nr=True) if klass.supertype is not None else '-1') class_fields.append( _class_object_ref((len(klass.superclasses) != 0), 'supers', klass_name)) class_fields.append(_class_object_ref(is_slots, 'slots', klass_name)) class_fields.append( _class_object_ref(klass.can_create, 'init_type', klass_name)) class_fields.append( _class_object_ref((klass.gc_traverse_code is not None), 'traverse', klass_name)) class_fields.append( _class_object_ref((klass.gc_clear_code is not None), 'clear', klass_name)) class_fields.append( _class_object_ref((klass.bi_get_buffer_code is not None), 'getbuffer', klass_name)) class_fields.append( _class_object_ref((klass.bi_release_buffer_code is not None), 'releasebuffer', klass_name)) class_fields.append( _class_object_ref(_need_dealloc(spec, bindings, klass), 'dealloc', klass_name)) class_fields.append( _class_object_ref((spec.c_bindings or klass.needs_copy_helper), 'assign', klass_name)) class_fields.append( _class_object_ref((spec.c_bindings or klass.needs_array_helper), 'array', klass_name)) class_fields.append( _class_object_ref((spec.c_bindings or klass.needs_copy_helper), 'copy', klass_name)) class_fields.append( _class_object_ref((not spec.c_bindings and klass.iface_file.type is not IfaceFileType.NAMESPACE), 'release', klass_name)) class_fields.append( _class_object_ref((len(klass.superclasses) != 0), 'cast', klass_name)) class_fields.append( _class_object_ref((klass.convert_to_type_code is not None and klass.iface_file.type is not IfaceFileType.NAMESPACE), 'convertTo', klass_name)) class_fields.append( _class_object_ref((klass.convert_from_type_code is not None and klass.iface_file.type is not IfaceFileType.NAMESPACE), 'convertFrom', klass_name)) class_fields.append('SIP_NULLPTR') class_fields.append( _class_object_ref((klass.pickle_code is not None), 'pickle', klass_name)) class_fields.append( _class_object_ref((klass.finalisation_code is not None), 'final', klass_name)) class_fields.append(_class_object_ref(klass.mixin, 'mixin', klass_name)) if _abi_supports_array(spec): class_fields.append( _class_object_ref((spec.c_bindings or klass.needs_array_helper), 'array_delete', klass_name)) if klass.can_create: class_fields.append(f'sizeof ({_scoped_class_name(spec, klass)})') else: class_fields.append('0') base_fields = ',\n '.join(base_fields) container_fields = ',\n '.join(container_fields) class_fields = ',\n '.join(class_fields) sf.write( f''' sipClassTypeDef sipTypeDef_{module.py_name}_{klass_name} = {{ {{ {base_fields}, }}, {{ {container_fields}, }}, {class_fields}, }}; ''') def _class_object_ref(test, object_name, klass_name): """ Return an appropriate reference to a class-specific object. """ return object_name + '_' + klass_name if test else 'SIP_NULLPTR' def _can_set_variable(variable): """ Return True if a variable can be set. """ if variable.no_setter: return False if len(variable.type.derefs) == 0 and variable.type.is_const: return False return True def _pyqt_emitters(sf, spec, klass): """ Generate the PyQt emitters for a class. """ klass_name = klass.iface_file.fq_cpp_name.as_word scope_s = _scoped_class_name(spec, klass) klass_name_ref = _cached_name_ref(klass.py_name) for member in klass.members: in_emitter = False for overload in klass.overloads: if not (overload.common is member and overload.pyqt_method_specifier is PyQtMethodSpecifier.SIGNAL and _has_optional_args(overload)): continue if not in_emitter: in_emitter = True sf.write('\n\n') if not spec.c_bindings: sf.write(f'extern "C" {{static int emit_{klass_name}_{overload.cpp_name}(void *, PyObject *);}}\n\n') sf.write( f'''static int emit_{klass_name}_{overload.cpp_name}(void *sipCppV, PyObject *sipArgs) {{ PyObject *sipParseErr = SIP_NULLPTR; {scope_s} *sipCpp = reinterpret_cast<{scope_s} *>(sipCppV); ''') # Generate the code that parses the args and emits the appropriate # overloaded signal. sf.write('\n {\n') _arg_parser(sf, spec, klass, overload.py_signature) sf.write( f''' {{ Py_BEGIN_ALLOW_THREADS sipCpp->{overload.cpp_name}(''') _call_args(sf, spec, overload.cpp_signature, overload.py_signature) sf.write('''); Py_END_ALLOW_THREADS ''') _delete_temporaries(sf, spec, overload.py_signature) sf.write( ''' return 0; } } ''') if in_emitter: member_name_ref = _cached_name_ref(member.py_name) sf.write( f''' sipNoMethod(sipParseErr, {klass_name_ref}, {member_name_ref}, SIP_NULLPTR); return -1; }} ''') def _pyqt_signal_table_entry(sf, spec, bindings, klass, signal, member_nr): """ Generate an entry in the PyQt signal table. """ klass_name = klass.iface_file.fq_cpp_name.as_word stripped = False signature_state = {} args = [] for arg in signal.cpp_signature.args: # Do some signal argument normalisation so that Qt doesn't have to. if arg.is_const and (arg.is_reference or len(arg.derefs) == 0): signature_state[arg] = arg.is_reference arg.is_const = False arg.is_reference = False if arg.scopes_stripped != 0: strip = arg.scopes_stripped stripped = True else: strip = STRIP_GLOBAL args.append( fmt_argument_as_cpp_type(spec, arg, scope=klass.iface_file, strip=strip)) # Note the lack of a separating space. args = ','.join(args) sf.write(f' {{"{signal.cpp_name}({args})') # If a scope was stripped then append an unstripped version which can be # parsed by PyQt. if stripped: args = [] for arg in signal.cpp_signature.args: args.append( fmt_argument_as_cpp_type(spec, arg, scope=klass.iface_file, strip=STRIP_GLOBAL)) # Note the lack of a separating space. args = ','.join(args) sf.write(f'|({args})') sf.write('", ') # Restore the signature state. for arg, is_reference in signature_state.items(): arg.is_const = True arg.is_reference = is_reference if bindings.docstrings: sf.write('"') if signal.docstring is not None: if signal.docstring.signature is DocstringSignature.PREPENDED: _overload_auto_docstring(sf, spec, signal) sf.write('\\n') sf.write(_docstring_text(signal.docstring)) if signal.docstring.signature is DocstringSignature.APPENDED: sf.write('\\n') _overload_auto_docstring(sf, spec, signal) else: sf.write('\\1') _overload_auto_docstring(sf, spec, signal) sf.write('", ') else: sf.write('SIP_NULLPTR, ') sf.write(f'&methods_{klass_name}[{member_nr}], ' if member_nr >= 0 else 'SIP_NULLPTR, ') sf.write(f'emit_{klass_name}_{signal.cpp_name}' if _has_optional_args(signal) else 'SIP_NULLPTR') sf.write('},\n') def _get_slot_name(slot_type): """ Return the sip module's string equivalent of a slot. """ return slot_type.name.lower() + '_slot' def _type_init(sf, spec, bindings, klass): """ Generate the initialisation function for the type. """ klass_name = klass.iface_file.fq_cpp_name.as_word # See if we need to name the self and owner arguments so that we can avoid # a compiler warning about an unused argument. need_self = (spec.c_bindings or klass.has_shadow) need_owner = spec.c_bindings for ctor in klass.ctors: if _is_used_in_code(ctor.method_code, 'sipSelf'): need_self = True if ctor.transfer is Transfer.TRANSFER: need_owner = True else: for arg in ctor.py_signature.args: if not arg.is_in: continue if arg.key is not None: need_self = True if arg.transfer is Transfer.TRANSFER: need_self = True if arg.transfer is Transfer.TRANSFER_THIS: need_owner = True sf.write('\n\n') if not spec.c_bindings: sf.write(f'extern "C" {{static void *init_type_{klass_name}(sipSimpleWrapper *, PyObject *, PyObject *, PyObject **, PyObject **, PyObject **);}}\n') sip_self = 'sipSelf' if need_self else '' sip_owner = 'sipOwner' if need_owner else '' sip_cpp_type = 'sip' + klass_name if klass.has_shadow else _scoped_class_name(spec, klass) sf.write( f'''static void *init_type_{klass_name}(sipSimpleWrapper *{sip_self}, PyObject *sipArgs, PyObject *sipKwds, PyObject **sipUnused, PyObject **{sip_owner}, PyObject **sipParseErr) {{ {sip_cpp_type} *sipCpp = SIP_NULLPTR; ''') if bindings.tracing: sf.write(f'\n sipTrace(SIP_TRACE_INITS, "init_type_{klass_name}()\\n");\n') # Generate the code that parses the Python arguments and calls the correct # constructor. for ctor in klass.ctors: if ctor.access_specifier is AccessSpecifier.PRIVATE: continue sf.write('\n {\n') if ctor.method_code is not None: error_flag = _need_error_flag(ctor.method_code) old_error_flag = _need_old_error_flag(ctor.method_code) else: error_flag = old_error_flag = False _arg_parser(sf, spec, klass, ctor.py_signature, ctor=ctor) _constructor_call(sf, spec, bindings, klass, ctor, error_flag, old_error_flag) sf.write(' }\n') sf.write( ''' return SIP_NULLPTR; } ''') def _count_virtual_overloads(spec, klass): """ Return the number of virtual members in a class. """ return len(list(_unique_class_virtual_overloads(spec, klass))) def _handling_exceptions(bindings, throw_args): """ Return True if exceptions from a callable are being handled. """ # Handle any exceptions if there was no throw specifier, or a non-empty # throw specifier. return bindings.exceptions and (throw_args is None or throw_args.arguments is not None) def _try(sf, bindings, throw_args): """ Generate the try block for a call. """ # Generate the block if there was no throw specifier, or a non-empty throw # specifier. if _handling_exceptions(bindings, throw_args): sf.write( ''' try { ''') def _catch(sf, spec, bindings, py_signature, throw_args, release_gil): """ Generate the catch blocks for a call. """ if _handling_exceptions(bindings, throw_args): use_handler = _abi_has_next_exception_handler(spec) sf.write(' }\n') if not use_handler: if throw_args is not None: for exception in throw_args.arguments: _catch_block(sf, spec, exception, py_signature=py_signature, release_gil=release_gil) elif spec.module.default_exception is not None: _catch_block(sf, spec, spec.module.default_exception, py_signature=py_signature, release_gil=release_gil) sf.write( ''' catch (...) { ''') if release_gil: sf.write( ''' Py_BLOCK_THREADS ''') _delete_outs(sf, spec, py_signature) _delete_temporaries(sf, spec, py_signature) if use_handler: sf.write( ''' void *sipExcState = SIP_NULLPTR; sipExceptionHandler sipExcHandler; std::exception_ptr sipExcPtr = std::current_exception(); while ((sipExcHandler = sipNextExceptionHandler(&sipExcState)) != SIP_NULLPTR) if (sipExcHandler(sipExcPtr)) return SIP_NULLPTR; ''') sf.write( ''' sipRaiseUnknownException(); return SIP_NULLPTR; } ''') def _catch_block(sf, spec, exception, py_signature=None, release_gil=False): """ Generate a single catch block. """ exception_fq_cpp_name = exception.iface_file.fq_cpp_name # The global scope is stripped from the exception name to be consistent # with older versions of SIP. exception_cpp_stripped = exception_fq_cpp_name.cpp_stripped(STRIP_GLOBAL) sip_exception_ref = 'sipExceptionRef' if exception.class_exception is not None or _is_used_in_code(exception.raise_code, 'sipExceptionRef') else '' sf.write( f''' catch ({exception_cpp_stripped} &{sip_exception_ref}) {{ ''') if release_gil: sf.write( ''' Py_BLOCK_THREADS ''') if py_signature is not None: _delete_outs(sf, spec, py_signature) _delete_temporaries(sf, spec, py_signature) result = 'SIP_NULLPTR' else: result = 'true' # See if the exception is a wrapped class. if exception.class_exception is not None: exception_cpp = exception_fq_cpp_name.as_cpp sf.write( f''' /* Hope that there is a valid copy ctor. */ {exception_cpp} *sipExceptionCopy = new {exception_cpp}(sipExceptionRef); sipRaiseTypeException({_gto_name(exception)}, sipExceptionCopy); ''') else: sf.write_code(exception.raise_code) sf.write( f''' return {result}; }} ''') def _throw_specifier(bindings, throw_args): """ Return a throw specifier. """ return ' noexcept' if bindings.exceptions and throw_args is not None and throw_args.arguments is None else '' def _constructor_call(sf, spec, bindings, klass, ctor, error_flag, old_error_flag): """ Generate a single constructor call. """ klass_name = klass.iface_file.fq_cpp_name.as_word scope_s = _scoped_class_name(spec, klass) sf.write(' {\n') if ctor.premethod_code is not None: sf.write('\n') sf.write_code(ctor.premethod_code) sf.write('\n') if error_flag: sf.write(' sipErrorState sipError = sipErrorNone;\n\n') elif old_error_flag: sf.write(' int sipIsErr = 0;\n\n') if ctor.deprecated: # Note that any temporaries will leak if an exception is raised. sf.write( f''' if (sipDeprecated({_cached_name_ref(klass.py_name)}, SIP_NULLPTR) < 0) return SIP_NULLPTR; ''') # Call any pre-hook. if ctor.prehook is not None: sf.write(f' sipCallHook("{ctor.prehook}");\n\n') if ctor.method_code is not None: sf.write_code(ctor.method_code) elif spec.c_bindings: sf.write(f' sipCpp = sipMalloc(sizeof ({scope_s}));\n') else: release_gil = _release_gil(ctor.gil_action, bindings) if ctor.raises_py_exception: sf.write(' PyErr_Clear();\n\n') if release_gil: sf.write(' Py_BEGIN_ALLOW_THREADS\n') _try(sf, bindings, ctor.throw_args) klass_type = 'sip' + klass_name if klass.has_shadow else scope_s sf.write(f' sipCpp = new {klass_type}(') if ctor.is_cast: # We have to fiddle the type to generate the correct code. arg0 = ctor.py_signature.args[0] saved_definition = arg0.definition arg0.definition = klass cast_call = fmt_argument_as_cpp_type(spec, arg0) arg0.definition = saved_definition sf.write(f'a0->operator {cast_call}()') else: _call_args(sf, spec, ctor.cpp_signature, ctor.py_signature) sf.write(');\n') _catch(sf, spec, bindings, ctor.py_signature, ctor.throw_args, release_gil) if release_gil: sf.write(' Py_END_ALLOW_THREADS\n') # This is a bit of a hack to say we want the result transferred. We # don't simply call sipTransferTo() because the wrapper object hasn't # been fully initialised yet. if ctor.transfer is Transfer.TRANSFER: sf.write('\n *sipOwner = Py_None;\n') # Handle any /KeepReference/ arguments. for arg_nr, arg in enumerate(ctor.py_signature.args): if not arg.is_in: continue if arg.key is not None: arg_name = fmt_argument_as_name(spec, arg, arg_nr) suffix = 'Keep' if (arg.type in (ArgumentType.ASCII_STRING, ArgumentType.LATIN1_STRING, ArgumentType.UTF8_STRING) and len(arg.derefs) == 1) or not arg.get_wrapper else 'Wrapper' sf.write(f'\n sipKeepReference((PyObject *)sipSelf, {arg.key}, {arg_name}{suffix});\n') _gc_ellipsis(sf, ctor.py_signature) _delete_temporaries(sf, spec, ctor.py_signature) sf.write('\n') if ctor.raises_py_exception: sf.write( ''' if (PyErr_Occurred()) { delete sipCpp; return SIP_NULLPTR; } ''') if error_flag: sf.write(' if (sipError == sipErrorNone)\n') if klass.has_shadow or ctor.posthook is not None: sf.write(' {\n') if klass.has_shadow: sf.write(' sipCpp->sipPySelf = sipSelf;\n\n') # Call any post-hook. if ctor.posthook is not None: sf.write(f' sipCallHook("{ctor.posthook}");\n\n') sf.write(' return sipCpp;\n') if klass.has_shadow or ctor.posthook is not None: sf.write(' }\n') sf.write( ''' if (sipUnused) { Py_XDECREF(*sipUnused); } sipAddException(sipError, sipParseErr); if (sipError == sipErrorFail) return SIP_NULLPTR; ''') else: if old_error_flag: sf.write( ''' if (sipIsErr) { if (sipUnused) { Py_XDECREF(*sipUnused); } sipAddException(sipErrorFail, sipParseErr); return SIP_NULLPTR; } ''') if klass.has_shadow: sf.write(' sipCpp->sipPySelf = sipSelf;\n\n') # Call any post-hook. if ctor.posthook is not None: sf.write(f' sipCallHook("{ctor.posthook}");\n\n') sf.write(' return sipCpp;\n') sf.write(' }\n') def _skip_overload(overload, member, klass, scope, want_local=True): """ See if a member overload should be skipped. """ # Skip if it's not the right name. if overload.common is not member: return True # Skip if it's a signal. if overload.pyqt_method_specifier is PyQtMethodSpecifier.SIGNAL: return True # Skip if it's a private abstract. if overload.is_abstract and overload.access_specifier is AccessSpecifier.PRIVATE: return True # If we are disallowing them, skip if it's not in the current class unless # it is protected. if want_local and overload.access_specifier is not AccessSpecifier.PROTECTED and klass is not scope: return True return False def _member_function(sf, spec, bindings, klass, member, original_klass): """ Generate a class member function. """ # Check that there is at least one overload that needs to be handled. See # if we can avoid naming the "self" argument (and suppress a compiler # warning). See if we need to remember if "self" was explicitly passed as # an argument. See if we need to handle keyword arguments. need_method = need_self = need_args = need_selfarg = need_orig_self = False for overload in original_klass.overloads: # Skip protected methods if we don't have the means to handle them. if overload.access_specifier is AccessSpecifier.PROTECTED and not klass.has_shadow: continue if not _skip_overload(overload, member, klass, original_klass): need_method = True if overload.access_specifier is not AccessSpecifier.PRIVATE: need_args = True if spec.abi_version >= (13, 0) or not overload.is_static: need_self = True if overload.is_abstract: need_orig_self = True elif overload.is_virtual or overload.is_virtual_reimplementation or _is_used_in_code(overload.method_code, 'sipSelfWasArg'): need_selfarg = True # Handle the trivial case. if not need_method: return klass_name = klass.iface_file.fq_cpp_name.as_word member_py_name = member.py_name.name sf.write('\n\n') # Generate the docstrings. if _has_member_docstring(bindings, member, original_klass.overloads): sf.write(f'PyDoc_STRVAR(doc_{klass_name}_{member_py_name}, "') has_auto_docstring = _member_docstring(sf, spec, bindings, member, original_klass.overloads, is_method=not klass.is_hidden_namespace) sf.write('");\n\n') else: has_auto_docstring = False if member.no_arg_parser or member.allow_keyword_args: arg3_type = ', PyObject *' arg3_decl = ', PyObject *sipKwds' else: arg3_type = '' arg3_decl = '' sip_self = 'sipSelf' if need_self else '' sip_args = 'sipArgs' if need_args else '' if not spec.c_bindings: sf.write(f'extern "C" {{static PyObject *meth_{klass_name}_{member_py_name}(PyObject *, PyObject *{arg3_type});}}\n') sf.write(f'static PyObject *meth_{klass_name}_{member_py_name}(PyObject *{sip_self}, PyObject *{sip_args}{arg3_decl})\n{{\n') if bindings.tracing: sf.write(f' sipTrace(SIP_TRACE_METHODS, "meth_{klass_name}_{member_py_name}()\\n");\n\n') if not member.no_arg_parser: if need_args: sf.write(' PyObject *sipParseErr = SIP_NULLPTR;\n') if need_selfarg: # This determines if we call the explicitly scoped version or the # unscoped version (which will then go via the vtable). # # - If the call was unbound and self was passed as the first # argument (ie. Foo.meth(self)) then we always want to call the # explicitly scoped version. # # - If the call was bound then we only call the unscoped version in # case there is a C++ sub-class reimplementation that Python # knows nothing about. Otherwise, if the call was invoked by # super() within a Python reimplementation then the Python # reimplementation would be called recursively. # # In addition, if the type is a derived class then we know that # there can't be a C++ sub-class that we don't know about so we can # avoid the vtable. # # Note that we would like to rename 'sipSelfWasArg' to # 'sipExplicitScope' but it is part of the public API. if spec.abi_version >= (13, 0): sipself_test = f'!PyObject_TypeCheck(sipSelf, sipTypeAsPyTypeObject({_gto_name(klass)}))' else: sipself_test = '!sipSelf' sf.write(f' bool sipSelfWasArg = ({sipself_test} || sipIsDerivedClass((sipSimpleWrapper *)sipSelf));\n') if need_orig_self: # This is similar to the above but for abstract methods. We allow # the (potential) recursion because it means that the concrete # implementation can be put in a mixin and it will all work. sf.write(' PyObject *sipOrigSelf = sipSelf;\n') for overload in original_klass.overloads: # If we are handling one variant then we must handle them all. if _skip_overload(overload, member, klass, original_klass, want_local=False): continue if overload.access_specifier is AccessSpecifier.PRIVATE: continue if member.no_arg_parser: sf.write_code(overload.method_code) break _function_body(sf, spec, bindings, klass, overload, original_klass=original_klass) if not member.no_arg_parser: sip_parse_err = 'sipParseErr' if need_args else 'SIP_NULLPTR' klass_py_name_ref = _cached_name_ref(klass.py_name) member_py_name_ref = _cached_name_ref(member.py_name) docstring_ref = f'doc_{klass_name}_{member_py_name}' if has_auto_docstring else 'SIP_NULLPTR' sf.write( f''' sipNoMethod({sip_parse_err}, {klass_py_name_ref}, {member_py_name_ref}, {docstring_ref}); return SIP_NULLPTR; ''') sf.write('}\n') def _function_body(sf, spec, bindings, scope, overload, original_klass=None, dereferenced=True): """ Generate the function calls for a particular overload. """ if scope is None: original_scope = None elif isinstance(scope, WrappedClass): # If there was no original class (ie. where a virtual was first # defined) then use this class, if original_klass is None: original_klass = scope original_scope = original_klass else: original_scope = scope py_signature = overload.py_signature sf.write('\n {\n') # In case we have to fiddle with it. py_signature_adjusted = False if is_number_slot(overload.common.py_slot): # Number slots must have two arguments because we parse them slightly # differently. if len(py_signature.args) == 1: py_signature.args.append(py_signature.args[0]) # Insert self in the right place. py_signature.args[0] = Argument(ArgumentType.CLASS, is_in=True, is_reference=True, definition=original_klass) py_signature_adjusted = True _arg_parser(sf, spec, scope, py_signature, overload=overload) elif not is_int_arg_slot(overload.common.py_slot) and not is_zero_arg_slot(overload.common.py_slot): _arg_parser(sf, spec, scope, py_signature, overload=overload) _function_call(sf, spec, bindings, scope, overload, dereferenced, original_scope) sf.write(' }\n') if py_signature_adjusted: del overload.py_signature.args[0] def _handle_result(sf, spec, overload, is_new_instance, result_size_arg_nr, action): """ Generate the code to handle the result of a call to a member function. """ result = overload.py_signature.result if result.type is ArgumentType.VOID and len(result.derefs) == 0: result = None # See if we are returning 0, 1 or more values. nr_return_values = 0 if result is not None: only_out_arg_nr = -1 nr_return_values += 1 has_owner = False for arg_nr, arg in enumerate(overload.py_signature.args): if arg.is_out: only_out_arg_nr = arg_nr nr_return_values += 1 if arg.transfer is Transfer.TRANSFER_THIS: has_owner = True # Handle the trivial case. if nr_return_values == 0: sf.write( f''' Py_INCREF(Py_None); {action} Py_None; ''') return # Handle results that are classes or mapped types separately. if result is not None and result.type in (ArgumentType.CLASS, ArgumentType.MAPPED): result_gto_name = _gto_name(result.definition) if overload.transfer is Transfer.TRANSFER_BACK: result_owner = 'Py_None' elif overload.transfer is Transfer.TRANSFER: result_owner = 'sipSelf' else: result_owner = 'SIP_NULLPTR' sip_res = _const_cast(spec, result, 'sipRes') if is_new_instance or overload.factory: this_action = action if nr_return_values == 1 else 'PyObject *sipResObj =' owner = '(PyObject *)sipOwner' if has_owner and overload.factory else result_owner sf.write(f' {this_action} sipConvertFromNewType({sip_res}, {result_gto_name}, {owner});\n') # Shortcut if this is the only value returned. if nr_return_values == 1: return else: need_xfer = overload.transfer is Transfer.TRANSFER and overload.is_static this_action = 'PyObject *sipResObj =' if nr_return_values > 1 or need_xfer else action owner = 'SIP_NULLPTR' if need_xfer else result_owner sf.write(f' {this_action} sipConvertFromType({sip_res}, {result_gto_name}, {owner});\n') # Transferring the result of a static overload needs an explicit # call to sipTransferTo(). if need_xfer: sf.write('\n sipTransferTo(sipResObj, Py_None);\n') # Shortcut if this is the only value returned. if nr_return_values == 1: if need_xfer: sf.write('\n return sipResObj;\n') return # If there are multiple values then build a tuple. if nr_return_values > 1: build_result_args = ['0'] # Build the format string. format_s = '' if result is not None: format_s += 'R' if result.type in (ArgumentType.CLASS, ArgumentType.MAPPED) else _get_build_result_format(result) for arg in overload.py_signature.args: if arg.is_out: format_s += _get_build_result_format(arg) build_result_args.append('"(' + format_s + ')"') # Pass the values for conversion. if result is not None: build_result_args.append('sipResObj' if result.type in (ArgumentType.CLASS, ArgumentType.MAPPED) else 'sipRes') if result.type is ArgumentType.ENUM and result.definition.fq_cpp_name is not None: build_result_args.append(_gto_name(result.definition)) for arg_nr, arg in enumerate(overload.py_signature.args): if arg.is_out: build_result_args.append(fmt_argument_as_name(spec, arg, arg_nr)) if arg.type in (ArgumentType.CLASS, ArgumentType.MAPPED): build_result_args.append(_gto_name(arg.definition)) transfer = 'Py_None' if arg.transfer is Transfer.TRANSFER_BACK else 'SIP_NULLPTR' build_result_args.append(transfer) elif arg.type is ArgumentType.ENUM and arg.definition.fq_cpp_name is not None: build_result_args.append(_gto_name(arg.definition)) build_result_args = ', '.join(build_result_args) sf.write(f' {action} sipBuildResult({build_result_args});\n') # All done for multiple values. return # Deal with the only returned value. if only_out_arg_nr < 0: value = result value_name = 'sipRes' else: value = overload.py_signature.args[only_out_arg_nr] value_name = fmt_argument_as_name(spec, value, only_out_arg_nr) if value.type in (ArgumentType.CLASS, ArgumentType.MAPPED): need_new_instance = _need_new_instance(value) convertor = 'sipConvertFromNewType' if need_new_instance else 'sipConvertFromType' value_name = _const_cast(spec, value, value_name) transfer = 'Py_None' if not need_new_instance and value.transfer is Transfer.TRANSFER_BACK else 'SIP_NULLPTR' sf.write(f' {action} {convertor}({value_name}, {_gto_name(value.definition)}, {transfer});\n') elif value.type is ArgumentType.ENUM: if value.definition.fq_cpp_name is not None: if not spec.c_bindings: value_name = f'static_cast({value_name})' sf.write(f' {action} sipConvertFromEnum({value_name}, {_gto_name(value.definition)});\n') else: sf.write(f' {action} PyLong_FromLong({value_name});\n') elif value.type is ArgumentType.ASCII_STRING: if len(value.derefs) == 0: sf.write(f' {action} PyUnicode_DecodeASCII(&{value_name}, 1, SIP_NULLPTR);\n') else: sf.write( f''' if ({value_name} == SIP_NULLPTR) {{ Py_INCREF(Py_None); return Py_None; }} {action} PyUnicode_DecodeASCII({value_name}, strlen({value_name}), SIP_NULLPTR); ''') elif value.type is ArgumentType.LATIN1_STRING: if len(value.derefs) == 0: sf.write(f' {action} PyUnicode_DecodeLatin1(&{value_name}, 1, SIP_NULLPTR);\n') else: sf.write( f''' if ({value_name} == SIP_NULLPTR) {{ Py_INCREF(Py_None); return Py_None; }} {action} PyUnicode_DecodeLatin1({value_name}, strlen({value_name}), SIP_NULLPTR); ''') elif value.type is ArgumentType.UTF8_STRING: if len(value.derefs) == 0: sf.write(f' {action} PyUnicode_FromStringAndSize(&{value_name}, 1);\n') else: sf.write( f''' if ({value_name} == SIP_NULLPTR) {{ Py_INCREF(Py_None); return Py_None; }} {action} PyUnicode_FromString({value_name}); ''') elif value.type in (ArgumentType.SSTRING, ArgumentType.USTRING, ArgumentType.STRING): cast = '' if value.type is ArgumentType.STRING else '(char *)' if len(value.derefs) == 0: sf.write(f' {action} PyBytes_FromStringAndSize({cast}&{value_name}, 1);\n') else: sf.write( f''' if ({value_name} == SIP_NULLPTR) {{ Py_INCREF(Py_None); return Py_None; }} {action} PyBytes_FromString({cast}{value_name}); ''') elif value.type is ArgumentType.WSTRING: if len(value.derefs) == 0: sf.write(f' {action} PyUnicode_FromWideChar(&{value_name}, 1);\n') else: sf.write( f''' if ({value_name} == SIP_NULLPTR) {{ Py_INCREF(Py_None); return Py_None; }} {action} PyUnicode_FromWideChar({value_name}, (Py_ssize_t)wcslen({value_name})); ''') elif value.type in (ArgumentType.BOOL, ArgumentType.CBOOL): sf.write(f' {action} PyBool_FromLong({value_name});\n') elif value.type in (ArgumentType.BYTE, ArgumentType.SBYTE, ArgumentType.SHORT, ArgumentType.INT, ArgumentType.CINT, ArgumentType.LONG): sf.write(f' {action} PyLong_FromLong({value_name});\n') elif value.type in (ArgumentType.UBYTE, ArgumentType.USHORT, ArgumentType.UINT, ArgumentType.ULONG, ArgumentType.SIZE): sf.write(f' {action} PyLong_FromUnsignedLong({value_name});\n') elif value.type is ArgumentType.LONGLONG: sf.write(f' {action} PyLong_FromLongLong({value_name});\n') elif value.type is ArgumentType.ULONGLONG: sf.write(f' {action} PyLong_FromUnsignedLongLong({value_name});\n') elif value.type is ArgumentType.SSIZE: sf.write(f' {action} PyLong_FromSsize_t({value_name});\n') elif value.type is ArgumentType.VOID: convertor = 'sipConvertFromConstVoidPtr' if value.is_const else 'sipConvertFromVoidPtr' if result_size_arg_nr >= 0: convertor += 'AndSize' sf.write(f' {action} {convertor}({_get_void_ptr_cast(value)}{value_name}') if result_size_arg_nr >= 0: sf.write(', ' + fmt_argument_as_name(spec, overload.py_signature.args[result_size_arg_nr], result_size_arg_nr)) sf.write(');\n') elif value.type is ArgumentType.CAPSULE: sf.write(f' {action} PyCapsule_New({value_name}, "{value.definition.as_cpp}", SIP_NULLPTR);\n') elif value.type in (ArgumentType.STRUCT, ArgumentType.UNION): convertor = 'sipConvertFromConstVoidPtr' if value.is_const else 'sipConvertFromVoidPtr' sf.write(f' {action} {convertor}({value_name});\n') elif value.type in (ArgumentType.FLOAT, ArgumentType.CFLOAT): sf.write(f' {action} PyFloat_FromDouble((double){value_name});\n') elif value.type in (ArgumentType.DOUBLE, ArgumentType.CDOUBLE): sf.write(f' {action} PyFloat_FromDouble({value_name});\n') elif value.type in (ArgumentType.PYOBJECT, ArgumentType.PYTUPLE, ArgumentType.PYLIST, ArgumentType.PYDICT, ArgumentType.PYCALLABLE, ArgumentType.PYSLICE, ArgumentType.PYTYPE, ArgumentType.PYBUFFER, ArgumentType.PYENUM): sf.write(f' {action} {value_name};\n') def _get_build_result_format(type): """ Return the format string used by sipBuildResult() for a particular type. """ if type.type in (ArgumentType.CLASS, ArgumentType.MAPPED): return 'N' if _need_new_instance(type) else 'D' if type.type is ArgumentType.FAKE_VOID: return 'D' if type.type in (ArgumentType.BOOL, ArgumentType.CBOOL): return 'b' if type.type in (ArgumentType.ASCII_STRING, ArgumentType.LATIN1_STRING, ArgumentType.UTF8_STRING): return 'A' if _is_string(type) else 'a' if type.type in (ArgumentType.SSTRING, ArgumentType.USTRING, ArgumentType.STRING): return 's' if _is_string(type) else 'c' if type.type is ArgumentType.WSTRING: return 'x' if _is_string(type) else 'w' if type.type is ArgumentType.ENUM: return 'F' if type.definition.fq_cpp_name is not None else 'e' if type.type in (ArgumentType.BYTE, ArgumentType.SBYTE): # Note that this is the correct thing to do even if char is unsigned. return 'L' if type.type is ArgumentType.UBYTE: return 'M' if type.type is ArgumentType.SHORT: return 'h' if type.type is ArgumentType.USHORT: return 't' if type.type in (ArgumentType.INT, ArgumentType.CINT): return 'i' if type.type is ArgumentType.UINT: return 'u' if type.type is ArgumentType.SIZE: return '=' if type.type is ArgumentType.LONG: return 'l' if type.type is ArgumentType.ULONG: return 'm' if type.type is ArgumentType.LONGLONG: return 'n' if type.type is ArgumentType.ULONGLONG: return 'o' if type.type in (ArgumentType.STRUCT, ArgumentType.UNION, ArgumentType.VOID): return 'V' if type.type is ArgumentType.CAPSULE: return 'z' if type.type in (ArgumentType.FLOAT, ArgumentType.CFLOAT): return 'f' if type.type in (ArgumentType.DOUBLE, ArgumentType.CDOUBLE): return 'd' if type.type in (ArgumentType.PYOBJECT, ArgumentType.PYTUPLE, ArgumentType.PYLIST, ArgumentType.PYDICT, ArgumentType.PYCALLABLE, ArgumentType.PYSLICE, ArgumentType.PYTYPE, ArgumentType.PYBUFFER, ArgumentType.PYENUM): return 'R' # We should never get here. return '' def _is_string(type): """ Check if a type is a string rather than a char type. """ nr_derefs = len(type.derefs) if type.is_out and not type.is_reference: nr_derefs -= 1 return nr_derefs > 0 def _needs_heap_copy(arg, using_copy_ctor=True): """ Return True if an argument (or result) needs to be copied to the heap. """ # The type is a class or mapped type and not a pointer. if not arg.no_copy and arg.type in (ArgumentType.CLASS, ArgumentType.MAPPED) and len(arg.derefs) == 0: # We need a copy unless it is a non-const reference. if not arg.is_reference or arg.is_const: # We assume we can copy a mapped type. if arg.type is ArgumentType.MAPPED: return True klass = arg.definition # We can't copy an abstract class. if klass.is_abstract: return False # We can copy if we have a public copy ctor. if not klass.cannot_copy: return True # We can't copy if we must use a copy ctor. if using_copy_ctor: return False # We can copy if we have a public assignment operator. return not klass.cannot_assign return False def _function_call(sf, spec, bindings, scope, overload, dereferenced, original_scope): """ Generate a function call. """ py_slot = overload.common.py_slot result = overload.py_signature.result result_cpp_type = fmt_argument_as_cpp_type(spec, result, plain=True, no_derefs=True) static_factory = (scope is None or overload.is_static) and overload.factory sf.write(' {\n') # If there is no shadow class then protected methods can never be called. if overload.access_specifier is AccessSpecifier.PROTECTED and not scope.has_shadow: sf.write( ''' /* Never reached. */ } ''') return # Save the full result type as we may want to fiddle with it. saved_result_is_const = result.is_const # See if we need to make a copy of the result on the heap. is_new_instance = _needs_heap_copy(result, using_copy_ctor=False) if is_new_instance: result.is_const = False result_decl = _get_result_decl(spec, scope, overload, result) if result_decl is not None: sf.write(' ' + result_decl + ';\n') separating_newline = True else: separating_newline = False # See if we want to keep a reference to the result. post_process = result.key is not None delete_temporaries = True result_size_arg_nr = -1 for arg_nr, arg in enumerate(overload.py_signature.args): if arg.result_size: result_size_arg_nr = arg_nr if static_factory and arg.key is not None: post_process = True # If we have an In,Out argument that has conversion code then we delay # the destruction of any temporary variables until after we have # converted the outputs. if arg.is_in and arg.is_out and _get_convert_to_type_code(arg) is not None: delete_temporaries = False post_process = True # If we are returning a class via an output only reference or pointer # then we need an instance on the heap. if arg.type in (ArgumentType.CLASS, ArgumentType.MAPPED) and _need_new_instance(arg): arg_name = fmt_argument_as_name(spec, arg, arg_nr) arg_cpp_type = fmt_argument_as_cpp_type(spec, arg, plain=True, no_derefs=True) sf.write(f' {arg_name} = new {arg_cpp_type}();\n') separating_newline = True if post_process: sf.write(' PyObject *sipResObj;\n') separating_newline = True if overload.premethod_code is not None: sf.write('\n') sf.write_code(overload.premethod_code) error_flag = old_error_flag = False if overload.method_code is not None: # See if the handwritten code seems to be using the error flag. if _need_error_flag(overload.method_code): sf.write(' sipErrorState sipError = sipErrorNone;\n') error_flag = True separating_newline = True elif _need_old_error_flag(overload.method_code): sf.write(' int sipIsErr = 0;\n') old_error_flag = True separating_newline = True if separating_newline: sf.write('\n') # If it is abstract make sure that self was bound. if overload.is_abstract: sf.write( f''' if (!sipOrigSelf) {{ sipAbstractMethod({_cached_name_ref(scope.py_name)}, {_cached_name_ref(overload.common.py_name)}); return SIP_NULLPTR; }} ''') if overload.deprecated: scope_py_name_ref = _cached_name_ref(scope.py_name) if scope is not None and scope.py_name is not None else 'SIP_NULLPTR' error_return = '-1' if is_void_return_slot(py_slot) or is_int_return_slot(py_slot) or is_ssize_return_slot(py_slot) or is_hash_return_slot(py_slot) else 'SIP_NULLPTR' # Note that any temporaries will leak if an exception is raised. sf.write( f''' if (sipDeprecated({scope_py_name_ref}, {_cached_name_ref(overload.common.py_name)}) < 0) return {error_return}; ''') # Call any pre-hook. if overload.prehook is not None: sf.write(f' sipCallHook("{overload.prehook}");\n\n') if overload.method_code is not None: sf.write_code(overload.method_code) else: release_gil = _release_gil(overload.gil_action, bindings) needs_closing_paren = False if is_new_instance and spec.c_bindings: sf.write( f''' if ((sipRes = ({result_cpp_type} *)sipMalloc(sizeof ({result_cpp_type}))) == SIP_NULLPTR) {{ ''') _gc_ellipsis(sf, overload.py_signature) sf.write( ''' return SIP_NULLPTR; } ''') if overload.raises_py_exception: sf.write(' PyErr_Clear();\n\n') if isinstance(scope, WrappedClass) and scope.len_cpp_name is not None: _sequence_support(sf, spec, scope, overload) if release_gil: sf.write(' Py_BEGIN_ALLOW_THREADS\n') _try(sf, bindings, overload.throw_args) sf.write(' ') if result_decl is not None: # Construct a copy on the heap if needed. if is_new_instance: if spec.c_bindings: sf.write('*sipRes = ') elif result.type is ArgumentType.CLASS and result.definition.cannot_copy: sf.write(f'sipRes = reinterpret_cast<{result_cpp_type} *>(::operator new(sizeof ({result_cpp_type})));\n *sipRes = ') else: sf.write(f'sipRes = new {result_cpp_type}(') needs_closing_paren = True else: sf.write('sipRes = ') # See if we need the address of the result. Any reference will # be non-const. if result.type in (ArgumentType.CLASS, ArgumentType.MAPPED) and (len(result.derefs) == 0 or result.is_reference): sf.write('&') if py_slot is None: _cpp_function_call(sf, spec, scope, overload, original_scope) elif py_slot is PySlot.CALL: sf.write('(*sipCpp)(') _call_args(sf, spec, overload.cpp_signature, overload.py_signature) sf.write(')') else: sf.write(_get_slot_call(spec, scope, overload, dereferenced)) if needs_closing_paren: sf.write(')') sf.write(';\n') _catch(sf, spec, bindings, overload.py_signature, overload.throw_args, release_gil) if release_gil: sf.write(' Py_END_ALLOW_THREADS\n') for arg_nr, arg in enumerate(overload.py_signature.args): if not arg.is_in: continue # Handle any /KeepReference/ arguments except for static factories. if not static_factory and arg.key is not None: sip_self = 'SIP_NULLPTR' if scope is None or overload.is_static else 'sipSelf' keep_reference_call = _get_keep_reference_call(spec, arg, arg_nr, sip_self) sf.write(f'\n {keep_reference_call};\n') # Handle /TransferThis/ for non-factory methods. if not overload.factory and arg.transfer is Transfer.TRANSFER_THIS: sf.write( ''' if (sipOwner) sipTransferTo(sipSelf, (PyObject *)sipOwner); else sipTransferBack(sipSelf); ''') if overload.transfer is Transfer.TRANSFER_THIS: sf.write('\n sipTransferTo(sipSelf, SIP_NULLPTR);\n') _gc_ellipsis(sf, overload.py_signature) if delete_temporaries and not is_zero_arg_slot(py_slot): _delete_temporaries(sf, spec, overload.py_signature) sf.write('\n') # Handle the error flag if it was used. error_value = '-1' if is_void_return_slot(py_slot) or is_int_return_slot(py_slot) or is_ssize_return_slot(py_slot) or is_hash_return_slot(py_slot) else '0' if overload.raises_py_exception: sf.write( f''' if (PyErr_Occurred()) return {error_value}; ''') elif error_flag: if not is_zero_arg_slot(py_slot): sf.write( f''' if (sipError == sipErrorFail) return {error_value}; ''') sf.write( ''' if (sipError == sipErrorNone) { ''') elif old_error_flag: sf.write( f''' if (sipIsErr) return {error_value}; ''') # Call any post-hook. if overload.posthook is not None: sf.write(f'\n sipCallHook("{overload.posthook}");\n') if is_void_return_slot(py_slot): sf.write( ''' return 0; ''') elif is_inplace_number_slot(py_slot) or is_inplace_sequence_slot(py_slot): sf.write( ''' Py_INCREF(sipSelf); return sipSelf; ''') elif is_int_return_slot(py_slot) or is_ssize_return_slot(py_slot) or is_hash_return_slot(py_slot): sf.write( ''' return sipRes; ''') else: action = 'sipResObj =' if post_process else 'return' _handle_result(sf, spec, overload, is_new_instance, result_size_arg_nr, action) # Delete the temporaries now if we haven't already done so. if not delete_temporaries: _delete_temporaries(sf, spec, overload.py_signature) # Keep a reference to a pointer to a class if it isn't owned by Python. if result.key is not None: sip_self = 'SIP_NULLPTR' if overload.is_static else 'sipSelf' sf.write(f'\n sipKeepReference({sip_self}, {result.key}, sipResObj);\n') # Keep a reference to any argument with the result if the function is a # static factory. if static_factory: for arg_nr, arg in enumerate(overload.py_signature.args): if not arg.is_in: continue if arg.key != None: keep_reference_call = _get_keep_reference_call(spec, arg, arg_nr, 'sipResObj') sf.write(f'\n {keep_reference_call};\n') if post_process: sf.write('\n return sipResObj;\n') if error_flag: sf.write(' }\n') if not is_zero_arg_slot(py_slot): sf.write('\n sipAddException(sipError, &sipParseErr);\n') sf.write(' }\n') # Restore the full state of the result. result.is_const = saved_result_is_const def _get_keep_reference_call(spec, arg, arg_nr, object_name): """ Return a call to sipKeepReference() for an argument. """ arg_name = fmt_argument_as_name(spec, arg, arg_nr) suffix = 'Wrapper' if arg.get_wrapper and (arg.type not in (ArgumentType.ASCII_STRING, ArgumentType.LATIN1_STRING, ArgumentType.UTF8_STRING) or len(arg.derefs) != 1) else 'Keep' return f'sipKeepReference({object_name}, {arg.key}, {arg_name}{suffix})' def _get_result_decl(spec, scope, overload, result): """ Return the declaration of a variable to hold the result of a function call if one is needed. """ # See if sipRes is needed. no_result = (is_inplace_number_slot(overload.common.py_slot) or is_inplace_sequence_slot(overload.common.py_slot) or (result.type is ArgumentType.VOID and len(result.derefs) == 0)) if no_result: return None result_decl = _get_named_value_decl(spec, scope, result, 'sipRes') # The typical %MethodCode usually causes a compiler warning, so we # initialise the result in that case to try and suppress it. initial_value = ' = ' + _cast_zero(spec, result) if overload.method_code is not None else '' return result_decl + initial_value def _get_slot_call(spec, scope, overload, dereferenced): """ Return the call to a Python slot (except for PySlot.CALL which is handled separately). """ py_slot = overload.common.py_slot if py_slot is PySlot.GETITEM: return f'(*sipCpp)[{_get_slot_arg(spec, overload, 0)}]' if py_slot in (PySlot.INT, PySlot.FLOAT): return '*sipCpp' if py_slot is PySlot.ADD: return _get_number_slot_call(spec, overload, '+') if py_slot is PySlot.CONCAT: return _get_binary_slot_call(spec, scope, overload, '+', dereferenced) if py_slot is PySlot.SUB: return _get_number_slot_call(spec, overload, '-') if py_slot in (PySlot.MUL, PySlot.MATMUL): return _get_number_slot_call(spec, overload, '*') if py_slot is PySlot.REPEAT: return _get_binary_slot_call(spec, scope, overload, '*', dereferenced) if py_slot is PySlot.TRUEDIV: return _get_number_slot_call(spec, overload, '/') if py_slot is PySlot.MOD: return _get_number_slot_call(spec, overload, '%') if py_slot is PySlot.AND: return _get_number_slot_call(spec, overload, '&') if py_slot is PySlot.OR: return _get_number_slot_call(spec, overload, '|') if py_slot is PySlot.XOR: return _get_number_slot_call(spec, overload, '^') if py_slot is PySlot.LSHIFT: return _get_number_slot_call(spec, overload, '<<') if py_slot is PySlot.RSHIFT: return _get_number_slot_call(spec, overload, '>>') if py_slot in (PySlot.IADD, PySlot.ICONCAT): return _get_binary_slot_call(spec, scope, overload, '+=', dereferenced) if py_slot is PySlot.ISUB: return _get_binary_slot_call(spec, scope, overload, '-=', dereferenced) if py_slot in (PySlot.IMUL, PySlot.IREPEAT, PySlot.IMATMUL): return _get_binary_slot_call(spec, scope, overload, '*=', dereferenced) if py_slot is PySlot.ITRUEDIV: return _get_binary_slot_call(spec, scope, overload, '/=', dereferenced) if py_slot is PySlot.IMOD: return _get_binary_slot_call(spec, scope, overload, '%=', dereferenced) if py_slot is PySlot.IAND: return _get_binary_slot_call(spec, scope, overload, '&=', dereferenced) if py_slot is PySlot.IOR: return _get_binary_slot_call(spec, scope, overload, '|=', dereferenced) if py_slot is PySlot.IXOR: return _get_binary_slot_call(spec, scope, overload, '^=', dereferenced) if py_slot is PySlot.ILSHIFT: return _get_binary_slot_call(spec, scope, overload, '<<=', dereferenced) if py_slot is PySlot.IRSHIFT: return _get_binary_slot_call(spec, scope, overload, '>>=', dereferenced) if py_slot is PySlot.INVERT: return '~(*sipCpp)' if py_slot is PySlot.LT: return _get_binary_slot_call(spec, scope, overload, '<', dereferenced) if py_slot is PySlot.LE: return _get_binary_slot_call(spec, scope, overload, '<=', dereferenced) if py_slot is PySlot.EQ: return _get_binary_slot_call(spec, scope, overload, '==', dereferenced) if py_slot is PySlot.NE: return _get_binary_slot_call(spec, scope, overload, '!=', dereferenced) if py_slot is PySlot.GT: return _get_binary_slot_call(spec, scope, overload, '>', dereferenced) if py_slot is PySlot.GE: return _get_binary_slot_call(spec, scope, overload, '>=', dereferenced) if py_slot is PySlot.NEG: return '-(*sipCpp)' if py_slot is PySlot.POS: return '+(*sipCpp)' # We should never get here. return '' def _cpp_function_call(sf, spec, scope, overload, original_scope): """ Generate a call to a C++ function. """ cpp_name = overload.cpp_name # If the function is protected then call the public wrapper. If it is # virtual then call the explicit scoped function if "self" was passed as # the first argument. nr_parens = 1 if scope is None: sf.write(cpp_name + '(') elif scope.iface_file.type is IfaceFileType.NAMESPACE: sf.write(f'{scope.iface_file.fq_cpp_name.as_cpp}::{cpp_name}(') elif overload.is_static: if overload.access_specifier is AccessSpecifier.PROTECTED: sf.write(f'sip{scope.iface_file.fq_cpp_name.as_word}::sipProtect_{cpp_name}(') else: sf.write(f'{original_scope.iface_file.fq_cpp_name.as_cpp}::{cpp_name}(') elif overload.access_specifier is AccessSpecifier.PROTECTED: if not overload.is_abstract and (overload.is_virtual or overload.is_virtual_reimplementation): sf.write(f'sipCpp->sipProtectVirt_{cpp_name}(sipSelfWasArg') if len(overload.cpp_signature.args) != 0: sf.write(', ') else: sf.write(f'sipCpp->sipProtect_{cpp_name}(') elif not overload.is_abstract and (overload.is_virtual or overload.is_virtual_reimplementation): sf.write(f'(sipSelfWasArg ? sipCpp->{original_scope.iface_file.fq_cpp_name.as_cpp}::{cpp_name}(') _call_args(sf, spec, overload.cpp_signature, overload.py_signature) sf.write(f') : sipCpp->{cpp_name}(') nr_parens += 1 else: sf.write(f'sipCpp->{cpp_name}(') _call_args(sf, spec, overload.cpp_signature, overload.py_signature) sf.write(')' * nr_parens) def _get_slot_arg(spec, overload, arg_nr): """ Return an argument to a slot call. """ arg = overload.py_signature.args[arg_nr] dereference = '*' if arg.type in (ArgumentType.CLASS, ArgumentType.MAPPED) and len(arg.derefs) == 0 else '' return dereference + fmt_argument_as_name(spec, arg, arg_nr) # A map of operators and their complements. _OPERATOR_COMPLEMENTS = { '<': '>=', '<=': '>', '==': '!=', '!=': '==', '>': '<=', '>=': '<', } def _get_binary_slot_call(spec, scope, overload, operator, dereferenced): """ Return the call to a binary (non-number) slot method. """ slot_call = '' if overload.is_complementary: operator = _OPERATOR_COMPLEMENTS[operator] slot_call += '!' if overload.is_global: # If it has been moved from a namespace then get the C++ scope. if overload.common.namespace_iface_file is not None: slot_call += overload.common.namespace_iface_file.fq_cpp_name.as_cpp + '::' if dereferenced: slot_call += f'operator{operator}((*sipCpp), ' else: slot_call += f'operator{operator}(sipCpp, ' else: dereference = '->' if dereferenced else '.' if overload.is_abstract: slot_call += f'sipCpp{dereference}operator{operator}(' else: slot_call += f'sipCpp{dereference}{scope.iface_file.fq_cpp_name.as_cpp}::operator{operator}(' slot_call += _get_slot_arg(spec, overload, 0) slot_call += ')' return slot_call def _get_number_slot_call(spec, overload, operator): """ Return the call to a binary number slot method. """ arg0 = _get_slot_arg(spec, overload, 0) arg1 = _get_slot_arg(spec, overload, 1) return f'({arg0} {operator} {arg1})' def _arg_parser(sf, spec, scope, py_signature, ctor=None, overload=None): """ Generate the argument variables for a member function/constructor/operator. """ # If the scope is just a namespace, then ignore it. if isinstance(scope, WrappedClass) and scope.iface_file.type is IfaceFileType.NAMESPACE: scope = None # For ABI v13 and later static methods use self for the type object. if spec.abi_version >= (13, 0): handle_self = (scope is not None and overload is not None and overload.common.py_slot is None) else: handle_self = (scope is not None and overload is not None and overload.common.py_slot is None and not overload.is_static) # Generate the local variables that will hold the parsed arguments and # values returned via arguments. array_len_arg_nr = -1 need_owner = False ctor_needs_self = False for arg_nr, arg in enumerate(py_signature.args): if arg.array is ArrayArgument.ARRAY_SIZE: array_len_arg_nr = arg_nr _argument_variable(sf, spec, scope, arg, arg_nr) if arg.transfer is Transfer.TRANSFER_THIS: need_owner = True if ctor is not None and arg.transfer is Transfer.TRANSFER: ctor_needs_self = True if overload is not None and need_owner: sf.write(' sipWrapper *sipOwner = SIP_NULLPTR;\n') if handle_self and not overload.is_static: cpp_type = 'const ' if overload.is_const else '' if overload.access_specifier is AccessSpecifier.PROTECTED and scope.has_shadow: cpp_type += 'sip' + scope.iface_file.fq_cpp_name.as_word else: cpp_type += _scoped_class_name(spec, scope) sf.write(f' {cpp_type} *sipCpp;\n\n') elif len(py_signature.args) != 0: sf.write('\n') # Generate the call to the parser function. args = [] single_arg = False if overload is not None and is_number_slot(overload.common.py_slot): parser_function = 'sipParsePair' args.append('&sipParseErr') args.append('sipArg0') args.append('sipArg1') elif overload is not None and overload.common.py_slot is PySlot.SETATTR: # We don't even try to invoke the parser if there is a value and there # shouldn't be (or vice versa) so that the list of errors doesn't get # polluted with signatures that can never apply. if overload.is_delattr: operator = '==' sip_value = 'SIP_NULLPTR' else: operator = '!=' sip_value = 'sipValue' parser_function = f'sipValue {operator} SIP_NULLPTR && sipParsePair' args.append('&sipParseErr') args.append('sipName') args.append(sip_value) elif (overload is not None and overload.common.allow_keyword_args) or ctor is not None: # We handle keywords if we might have been passed some (because one of # the overloads uses them or we are a ctor). However this particular # overload might not have any. if overload is not None: kw_args = overload.kw_args elif ctor is not None: kw_args = ctor.kw_args else: kw_args = KwArgs.NONE # The above test isn't good enough because when the flags were set in # the parser we couldn't know for sure if an argument was an output # pointer. Therefore we check here. The drawback is that we may # generate the name string for the argument but never use it, or we # might have an empty keyword name array or one that contains only # NULLs. is_ka_list = False if kw_args is not KwArgs.NONE: for arg in py_signature.args: if not arg.is_in: continue if not is_ka_list: sf.write(' static const char *sipKwdList[] = {\n') is_ka_list = True if arg.name is not None and (kw_args is KwArgs.ALL or arg.default_value is not None): arg_name_ref = _cached_name_ref(arg.name) else: arg_name_ref = 'SIP_NULLPTR' sf.write(f' {arg_name_ref},\n') if is_ka_list: sf.write(' };\n\n') parser_function = 'sipParseKwdArgs' args.append('sipParseErr' if ctor is not None else '&sipParseErr') args.append('sipArgs') args.append('sipKwds') args.append('sipKwdList' if is_ka_list else 'SIP_NULLPTR') args.append('sipUnused' if ctor is not None else 'SIP_NULLPTR') else: single_arg = not (overload is None or overload.common.py_slot is None or is_multi_arg_slot(overload.common.py_slot)) plural = '' if single_arg else 's' parser_function = 'sipParseArgs' args.append('&sipParseErr') args.append('sipArg' + plural) # Generate the format string. format_s = '"' optional_args = False if single_arg: format_s += '1' if ctor_needs_self: format_s += '#' elif handle_self: if overload.is_static: format_s += 'C' elif overload.access_is_really_protected: format_s += 'p' else: format_s += 'B' for arg in py_signature.args: if not arg.is_in: continue if arg.default_value is not None and not optional_args: format_s += '|' optional_args = True # Get the wrapper if explicitly asked for or we are going to keep a # reference to. However if it is an encoded string then we will get # the actual wrapper from the format character. if arg.get_wrapper: format_s += '@' elif arg.key is not None: if not (arg.type in (ArgumentType.ASCII_STRING, ArgumentType.LATIN1_STRING, ArgumentType.UTF8_STRING) and len(arg.derefs) == 1): format_s += '@' if arg.type is ArgumentType.ASCII_STRING: format_s += 'AA' if _is_string(arg) else 'aA' elif arg.type is ArgumentType.LATIN1_STRING: format_s += 'AL' if _is_string(arg) else 'aL' elif arg.type is ArgumentType.UTF8_STRING: format_s += 'A8' if _is_string(arg) else 'a8' elif arg.type in (ArgumentType.SSTRING, ArgumentType.USTRING, ArgumentType.STRING): if arg.array is ArrayArgument.ARRAY: format_s += 'k' elif _is_string(arg): format_s += 's' else: format_s += 'c' elif arg.type is ArgumentType.WSTRING: if arg.array is ArrayArgument.ARRAY: format_s += 'K' elif _is_string(arg): format_s += 'x' else: format_s += 'w' elif arg.type is ArgumentType.ENUM: if arg.definition.fq_cpp_name is None: format_s += 'e' elif arg.is_constrained: format_s += 'XE' else: format_s += 'E' elif arg.type is ArgumentType.BOOL: format_s += 'b' elif arg.type is ArgumentType.CBOOL: format_s += 'Xb' elif arg.type is ArgumentType.INT: if arg.array is not ArrayArgument.ARRAY_SIZE: format_s += 'i' elif arg.type is ArgumentType.UINT: if arg.array is not ArrayArgument.ARRAY_SIZE: format_s += 'u' elif arg.type is ArgumentType.SIZE: if arg.array is not ArrayArgument.ARRAY_SIZE: format_s += '=' elif arg.type is ArgumentType.CINT: format_s += 'Xi' elif arg.type is ArgumentType.BYTE: if arg.array is not ArrayArgument.ARRAY_SIZE: format_s += 'I' if _abi_has_working_char_conversion(spec) else 'L' elif arg.type is ArgumentType.SBYTE: if arg.array is not ArrayArgument.ARRAY_SIZE: format_s += 'L' elif arg.type is ArgumentType.UBYTE: if arg.array is not ArrayArgument.ARRAY_SIZE: format_s += 'M' elif arg.type is ArgumentType.SHORT: if arg.array is not ArrayArgument.ARRAY_SIZE: format_s += 'h' elif arg.type is ArgumentType.USHORT: if arg.array is not ArrayArgument.ARRAY_SIZE: format_s += 't' elif arg.type is ArgumentType.LONG: if arg.array is not ArrayArgument.ARRAY_SIZE: format_s += 'l' elif arg.type is ArgumentType.ULONG: if arg.array is not ArrayArgument.ARRAY_SIZE: format_s += 'm' elif arg.type is ArgumentType.LONGLONG: if arg.array is not ArrayArgument.ARRAY_SIZE: format_s += 'n' elif arg.type is ArgumentType.ULONGLONG: if arg.array is not ArrayArgument.ARRAY_SIZE: format_s += 'o' elif arg.type in (ArgumentType.STRUCT, ArgumentType.UNION, ArgumentType.VOID): format_s += 'v' elif arg.type is ArgumentType.CAPSULE: format_s += 'z' elif arg.type is ArgumentType.FLOAT: format_s += 'f' elif arg.type is ArgumentType.CFLOAT: format_s += 'Xf' elif arg.type is ArgumentType.DOUBLE: format_s += 'd' elif arg.type is ArgumentType.CDOUBLE: format_s += 'Xd' elif arg.type in (ArgumentType.CLASS, ArgumentType.MAPPED): if arg.array is ArrayArgument.ARRAY: format_s += '>' if arg.type is ArgumentType.CLASS and _abi_supports_array(spec) else 'r' else: format_s += 'J' + _get_subformat_char(arg) elif arg.type is ArgumentType.PYOBJECT: format_s += 'P' + _get_subformat_char(arg) elif arg.type in (ArgumentType.PYTUPLE, ArgumentType.PYLIST, ArgumentType.PYDICT, ArgumentType.PYSLICE, ArgumentType.PYTYPE): format_s += 'N' if arg.allow_none else 'T' elif arg.type is ArgumentType.PYCALLABLE: format_s += 'H' if arg.allow_none else 'F' elif arg.type is ArgumentType.PYBUFFER: format_s += '$' if arg.allow_none else '!' elif arg.type is ArgumentType.PYENUM: format_s += '^' if arg.allow_none else '&' elif arg.type is ArgumentType.ELLIPSIS: format_s += 'W' format_s += '"' args.append(format_s) # Generate the parameters corresponding to the format string. if ctor_needs_self: args.append('sipSelf') elif handle_self: args.append('&sipSelf') if not overload.is_static: args.append(_gto_name(scope)) args.append('&sipCpp') for arg_nr, arg in enumerate(py_signature.args): if not arg.is_in: continue arg_name = fmt_argument_as_name(spec, arg, arg_nr) arg_name_ref = '&' + arg_name # Use the wrapper name if it was explicitly asked for. if arg.get_wrapper: args.append(f'&{arg_name}Wrapper') elif arg.key is not None: args.append(f'&{arg_name}Keep') if arg.type is ArgumentType.MAPPED: mapped_type = arg.definition args.append(_gto_name(mapped_type)) args.append(arg_name_ref) if arg.array is ArrayArgument.ARRAY: array_len_arg_name = fmt_argument_as_name(spec, py_signature.args[array_len_arg_nr], array_len_arg_nr) args.append('&' + array_len_arg_name) elif mapped_type.convert_to_type_code is not None and not arg.is_constrained: args.append('SIP_NULLPTR' if mapped_type.no_release else f'&{arg_name}State') if mapped_type.needs_user_state: args.append(f'&{arg_name}UserState') elif arg.type is ArgumentType.CLASS: klass = arg.definition args.append(_gto_name(klass)) args.append(arg_name_ref) if arg.array is ArrayArgument.ARRAY: array_len_arg_name = fmt_argument_as_name(spec, py_signature.args[array_len_arg_nr], array_len_arg_nr) args.append('&' + array_len_arg_name) if _abi_supports_array(spec): args.append(f'&{arg_name}IsTemp') else: if arg.transfer is Transfer.TRANSFER_THIS: args.append('sipOwner' if ctor is not None else '&sipOwner') if klass.convert_to_type_code is not None and not arg.is_constrained: args.append(f'&{arg_name}State') elif arg.type in (ArgumentType.ASCII_STRING, ArgumentType.LATIN1_STRING, ArgumentType.UTF8_STRING): if arg.key is None and len(arg.derefs) == 1: args.append(f'&{arg_name}Keep') args.append(arg_name_ref) elif arg.type is ArgumentType.PYTUPLE: args.append('&PyTuple_Type') args.append(arg_name_ref) elif arg.type is ArgumentType.PYLIST: args.append('&PyList_Type') args.append(arg_name_ref) elif arg.type is ArgumentType.PYDICT: args.append('&PyDict_Type') args.append(arg_name_ref) elif arg.type is ArgumentType.PYSLICE: args.append('&PySlice_Type') args.append(arg_name_ref) elif arg.type is ArgumentType.PYTYPE: args.append('&PyType_Type') args.append(arg_name_ref) elif arg.type is ArgumentType.ENUM: if arg.definition.fq_cpp_name is not None: args.append(_gto_name(arg.definition)) args.append(arg_name_ref) elif arg.type is ArgumentType.CAPSULE: args.append('"' + arg.definition.as_cpp + '"') args.append(arg_name_ref) else: if arg.array is not ArrayArgument.ARRAY_SIZE: args.append(arg_name_ref) if arg.array is ArrayArgument.ARRAY: array_len_arg_name = fmt_argument_as_name(spec, py_signature.args[array_len_arg_nr], array_len_arg_nr) args.append('&' + array_len_arg_name) args = ', '.join(args) sf.write(f' if ({parser_function}({args}))\n') def _get_subformat_char(arg): """ Return the sub-format character for an argument. """ flags = 0 if arg.transfer is Transfer.TRANSFER: flags |= 0x02 if arg.transfer is Transfer.TRANSFER_BACK: flags |= 0x04 if arg.type in (ArgumentType.CLASS, ArgumentType.MAPPED): if len(arg.derefs) == 0 or arg.disallow_none: flags |= 0x01 if arg.transfer is Transfer.TRANSFER_THIS: flags |= 0x10 if arg.is_constrained or (arg.type is ArgumentType.CLASS and arg.definition.convert_to_type_code is None): flags |= 0x08 return chr(ord('0') + flags) def _get_convert_to_type_code(type): """ Return a type's %ConvertToTypeCode. """ if type.type in (ArgumentType.CLASS, ArgumentType.MAPPED) and not type.is_constrained: return type.definition.convert_to_type_code return None def _gc_ellipsis(sf, signature): """ Generate the code to garbage collect any ellipsis argument. """ last = len(signature.args) - 1 if last >= 0 and signature.args[last].type is ArgumentType.ELLIPSIS: sf.write(f'\n Py_DECREF(a{last});\n') def _delete_outs(sf, spec, py_signature): """ Generate the code to delete any instances created to hold /Out/ arguments. """ for arg_nr, arg in enumerate(py_signature.args): if arg.type in (ArgumentType.CLASS, ArgumentType.MAPPED) and _need_new_instance(arg): sf.write(f' delete {fmt_argument_as_name(spec, arg, arg_nr)};\n') def _delete_temporaries(sf, spec, py_signature): """ Generate the code to delete any temporary variables on the heap created by type convertors. """ for arg_nr, arg in enumerate(py_signature.args): arg_name = fmt_argument_as_name(spec, arg, arg_nr) if arg.array is ArrayArgument.ARRAY and arg.type in (ArgumentType.CLASS, ArgumentType.MAPPED): if arg.transfer is not Transfer.TRANSFER: extra_indent = '' if arg.type is ArgumentType.CLASS and _abi_supports_array(spec): sf.write(f' if ({arg_name}IsTemp)\n') extra_indent = ' ' if spec.c_bindings: sf.write(f' {extra_indent}sipFree({arg_name});\n') else: sf.write(f' {extra_indent}delete[] {arg_name};\n') continue if not arg.is_in: continue if arg.type in (ArgumentType.ASCII_STRING, ArgumentType.LATIN1_STRING, ArgumentType.UTF8_STRING) and len(arg.derefs) == 1: decref = 'Py_XDECREF' if arg.default_value is not None else 'Py_DECREF' sf.write(f' {decref}({arg_name}Keep);\n') elif arg.type is ArgumentType.WSTRING and len(arg.derefs) == 1: if spec.c_bindings or not arg.is_const: sf.write(f' sipFree({arg_name});\n') else: sf.write(f' sipFree(const_cast({arg_name}));\n') else: convert_to_type_code = _get_convert_to_type_code(arg) if convert_to_type_code is not None: if arg.type is ArgumentType.MAPPED and arg.definition.no_release: continue sf.write(f' sipReleaseType{_user_state_suffix(spec, arg)}(') if spec.c_bindings or not arg.is_const: sf.write(arg_name) else: arg_cpp_plain = fmt_argument_as_cpp_type(spec, arg, plain=True, no_derefs=True) sf.write(f'const_cast<{arg_cpp_plain} *>({arg_name})') sf.write(f', {_gto_name(arg.definition)}, {arg_name}State') if _type_needs_user_state(arg): sf.write(f', {arg_name}UserState') sf.write(');\n') def _get_normalised_cached_name(cached_name): """ Return the normalised form of a cached name. """ # If the name seems to be a template then just use the offset to ensure # that it is unique. if '<' in cached_name.name: return str(cached_name.offset) # Handle C++ and Python scopes. return cached_name.name.replace(':', '_').replace('.', '_') def _scoped_class_name(spec, klass): """ Return a scoped class name as a string. Protected classes have to be explicitly scoped. """ return fmt_class_as_scoped_name(spec, klass, scope=klass.iface_file) def _need_error_flag(code): """ Return True if handwritten code uses the error flag. """ return _is_used_in_code(code, 'sipError') def _need_old_error_flag(code): """ Return True if handwritten code uses the deprecated error flag. """ return _is_used_in_code(code, 'sipIsErr') def _need_new_instance(arg): """ Return True if the argument type means an instance needs to be created on the heap to pass back to Python. """ if not arg.is_in and arg.is_out: if arg.is_reference and len(arg.derefs) == 0: return True if not arg.is_reference and len(arg.derefs) == 1: return True return False def _fake_protected_args(signature): """ Convert any protected arguments (ie. those whose type is unavailable outside of a shadow class) to a fundamental type to be used instead (with suitable casts). """ protection_state = [] for arg in signature.args: if arg.type is ArgumentType.ENUM and arg.definition.is_protected: protection_state.append(arg) arg.type = ArgumentType.INT elif arg.type is ArgumentType.CLASS and arg.definition.is_protected: protection_state.append((arg, arg.derefs, arg.is_reference)) arg.type = ArgumentType.FAKE_VOID arg.derefs = [False] arg.is_reference = False return protection_state def _restore_protected_args(protection_state): """ Restore any protected arguments faked by _fake_protected_args(). """ for protected in protection_state: if isinstance(protected, Argument): protected.type = ArgumentType.ENUM else: protected, derefs, is_reference = protected protected.type = ArgumentType.CLASS protected.derefs = derefs protected.is_reference = is_reference def _remove_protection(arg, protection_state): """ Reset and save any protections so that the argument will be rendered exactly as defined in C++. """ if arg.type in (ArgumentType.CLASS, ArgumentType.ENUM) and arg.definition.is_protected: arg.definition.is_protected = False protection_state.add(arg.definition) def _remove_protections(signature, protection_state): """ Reset and save any protections so that the signature will be rendered exactly as defined in C++. """ _remove_protection(signature.result, protection_state) for arg in signature.args: _remove_protection(arg, protection_state) def _restore_protections(protection_state): """ Restore any protections removed by _remove_protection(). """ for protected in protection_state: protected.is_protected = True def _need_dealloc(spec, bindings, klass): """ Return True if a dealloc function is needed for a class. """ if klass.iface_file.type is IfaceFileType.NAMESPACE: return False # Each of these conditions cause some code to be generated. if bindings.tracing: return True if spec.c_bindings: return True if len(klass.dealloc_code) != 0: return True if klass.dtor is AccessSpecifier.PUBLIC: return True if klass.has_shadow: return True return False def _arg_name(spec, name, code): """ Return the argument name to use in a function definition for handwritten code. """ # Always use the name in C code. if spec.c_bindings: return name # Use the name if it is used in the handwritten code. if _is_used_in_code(code, name): return name # Don't use the name. return '' def _use_in_code(code, s, spec=None): """ Return the string to use depending on whether it is used in some code and optionally if the bindings are for C. """ # Always use the string for C bindings. if spec is not None and spec.c_bindings: return s return s if _is_used_in_code(code, s) else '' def _is_used_in_code(code, s): """ Return True if a string is used in code. """ # The code may be a list of code blocks or an optional code block. if code is None: return False if isinstance(code, CodeBlock): code = [code] for cb in code: if s in cb.text: return True return False def _class_from_void(spec, klass): """ Return an assignment statement from a void * variable to a class instance variable. """ klass_type = _scoped_class_name(spec, klass) if spec.c_bindings: return f'{klass_type} *sipCpp = ({klass_type} *)sipCppV' return f'{klass_type} *sipCpp = reinterpret_cast<{klass_type} *>(sipCppV)' def _mapped_type_from_void(spec, mapped_type_type): """ Return an assignment statement from a void * variable to a mapped type variable. """ if spec.c_bindings: return f'{mapped_type_type} *sipCpp = ({mapped_type_type} *)sipCppV' return f'{mapped_type_type} *sipCpp = reinterpret_cast<{mapped_type_type} *>(sipCppV)' # The types that need a Python reference. _PY_REF_TYPES = (ArgumentType.ASCII_STRING, ArgumentType.LATIN1_STRING, ArgumentType.UTF8_STRING, ArgumentType.USTRING, ArgumentType.SSTRING, ArgumentType.STRING) def _keep_py_reference(arg): """ Return True if the argument has a type that requires an extra reference to the originating object to be kept. """ return (arg.type in _PY_REF_TYPES and not arg.is_reference and len(arg.derefs) > 0) def _get_encoding(type): """ Return the encoding character for the given type. """ if type.type is ArgumentType.ASCII_STRING: encoding = 'A' elif type.type is ArgumentType.LATIN1_STRING: encoding = 'L' elif type.type is ArgumentType.UTF8_STRING: encoding = '8' elif type.type is ArgumentType.WSTRING: encoding = 'w' if len(type.derefs) == 0 else 'W' else: encoding = 'N' return encoding def _has_member_docstring(bindings, member, overloads): """ Return True if a function/method has a docstring. """ auto_docstring = False # Check for any explicit docstrings and remember if there were any that # could be automatically generated. for overload in _callable_overloads(member, overloads): if overload.docstring is not None: return True if bindings.docstrings: auto_docstring = True if member.no_arg_parser: return False return auto_docstring def _member_docstring(sf, spec, bindings, member, overloads, is_method=False): """ Generate the docstring for all overloads of a function/method. Return True if the docstring was entirely automatically generated. """ NEWLINE = '\\n"\n"' auto_docstring = True # See if all the docstrings are automatically generated. all_auto = True any_implied = False for overload in _callable_overloads(member, overloads): if overload.docstring is not None: all_auto = False if overload.docstring.signature is not DocstringSignature.DISCARDED: any_implied = True # Generate the docstring. is_first = True for overload in _callable_overloads(member, overloads): if not is_first: sf.write(NEWLINE) # Insert a blank line if any explicit docstring wants to include a # signature. This maintains compatibility with previous versions. if any_implied: sf.write(NEWLINE) if overload.docstring is not None: if overload.docstring.signature is DocstringSignature.PREPENDED: _member_auto_docstring(sf, spec, bindings, overload, is_method) sf.write(NEWLINE) sf.write(_docstring_text(overload.docstring)) if overload.docstring.signature is DocstringSignature.APPENDED: sf.write(NEWLINE) _member_auto_docstring(sf, spec, bindings, overload, is_method) auto_docstring = False elif all_auto or any_implied: _member_auto_docstring(sf, spec, bindings, overload, is_method) is_first = False return auto_docstring def _member_auto_docstring(sf, spec, bindings, overload, is_method): """ Generate the automatic docstring for a function/method. """ if bindings.docstrings: _overload_auto_docstring(sf, spec, overload, is_method=is_method) def _has_class_docstring(bindings, klass): """ Return True if a class has a docstring. """ auto_docstring = False # Check for any explicit docstrings and remember if there were any that # could be automatically generated. if klass.docstring is not None: return True for ctor in klass.ctors: if ctor.access_specifier is AccessSpecifier.PRIVATE: continue if ctor.docstring is not None: return True if bindings.docstrings: auto_docstring = True if not klass.can_create: return False return auto_docstring def _class_docstring(sf, spec, bindings, klass): """ Generate the docstring for a class. """ NEWLINE = '\\n"\n"' # See if all the docstrings are automatically generated. all_auto = (klass.docstring is None) any_implied = False for ctor in klass.ctors: if ctor.access_specifier is AccessSpecifier.PRIVATE: continue if ctor.docstring is not None: all_auto = False if ctor.docstring.signature is not DocstringSignature.DISCARDED: any_implied = True # Generate the docstring. if all_auto: sf.write('\\1') if klass.docstring is not None and klass.docstring.signature is not DocstringSignature.PREPENDED: sf.write(_docstring_text(klass.docstring)) is_first = False else: is_first = True if klass.docstring is None or klass.docstring.signature is not DocstringSignature.DISCARDED: for ctor in klass.ctors: if ctor.access_specifier is AccessSpecifier.PRIVATE: continue if not is_first: sf.write(NEWLINE) # Insert a blank line if any explicit docstring wants to # include a signature. This maintains compatibility with # previous versions. if any_implied: sf.write(NEWLINE) if ctor.docstring is not None: if ctor.docstring.signature is DocstringSignature.PREPENDED: _ctor_auto_docstring(sf, spec, bindings, klass, ctor) sf.write(NEWLINE) sf.write(_docstring_text(ctor.docstring)) if ctor.docstring.signature is DocstringSignature.APPENDED: sf.write(NEWLINE) _ctor_auto_docstring(sf, spec, bindings, klass, ctor) elif all_auto or any_implied: _ctor_auto_docstring(sf, spec, bindings, klass, ctor) is_first = False if klass.docstring is not None and klass.docstring.signature is DocstringSignature.PREPENDED: if not is_first: sf.write(NEWLINE) sf.write(NEWLINE) sf.write(_docstring_text(klass.docstring)) def _ctor_auto_docstring(sf, spec, bindings, klass, ctor): """ Generate the automatic docstring for a ctor. """ if bindings.docstrings: py_name = fmt_scoped_py_name(klass.scope, klass.py_name.name) signature = fmt_signature_as_type_hint(spec, ctor.py_signature, need_self=False, exclude_result=True) sf.write(py_name + signature) def _docstring_text(docstring): """ Return the text of a docstring. """ text = docstring.text # Remove any single trailing newline. if text.endswith('\n'): text = text[:-1] s = '' for ch in text: if ch == '\n': # Let the compiler concatanate lines. s += '\\n"\n"' elif ch in r'\"': s += '\\' s += ch elif ch.isprintable(): s += ch else: s += f'\\{ord(ch):03o}' return s def _module_docstring(sf, module): """ Generate the definition of a module's optional docstring. """ if module.docstring is not None: sf.write( f''' "PyDoc_STRVAR(doc_mod_{module.py_name}, "{_docstring_text(module.docstring)}"); ''') def _get_void_ptr_cast(type): """ Return a void* cast for an argument if needed. """ # Generate a cast if the argument's type was a typedef. This allows us to # use typedef's to void* to hide something more complex that we don't # handle. if type.original_typedef is None: return '' return '(const void *)' if type.is_const else '(void *)' def _declare_limited_api(sf, py_debug, module=None): """ Declare the use of the limited API. """ if py_debug: return if module is None or module.use_limited_api: sf.write( ''' #if !defined(Py_LIMITED_API) #define Py_LIMITED_API #endif ''') def _plugin_signals_table(sf, spec, bindings, klass): """ Generate the PyQt signals table and return True if anything was generated. """ # Handle the trivial case. if not klass.is_qobject: return False is_signals = False # The signals must be grouped by name. for member in klass.members: member_nr = member.member_nr for overload in klass.overloads: if overload.common is not member or overload.pyqt_method_specifier is not PyQtMethodSpecifier.SIGNAL: continue if member_nr >= 0: # See if there is a non-signal overload. for non_sig in klass.overloads: if non_sig is not overload and non_sig.common is member and non_sig.pyqt_method_specifier is not PyQtMethodSpecifier.SIGNAL: break else: member_nr = -1 if not is_signals: is_signals = True _pyqt_emitters(sf, spec, klass) pyqt_version = '5' if _pyqt5(spec) else '6' sf.write( f''' /* Define this type's signals. */ static const pyqt{pyqt_version}QtSignal signals_{klass.iface_file.fq_cpp_name.as_word}[] = {{ ''') # We enable a hack that supplies any missing optional arguments. # We only include the version with all arguments and provide an # emitter function which handles the optional arguments. _pyqt_signal_table_entry(sf, spec, bindings, klass, overload, member_nr) member_nr = -1 if is_signals: sf.write(' {SIP_NULLPTR, SIP_NULLPTR, SIP_NULLPTR, SIP_NULLPTR}\n};\n') return is_signals def _has_optional_args(overload): """ Return True if an overload has optional arguments. """ args = overload.cpp_signature.args return len(args) != 0 and args[-1].default_value is not None def _pyqt_class_plugin(sf, spec, bindings, klass): """ Generate any extended class definition data for PyQt. Return True if anything was generated. """ is_signals = _plugin_signals_table(sf, spec, bindings, klass) # The PyQt6 support code doesn't assume the structure is generated. if _pyqt6(spec): generated = is_signals if klass.is_qobject and not klass.pyqt_no_qmetaobject: generated = True if klass.pyqt_interface is not None: generated = True if not generated: return False klass_name = klass.iface_file.fq_cpp_name.as_word pyqt_version = '5' if _pyqt5(spec) else '6' sf.write(f'\n\nstatic pyqt{pyqt_version}ClassPluginDef plugin_{klass_name} = {{\n') mo_ref = f'&{_scoped_class_name(spec, klass)}::staticMetaObject' if klass.is_qobject and not klass.pyqt_no_qmetaobject else 'SIP_NULLPTR' sf.write(f' {mo_ref},\n') if _pyqt5(spec): sf.write(f' {klass.pyqt_flags},\n') signals_ref = f'signals_{klass_name}' if is_signals else 'SIP_NULLPTR' sf.write(f' {signals_ref},\n') interface_ref = f'"{klass.pyqt_interface}"' if klass.pyqt_interface is not None else 'SIP_NULLPTR' sf.write(f' {interface_ref}\n') sf.write('};\n') return True def _global_function_table_entries(sf, spec, bindings, members): """ Generate the entries in a table of PyMethodDef for global functions. """ for member in members: if member.py_slot is None: py_name = _get_normalised_cached_name(member.py_name) sf.write(f' {{sipName_{py_name}, ') if member.no_arg_parser or member.allow_keyword_args: sf.write(f'SIP_MLMETH_CAST(func_{member.py_name.name}), METH_VARARGS|METH_KEYWORDS') else: sf.write(f'func_{member.py_name.name}, METH_VARARGS') docstring = _optional_ptr( _has_member_docstring(bindings, member, spec.module.overloads), 'doc_' + member.py_name.name) sf.write(f', {docstring}}},\n') def _enum_class_scope(spec, enum): """ Return the scope of an unscoped enum as a string. """ if enum.is_protected: scope_s = 'sip' + enum.scope.iface_file.fq_cpp_name.as_word elif enum.scope.is_protected: scope_s = _scoped_class_name(spec, enum.scope) else: scope_s = enum.scope.iface_file.fq_cpp_name.as_cpp return scope_s def _include_sip_h(sf, module): """ Generate the inclusion of sip.h. """ if module.py_ssize_t_clean: sf.write( ''' #define PY_SSIZE_T_CLEAN ''') sf.write( ''' #include "sip.h" ''') def _enum_member(spec, enum_member): """ Return an enum member as a string. """ if spec.c_bindings: return enum_member.cpp_name enum = enum_member.scope if enum.no_scope: scope_s = '' else: if enum.is_scoped: scope_s = '::' + enum.cached_fq_cpp_name.name elif isinstance(enum.scope, WrappedClass): scope_s = _enum_class_scope(spec, enum) elif isinstance(enum.scope, MappedType): scope_s = enum.scope.iface_file.fq_cpp_name.as_cpp else: # This can't happen. scope_s = '' scope_s += '::' return f'static_cast({scope_s}{enum_member.cpp_name})' def _type_needs_user_state(type): """ Return True if a type needs user state to be provided. """ return type.type is ArgumentType.MAPPED and type.definition.needs_user_state def _user_state_suffix(spec, type): """ Return the suffix for functions that have a variant that supports a user state. """ return 'US' if spec.abi_version >= (13, 0) and _type_needs_user_state(type) else '' def _exception_handler(sf, spec): """ Generate the exception handler for a module. """ need_decl = True for exception in spec.exceptions: if exception.iface_file.module is spec.module: if need_decl: sf.write( f''' /* Handle the exceptions defined in this module. */ bool sipExceptionHandler_{spec.module.py_name}(std::exception_ptr sipExcPtr) {{ try {{ std::rethrow_exception(sipExcPtr); }} ''') need_decl = False _catch_block(sf, spec, exception) if not need_decl: sf.write( ''' catch (...) {} return false; } ''') def _pyqt5(spec): """ Return True if the PyQt5 plugin was specified. """ return 'PyQt5' in spec.plugins def _pyqt6(spec): """ Return True if the PyQt6 plugin was specified. """ return 'PyQt6' in spec.plugins def _append_qualifier_defines(module, bindings, qualifier_defines): """ Append the #defines for each feature defined in a module to a list of them. """ for qualifier in module.qualifiers: qualifier_type_name = None if qualifier.type is QualifierType.TIME: if _qualifier_enabled(qualifier, bindings): qualifier_type_name = 'TIMELINE' elif qualifier.type is QualifierType.PLATFORM: if _qualifier_enabled(qualifier, bindings): qualifier_type_name = 'PLATFORM' elif qualifier.type is QualifierType.FEATURE: if qualifier.name not in bindings.disabled_features and qualifier.enabled_by_default: qualifier_type_name = 'FEATURE' if qualifier_type_name is not None: qualifier_defines.append(f'#define SIP_{qualifier_type_name}_{qualifier.name}') def _qualifier_enabled(qualifier, bindings): """ Return True if a qualifier is enabled. """ for tag in bindings.tags: if qualifier.name == tag: return qualifier.enabled_by_default return False def _overload_auto_docstring(sf, spec, overload, is_method=True): """ Generate the docstring for a single API overload. """ need_self = is_method and not overload.is_static signature = fmt_signature_as_type_hint(spec, overload.py_signature, need_self=need_self) sf.write(overload.common.py_name.name + signature) def _sequence_support(sf, spec, klass, overload): """ Generate extra support for sequences because the class has an overload that has been annotated with __len__. """ # We require a single int argument. if len(overload.py_signature.args) != 1: return arg0 = overload.py_signature.args[0] if not py_as_int(arg0): return # At the moment all we do is check that an index to __getitem__ is within # range so that the class supports Python iteration. In the future we # should add support for negative indices, slices, __setitem__ and # __delitem__ (which will require enhancements to the sip module ABI). if overload.common.py_slot is PySlot.GETITEM: index_arg = fmt_argument_as_name(spec, arg0, 0) sf.write( f''' if ({index_arg} < 0 || {index_arg} >= sipCpp->{klass.len_cpp_name}()) {{ PyErr_SetNone(PyExc_IndexError); return SIP_NULLPTR; }} ''') def _abi_has_next_exception_handler(spec): """ Return True if the ABI implements sipNextExceptionHandler(). """ return _abi_version_check(spec, (12, 9), (13, 1)) def _abi_has_working_char_conversion(spec): """ Return True if the ABI has working char to/from a Python integer converters (ie. char is not assumed to be signed). """ return _abi_version_check(spec, (12, 15), (13, 8)) def _abi_supports_array(spec): """ Return True if the ABI supports sip.array. """ return _abi_version_check(spec, (12, 11), (13, 4)) def _abi_version_check(spec, min_12, min_13): """ Return True if the ABI version meets minimum version requirements. """ return spec.abi_version >= min_13 or (min_12 <= spec.abi_version < (13, 0)) def _cached_name_ref(cached_name, as_nr=False): """ Return a reference to a cached name. """ prefix = 'sipNameNr_' if as_nr else 'sipName_' return prefix + _get_normalised_cached_name(cached_name) def _const_cast(spec, type, value): """ Return a value with an appropriate const_cast to a type. """ if type.is_const: cpp_type = fmt_argument_as_cpp_type(spec, type, plain=True, no_derefs=True) return f'const_cast<{cpp_type} *>({value})' return value def _callable_overloads(member, overloads): """ An iterator over the non-private and non-signal overloads. """ for overload in overloads: if overload.common is member and overload.access_specifier is not AccessSpecifier.PRIVATE and overload.pyqt_method_specifier is not PyQtMethodSpecifier.SIGNAL: yield overload def _gto_name(wrapped_object): """ Return the name of the generated type object for a wrapped object. """ fq_cpp_name = wrapped_object.fq_cpp_name if isinstance(wrapped_object, WrappedEnum) else wrapped_object.iface_file.fq_cpp_name return 'sipType_' + fq_cpp_name.as_word def _unique_class_ctors(spec, klass): """ An iterator over non-private ctors that have a unique C++ signature. """ for ctor in klass.ctors: if ctor.access_specifier is AccessSpecifier.PRIVATE: continue if ctor.cpp_signature is None: continue for do_ctor in klass.ctors: if do_ctor is ctor: yield ctor break if do_ctor.cpp_signature is not None and same_signature(spec, do_ctor.cpp_signature, ctor.cpp_signature): break def _unique_class_virtual_overloads(spec, klass): """ An iterator over non-private virtual overloads that have a unique C++ signature. """ for virtual_overload in klass.virtual_overloads: overload = virtual_overload.overload if overload.access_specifier is AccessSpecifier.PRIVATE: continue for do_virtual_overload in klass.virtual_overloads: if do_virtual_overload is virtual_overload: yield virtual_overload break do_overload = do_virtual_overload.overload if do_overload.cpp_name == overload.cpp_name and same_signature(spec, do_overload.cpp_signature, overload.cpp_signature): break def _module_supports_qt(spec): """ Return True if the module implements Qt support. """ return spec.pyqt_qobject is not None and spec.pyqt_qobject.iface_file.module is spec.module def _optional_ptr(is_ptr, name): """ Return an appropriate reference to an optional pointer. """ return name if is_ptr else 'SIP_NULLPTR' # The map of slots to C++ names. _SLOT_NAME_MAP = { PySlot.ADD: 'operator+', PySlot.SUB: 'operator-', PySlot.MUL: 'operator*', PySlot.TRUEDIV: 'operator/', PySlot.MOD: 'operator%', PySlot.AND: 'operator&', PySlot.OR: 'operator|', PySlot.XOR: 'operator^', PySlot.LSHIFT: 'operator<<', PySlot.RSHIFT: 'operator>>', PySlot.IADD: 'operator+=', PySlot.ISUB: 'operator-=', PySlot.IMUL: 'operator*=', PySlot.ITRUEDIV: 'operator/=', PySlot.IMOD: 'operator%=', PySlot.IAND: 'operator&=', PySlot.IOR: 'operator|=', PySlot.IXOR: 'operator^=', PySlot.ILSHIFT: 'operator<<=', PySlot.IRSHIFT: 'operator>>=', PySlot.INVERT: 'operator~', PySlot.CALL: 'operator()', PySlot.GETITEM: 'operator[]', PySlot.LT: 'operator<', PySlot.LE: 'operator<=', PySlot.EQ: 'operator==', PySlot.NE: 'operator!=', PySlot.GT: 'operator>', PySlot.GE: 'operator>=', } def _overload_cpp_name(overload): """ Return the C++ name of an overload. """ py_slot = overload.common.py_slot return overload.cpp_name if py_slot is None else _SLOT_NAME_MAP[py_slot] def _py_scope(scope): """ Return the Python scope by accounting for hidden C++ namespaces. """ return None if isinstance(scope, WrappedClass) and scope.is_hidden_namespace else scope def _release_gil(gil_action, bindings): """ Return True if the GIL is to be released. """ return bindings.release_gil if gil_action is GILAction.DEFAULT else gil_action is GILAction.RELEASE def _variables_in_scope(spec, scope, check_handler=True): """ An iterator over the variables in a scope. """ for variable in spec.variables: if _py_scope(variable.scope) is scope and variable.module is spec.module: if check_handler and variable.needs_handler: continue yield variable def _write_instances_table(sf, scope, instances, declaration_template): """ Write a table of instances. Return True if there was a table written. """ if len(instances) == 0: return False if scope is None: dict_type = 'module' suffix = '' else: dict_type = 'type' suffix = '_' + scope.iface_file.fq_cpp_name.as_word declaration = declaration_template.format(dict_type=dict_type, suffix=suffix) sf.write(f'\n\n{declaration} = {{\n') for instance in instances: entry = ', '.join(instance) sf.write(f' {{{entry}}},\n') sentinals = ', '.join('0' * len(instances[0])) sf.write(f' {{{sentinals}}}\n}};\n') return True class SourceFile: """ The encapsulation of a source file. """ def __init__(self, source_name, description, module, project, generated): """ Initialise the object. """ self._description = description self._module = module self.open(source_name, project) generated.append(source_name) def __enter__(self): """ Implement a context manager for the file. """ return self def __exit__(self, exc_type, exc_value, traceback): """ Implement a context manager for the file. """ self.close() def close(self): """ Close the source file. """ self._f.close() def open(self, source_name, project): """ Open a source file and make it current. """ self._f = open(source_name, 'w', encoding='UTF-8') self._line_nr = 1 self._write_header_comments(self._description, self._module, project.version_info) def write(self, s): """ Write a string while tracking the current line number. """ self._f.write(s) self._line_nr += s.count('\n') def write_code(self, code): """ Write some handwritten code. """ # A trivial case. if code is None: return # The code may be a single code block or a list of them. code_blocks = code if isinstance(code, list) else [code] # Another trivial case. if not code_blocks: return for code_block in code_blocks: self.write(f'#line {code_block.line_nr} "{self._posix_path(code_block.sip_file)}"\n') self.write(code_block.text) self.write(f'#line {self._line_nr + 1} "{self._posix_path(self._f.name)}"\n') @staticmethod def _posix_path(path): """ Return the POSIX format of a path. """ return path.replace('\\', '/') def _write_header_comments(self, description, module, version_info): """ Write the comments at the start of the file. """ version_info_s = f' *\n * Generated by SIP {SIP_VERSION_STR}\n' if version_info else '' copying_s = fmt_copying(module.copying, ' *') self.write( f'''/* * {description} {version_info_s}{copying_s} */ ''') class CompilationUnit(SourceFile): """ Encapsulate a compilation unit, ie. a C or C++ source file. """ def __init__(self, source_name, description, module, project, buildable, sip_api_file=True): """ Initialise the object. """ super().__init__(source_name, description, module, project, buildable.sources) self.write_code(module.unit_code) if sip_api_file: self.write(f'#include "sipAPI{module.py_name}.h"\n') sip-6.8.6/sipbuild/generator/outputs/extracts.py000066400000000000000000000024171464421045000220650ustar00rootroot00000000000000# SPDX-License-Identifier: BSD-2-Clause # Copyright (c) 2024 Phil Thompson from ...exceptions import UserException def output_extract(spec, extract_ref): """ Output an extract. """ # Get the id and file name from the reference. extract_id, _, extract_file = extract_ref.partition(':') if not extract_file: raise UserException( "an extract must be in the form 'id:file', not '{0}'".format( extract_ref)) # Get all the parts of this extract. ordered_parts = [] appended_parts = [] for extract in spec.extracts: if extract.id == extract_id: if extract.order < 0: appended_parts.append(extract) else: ordered_parts.append(extract) # Put the parts in the correct order. ordered_parts.sort(key=lambda e: e.order) ordered_parts.extend(appended_parts) if len(ordered_parts) == 0: raise UserException( "there is no extract defined with the identifier '{0}'".format( extract_id)) # Write the parts to the file. with open(extract_file, 'w', encoding='UTF-8') as ef: for extract_part in ordered_parts: ef.write(extract_part.text) sip-6.8.6/sipbuild/generator/outputs/formatters/000077500000000000000000000000001464421045000220405ustar00rootroot00000000000000sip-6.8.6/sipbuild/generator/outputs/formatters/__init__.py000066400000000000000000000014761464421045000241610ustar00rootroot00000000000000# SPDX-License-Identifier: BSD-2-Clause # Copyright (c) 2024 Phil Thompson # Publish the API. This is private to the rest of sip. from .argument import (fmt_argument_as_cpp_type, fmt_argument_as_name, fmt_argument_as_py_default_value, fmt_argument_as_py_type, fmt_argument_as_rest_ref, fmt_argument_as_type_hint) from .enum import (fmt_enum_as_cpp_type, fmt_enum_as_rest_ref, fmt_enum_as_type_hint) from .klass import (fmt_class_as_rest_ref, fmt_class_as_scoped_name, fmt_class_as_type_hint) from .misc import fmt_copying, fmt_scoped_py_name from .signature import (fmt_signature_as_cpp_declaration, fmt_signature_as_cpp_definition, fmt_signature_as_type_hint) from .value_list import (fmt_value_list_as_cpp_expression, fmt_value_list_as_rest_ref) sip-6.8.6/sipbuild/generator/outputs/formatters/argument.py000066400000000000000000000363311464421045000242420ustar00rootroot00000000000000# SPDX-License-Identifier: BSD-2-Clause # Copyright (c) 2024 Phil Thompson from ...scoped_name import STRIP_NONE from ...specification import ArgumentType, ArrayArgument, ClassKey, ValueType from ..type_hints import format_voidptr, TypeHintManager from .misc import fmt_scoped_py_name def fmt_argument_as_name(spec, arg, arg_nr): """ Return an argument as a name. """ if spec.module.use_arg_names and arg.name is not None and arg.type is not ArgumentType.ELLIPSIS: return arg.name.name return f'a{arg_nr}' def fmt_argument_as_cpp_type(spec, arg, name=None, scope=None, strip=STRIP_NONE, make_public=False, use_typename=True, plain=False, no_derefs=False, as_xml=False): """ Return an argument as a C++ type. """ original_typedef = arg.original_typedef nr_derefs = 0 if no_derefs else len(arg.derefs) is_const = arg.is_const and not plain is_reference = arg.is_reference and not plain s = '' if use_typename and original_typedef is not None and not original_typedef.no_type_name and arg.array is not ArrayArgument.ARRAY_SIZE: # Reverse the previous expansion of the typedef. if is_const and not original_typedef.type.is_const: s += 'const ' nr_derefs -= len(original_typedef.type.derefs) if original_typedef.type.is_reference: is_reference = False s += original_typedef.fq_cpp_name.cpp_stripped(strip) else: # A function type is handled differently because of the position of the # name. if arg.type is ArgumentType.FUNCTION: from .signature import fmt_signature_as_cpp_declaration s += fmt_argument_as_cpp_type(spec, arg.definition.result, scope=scope, strip=strip, as_xml=as_xml) s += ' (' + '*' * nr_derefs + name + ')(' s += fmt_signature_as_cpp_declaration(spec, arg.definition, scope=scope, as_xml=as_xml) s += ')' return s if is_const: s += 'const ' if arg.type in (ArgumentType.SBYTE, ArgumentType.SSTRING): s += 'signed char' elif arg.type in (ArgumentType.UBYTE, ArgumentType.USTRING): s += 'unsigned char' elif arg.type is ArgumentType.WSTRING: s += 'wchar_t' elif arg.type in (ArgumentType.BYTE, ArgumentType.ASCII_STRING, ArgumentType.LATIN1_STRING, ArgumentType.UTF8_STRING, ArgumentType.STRING): s += 'char' elif arg.type is ArgumentType.USHORT: s += 'unsigned short' elif arg.type is ArgumentType.SHORT: s += 'short' elif arg.type is ArgumentType.UINT: # Qt4 moc uses "uint" in signal signatures. We do all the time and # hope it is always defined. s += 'uint' elif arg.type in (ArgumentType.INT, ArgumentType.CINT): s += 'int' elif arg.type is ArgumentType.HASH: s += 'Py_hash_t' elif arg.type is ArgumentType.SSIZE: s += 'Py_ssize_t' elif arg.type is ArgumentType.SIZE: s += 'size_t' elif arg.type is ArgumentType.ULONG: s += 'unsigned long' elif arg.type is ArgumentType.LONG: s += 'long' elif arg.type is ArgumentType.ULONGLONG: s += 'unsigned long long' elif arg.type is ArgumentType.LONGLONG: s += 'long long' elif arg.type is ArgumentType.STRUCT: s += 'struct ' + arg.definition.as_cpp elif arg.type is ArgumentType.UNION: s += 'union ' + arg.definition.as_cpp elif arg.type is ArgumentType.CAPSULE: nr_derefs = 1 s += 'void' elif arg.type in (ArgumentType.FAKE_VOID, ArgumentType.VOID): s += 'void' elif arg.type in (ArgumentType.BOOL, ArgumentType.CBOOL): s += 'bool' elif arg.type in (ArgumentType.FLOAT, ArgumentType.CFLOAT): s += 'float' elif arg.type in (ArgumentType.DOUBLE, ArgumentType.CDOUBLE): s += 'double' elif arg.type is ArgumentType.DEFINED: # The only defined types still remaining are arguments to templates # and default values. if as_xml: s += arg.definition.as_py else: if spec.c_bindings: s += 'struct ' s += arg.definition.cpp_stripped(strip) elif arg.type is ArgumentType.MAPPED: s += fmt_argument_as_cpp_type(spec, arg.definition.type, scope=scope, strip=strip, as_xml=as_xml) elif arg.type is ArgumentType.CLASS: from .klass import fmt_class_as_scoped_name if spec.c_bindings: s += 'union ' if arg.definition.class_key is ClassKey.UNION else 'struct ' s += fmt_class_as_scoped_name(spec, arg.definition, scope=scope, strip=strip, make_public=make_public, as_xml=as_xml) elif arg.type is ArgumentType.TEMPLATE: from .template import fmt_template_as_cpp_type s += fmt_template_as_cpp_type(spec, arg.definition, strip=strip, as_xml=as_xml) elif arg.type is ArgumentType.ENUM: from .enum import fmt_enum_as_cpp_type s += fmt_enum_as_cpp_type(arg.definition, make_public=make_public, strip=strip) elif arg.type in (ArgumentType.PYOBJECT, ArgumentType.PYTUPLE, ArgumentType.PYLIST, ArgumentType.PYDICT, ArgumentType.PYCALLABLE, ArgumentType.PYSLICE, ArgumentType.PYTYPE, ArgumentType.PYBUFFER, ArgumentType.PYENUM, ArgumentType.ELLIPSIS): s += 'PyObject *' space_before_name = True for i in range(nr_derefs): # Note that we don't put a space before the '*' so that Qt normalised # signal signatures are correct. s += '*' space_before_name = False if arg.derefs[i]: s += ' const' space_before_name = True if is_reference: s += '&' if name: if space_before_name: s += ' ' s += name return s # The types that are implicitly pointers. _IMPLICIT_POINTERS = (ArgumentType.PYOBJECT, ArgumentType.PYTUPLE, ArgumentType.PYLIST, ArgumentType.PYDICT, ArgumentType.PYCALLABLE, ArgumentType.PYSLICE, ArgumentType.PYTYPE, ArgumentType.CAPSULE, ArgumentType.PYBUFFER, ArgumentType.PYENUM ) def fmt_argument_as_py_default_value(spec, arg, type_name, embedded=False, as_xml=False): """ Return the Python representation of an argument's default value. """ from .value_list import fmt_value_list_as_py_expression # Use any explicitly provided documentation. if arg.type_hints is not None and arg.type_hints.default_value is not None: return arg.type_hints.default_value # Translate some special cases. if len(arg.default_value) == 1 and arg.default_value[0].value_type is ValueType.NUMERIC: value = arg.default_value[0].value if value == 0 and ('voidptr' in type_name or len(arg.derefs) != 0 or arg.type in _IMPLICIT_POINTERS): return 'None' if arg.type in (ArgumentType.BOOL, ArgumentType.CBOOL): return 'True' if value else 'False' return fmt_value_list_as_py_expression(spec, arg.default_value, embedded=embedded, as_xml=as_xml) def fmt_argument_as_py_type(spec, arg, pep484=False, default_value=False, as_xml=False): """ Return an argument as a Python type. """ scope, name = _py_arg(spec, arg, pep484, as_xml) s = fmt_scoped_py_name(scope, name) if default_value and arg.default_value is not None: if arg.name is not None: s += ' ' + arg.name.name s += '=' + fmt_argument_as_py_default_value(spec, arg, name) return s def fmt_argument_as_rest_ref(spec, arg, out, as_xml=False): """ Return an argument as a reST reference. """ s = '' hint = _get_hint(arg, out) if hint is None: if arg.type is ArgumentType.CLASS: from .klass import fmt_class_as_rest_ref s += fmt_class_as_rest_ref(arg.definition) elif arg.type is ArgumentType.ENUM: if arg.definition.py_name is not None: from .enum import fmt_enum_as_rest_ref s += fmt_enum_as_rest_ref(arg.definition) else: s += 'int' elif arg.type is ArgumentType.MAPPED: # There would normally be a type hint. s += "unknown-type" else: s += fmt_argument_as_py_type(spec, arg, as_xml=as_xml) else: context = arg.definition if arg.type is ArgumentType.CLASS else None s += TypeHintManager(spec).as_rest_ref(hint, out, context, as_xml=as_xml) return s def fmt_argument_as_type_hint(spec, arg, defined, arg_nr=-1): """ Return an argument as a type hint. """ if arg.array is ArrayArgument.ARRAY_SIZE: return '' pep484 = defined is not None s = '' if arg_nr is None: # It's a variable. out = False elif arg_nr >= 0: # It's a callable input argument. out = False if arg.name is None: s += f'a{arg_nr}: ' else: name = _fix_py_keyword(arg.name.name) s += f'{name}: ' else: # It's a callable result or output argument. out = True hint = _get_hint(arg, out) # Assume pointers can be None unless specified otherwise. allow_none = arg.allow_none or (arg.type in (ArgumentType.CLASS, ArgumentType.MAPPED) and arg.definition.handles_none) if hint is None and allow_none: use_optional = True else: use_optional = (not arg.disallow_none and len(arg.derefs) != 0) if use_optional: s += 'typing.Optional[' if pep484 else 'Optional[' if arg.array is ArrayArgument.ARRAY: s += _sip_module_name(spec) + 'array[' if hint is None: if arg.type is ArgumentType.CLASS: from .klass import fmt_class_as_type_hint type_name = fmt_class_as_type_hint(spec, arg.definition, defined) elif arg.type is ArgumentType.ENUM: if arg.definition.py_name is not None: from .enum import fmt_enum_as_type_hint type_name = fmt_enum_as_type_hint(spec, arg.definition, defined) else: type_name = 'int' elif arg.type is ArgumentType.MAPPED: # There would normally be a type hint. type_name = 'typing.Any' if pep484 else 'Any' else: type_name = fmt_argument_as_py_type(spec, arg, pep484=pep484) else: type_hint_manager = TypeHintManager(spec) context = arg.definition if arg.type is ArgumentType.CLASS else None if pep484: type_name = type_hint_manager.as_type_hint(hint, out, context, defined) else: type_name = type_hint_manager.as_docstring(hint, out, context) s += type_name if arg.array is ArrayArgument.ARRAY: s += ']' if use_optional: s += ']' # See if the argument is optional. if arg_nr is not None and arg_nr >= 0 and arg.default_value is not None: # Historically .pyi files don't include specific default values. s += ' = ' s += '...' if pep484 else fmt_argument_as_py_default_value(spec, arg, type_name, embedded=True) return s _PYTHON_KEYWORDS = ( 'False', 'None', 'True', 'and', 'as', 'assert', 'break', 'class', 'continue', 'def', 'del', 'elif', 'else', 'except', 'finally', 'for', 'from', 'global', 'if', 'import', 'in', 'is', 'lambda', 'nonlocal', 'not', 'or', 'pass', 'raise', 'return', 'try', 'while', 'with', 'yield', 'exec', 'print', ) def _fix_py_keyword(word): """ Return a fixed word if it is a Python keyword (or has been at any time). """ if word in _PYTHON_KEYWORDS: word += '_' return word def _get_hint(arg, out): """ Return a raw type hint. """ # Use any explicit type hint unless the argument is constrained. if arg.type_hints is None: hint = None elif out: hint = arg.type_hints.hint_out elif arg.is_constrained: hint = None else: hint = arg.type_hints.hint_in return hint def _py_arg(spec, arg, pep484, as_xml): """ Return an argument as a 2-tuple of scope and name. """ type = arg.type definition = arg.definition sip_module_name = _sip_module_name(spec) scope = None name = "unknown-type" if type is ArgumentType.CLASS: scope = definition.scope name = definition.py_name.name elif type is ArgumentType.MAPPED: if definition.py_name is not None: name = definition.py_name.name elif type is ArgumentType.ENUM: if definition.py_name is None: name = 'int' else: scope = definition.scope name = definition.py_name.name elif type is ArgumentType.DEFINED: name = definition.as_py elif type is ArgumentType.CAPSULE: name = definition.base_name elif type in (ArgumentType.STRUCT, ArgumentType.UNION, ArgumentType.VOID): name = format_voidptr(spec, as_xml) elif type in (ArgumentType.STRING, ArgumentType.SSTRING, ArgumentType.USTRING): name = 'bytes' elif type in (ArgumentType.WSTRING, ArgumentType.ASCII_STRING, ArgumentType.LATIN1_STRING, ArgumentType.UTF8_STRING): name = 'bytes' if arg.array is ArrayArgument.ARRAY else 'str' elif type in (ArgumentType.BYTE, ArgumentType.SBYTE, ArgumentType.UBYTE, ArgumentType.USHORT, ArgumentType.UINT, ArgumentType.LONG, ArgumentType.LONGLONG, ArgumentType.ULONG, ArgumentType.ULONGLONG, ArgumentType.SHORT, ArgumentType.INT, ArgumentType.CINT, ArgumentType.SSIZE, ArgumentType.SIZE, ArgumentType.HASH): name = 'int' elif type in (ArgumentType.FLOAT, ArgumentType.CFLOAT, ArgumentType.DOUBLE, ArgumentType.CDOUBLE): name = 'float' elif type in (ArgumentType.BOOL, ArgumentType.CBOOL): name = 'bool' elif type in (ArgumentType.PYOBJECT, ArgumentType.ELLIPSIS): name = 'typing.Any' if pep484 else 'Any' elif type is ArgumentType.PYTUPLE: name = 'typing.Tuple' if pep484 else 'Tuple' elif type is ArgumentType.PYLIST: name = 'typing.List' if pep484 else 'List' elif type is ArgumentType.PYDICT: name = 'typing.Dict' if pep484 else 'Dict' elif type is ArgumentType.PYCALLABLE: name = 'typing.Callable[..., None]' if pep484 else 'Callable[..., None]' elif type is ArgumentType.PYSLICE: name = 'slice' elif type is ArgumentType.PYTYPE: name = 'type' elif type is ArgumentType.PYBUFFER: if pep484: name = sip_module_name + 'Buffer' else: # This replicates sip.pyi. name = f'Union[bytes, bytearray, memoryview, {sip_module_name}array, {sip_module_name}voidptr]' elif type is ArgumentType.PYENUM: name = 'enum.Enum' return scope, name def _sip_module_name(spec): """ Return the name of the sip module to be used as a prefix to an object in the module. """ sip_module = spec.sip_module return sip_module + '.' if sip_module else '' sip-6.8.6/sipbuild/generator/outputs/formatters/enum.py000066400000000000000000000041141464421045000233560ustar00rootroot00000000000000# SPDX-License-Identifier: BSD-2-Clause # Copyright (c) 2024 Phil Thompson from ...scoped_name import STRIP_NONE from ...specification import IfaceFileType from .misc import fmt_scoped_py_name, iface_is_defined def fmt_enum_member_as_rest_ref(enum_member): """ Return the fully qualified Python name of a member as a reST reference. """ enum = enum_member.scope module_name = enum.module.fq_py_name.name if enum.py_name is None: member_name = fmt_scoped_py_name(enum.scope, enum_member.py_name.name) return f':sip:ref:`~{module_name}.{member_name}`' enum_name = fmt_scoped_py_name(enum.scope, enum.py_name.name) member_name = enum_member.py_name.name return f':sip:ref:`~{module_name}.{enum_name}.{member_name}`' def fmt_enum_as_cpp_type(enum, make_public=False, strip=STRIP_NONE): """ Return an enum's fully qualified C++ type name. """ if enum.fq_cpp_name is None or (enum.is_protected and not make_public): return 'int' return enum.fq_cpp_name.cpp_stripped(strip) def fmt_enum_as_rest_ref(enum): """ Return a fully qualified Python name as a reST reference. """ module_name = enum.module.fq_py_name.name enum_name = fmt_scoped_py_name(enum.scope, enum.py_name.name) return f':sip:ref:`~{module_name}.{enum_name}`' def fmt_enum_as_type_hint(spec, enum, defined): """ Return the type hint. """ module = spec.module if enum.scope is None: # Global enums are defined early on. is_defined = True else: scope_iface = enum.scope.iface_file outer_scope = enum.scope.scope if scope_iface.type is IfaceFileType.CLASS else None is_defined = iface_is_defined(scope_iface, outer_scope, module, defined) quote = '' if is_defined else "'" # Include the module name if it is not the current one. module_name = enum.module.py_name + '.' if enum.module is not module and defined is not None else '' py_name = fmt_scoped_py_name(enum.scope, enum.py_name.name) return f'{quote}{module_name}{py_name}{quote}' sip-6.8.6/sipbuild/generator/outputs/formatters/klass.py000066400000000000000000000034371464421045000235360ustar00rootroot00000000000000# SPDX-License-Identifier: BSD-2-Clause # Copyright (c) 2024 Phil Thompson from ...scoped_name import STRIP_NONE from .misc import fmt_scoped_py_name, iface_is_defined from .template import fmt_template_as_cpp_type def fmt_class_as_rest_ref(klass): """ Return a fully qualified Python name as a reST reference. """ module_name = klass.iface_file.module.fq_py_name.name klass_name = fmt_scoped_py_name(klass.scope, klass.py_name.name) return f':sip:ref:`~{module_name}.{klass_name}`' def fmt_class_as_scoped_name(spec, klass, scope=None, strip=STRIP_NONE, make_public=False, as_xml=False): """ Return a class's scoped name. """ if klass.no_type_name: return fmt_template_as_cpp_type(spec, klass.template, strip=strip, as_xml=as_xml) # Protected classes have to be explicitly scoped. if klass.is_protected and not make_public: # This should never happen. if scope is None: scope = klass.iface_file return f'sip{scope.fq_cpp_name.as_word}::sip{klass.iface_file.fq_cpp_name.base_name}' return klass.iface_file.fq_cpp_name.cpp_stripped(strip) def fmt_class_as_type_hint(spec, klass, defined): """ Return the type hint. """ module = spec.module # We assume that an external class will be handled properly by some # handwritten type hint code. quote = '' if klass.external or iface_is_defined(klass.iface_file, klass.scope, module, defined) else "'" # Include the module name if it is not the current one. module_name = klass.iface_file.module.py_name + '.' if klass.iface_file.module is not module and defined is not None else '' py_name = fmt_scoped_py_name(klass.scope, klass.py_name.name) return f'{quote}{module_name}{py_name}{quote}' sip-6.8.6/sipbuild/generator/outputs/formatters/misc.py000066400000000000000000000025731464421045000233540ustar00rootroot00000000000000# SPDX-License-Identifier: BSD-2-Clause # Copyright (c) 2024 Phil Thompson def fmt_copying(copying, comment): """ Return a formatted %Copying text. """ s = ''.join([b.text for b in copying]).rstrip().replace('\n', '\n' + comment + ' ') if s: s = comment + '\n' + comment + ' ' + s + '\n' return s def fmt_scoped_py_name(scope, py_name): """ Return a formatted scoped Python name. """ if scope is None or scope.is_hidden_namespace: scope_s = '' else: scope_s = fmt_scoped_py_name(scope.scope, None) + scope.py_name.name + '.' if py_name is None: py_name_s = '' else: py_name_s = py_name return scope_s + py_name_s def iface_is_defined(iface_file, scope, module, defined): """ Return True if a type corresponding to an interface file has been defined in the context of a module. """ # It is implicitly defined if we are not keeping a record. if defined is None: return True # A type in another module would have been imported. if iface_file.module is not module: return True if iface_file not in defined: return False # Check all enclosing scopes have been defined as well. while scope is not None: if scope.iface_file not in defined: return False scope = scope.scope return True sip-6.8.6/sipbuild/generator/outputs/formatters/signature.py000066400000000000000000000063311464421045000244160ustar00rootroot00000000000000# SPDX-License-Identifier: BSD-2-Clause # Copyright (c) 2024 Phil Thompson from ...scoped_name import STRIP_NONE from ...specification import ArgumentType from .argument import (fmt_argument_as_cpp_type, fmt_argument_as_name, fmt_argument_as_type_hint) def fmt_signature_as_cpp_declaration(spec, cpp_signature, scope=None, strip=STRIP_NONE, make_public=False, as_xml=False): """ Return the C++ representation of a signature's arguments as a declaration. """ args = [fmt_argument_as_cpp_type(spec, arg, scope=scope, strip=strip, make_public=make_public, as_xml=as_xml) for arg in cpp_signature.args] return ', '.join(args) def fmt_signature_as_cpp_definition(spec, cpp_signature, scope=None, make_public=False): """ Return the C++ representation of a signature's arguments as a definition. """ args = [] for arg_nr, arg in enumerate(cpp_signature.args): arg_name = fmt_argument_as_name(spec, arg, arg_nr) args.append( fmt_argument_as_cpp_type(spec, arg, name=arg_name, scope=scope, make_public=make_public)) return ', '.join(args) def fmt_signature_as_type_hint(spec, py_signature, need_self=True, exclude_result=False, defined=None): """ Return a signature as a type hint. """ # Handle the input values. in_args = [] if need_self: in_args.append('self') nr_out = 0 for arg_nr, arg in enumerate(py_signature.args): if arg.is_out: nr_out += 1 if arg.is_in: type_hint = fmt_argument_as_type_hint(spec, arg, defined, arg_nr=arg_nr) if type_hint: in_args.append(type_hint) args_s = '(' + ', '.join(in_args) +')' if exclude_result: return args_s # Handle the output values. result = py_signature.result if result is None or (result.type is ArgumentType.VOID and len(result.derefs) == 0): is_result = False else: type_hints = result.type_hints # An empty type hint specifies a void return. if type_hints is not None and type_hints.hint_out is not None and type_hints.hint_out == '': is_result = False else: is_result = True if is_result or nr_out > 0: results_s = ' -> ' needs_tuple = ((is_result and nr_out > 0) or nr_out > 1) if needs_tuple: results_s += '(' if defined is None else 'typing.Tuple[' out_args = [] if is_result: type_hint = fmt_argument_as_type_hint(spec, result, defined) if type_hint: out_args.append(type_hint) for arg in py_signature.args: if arg.is_out: type_hint = fmt_argument_as_type_hint(spec, arg, defined) if type_hint: out_args.append(type_hint) results_s += ', '.join(out_args) if needs_tuple: results_s += ')' if defined is None else ']' elif defined is not None: results_s = ' -> None' else: # We don't show 'None' returns in docstrings. results_s = '' return f'{args_s}{results_s}' sip-6.8.6/sipbuild/generator/outputs/formatters/template.py000066400000000000000000000010131464421045000242200ustar00rootroot00000000000000# SPDX-License-Identifier: BSD-2-Clause # Copyright (c) 2024 Phil Thompson from ...scoped_name import STRIP_NONE from .signature import fmt_signature_as_cpp_declaration def fmt_template_as_cpp_type(spec, template, strip=STRIP_NONE, as_xml=False): """ Return the C++ representation of a template type. """ sig_s = fmt_signature_as_cpp_declaration(spec, template.types, strip=strip, as_xml=as_xml) return template.cpp_name.cpp_stripped(strip) + '<' + sig_s + '>' sip-6.8.6/sipbuild/generator/outputs/formatters/value_list.py000066400000000000000000000111241464421045000245600ustar00rootroot00000000000000# SPDX-License-Identifier: BSD-2-Clause # Copyright (c) 2024 Phil Thompson from ...specification import ValueType from .argument import fmt_argument_as_cpp_type, fmt_argument_as_py_type from .enum import fmt_enum_member_as_rest_ref def fmt_value_list_as_cpp_expression(spec, value_list): """ Return the C++ representation of a value list as an expression. """ return _expression(spec, value_list) def fmt_value_list_as_py_expression(spec, value_list, embedded=False, as_xml=False): """ The Python representation of a value list as an expression. """ return _expression(spec, value_list, as_python=True, embedded=embedded, as_xml=as_xml) def fmt_value_list_as_rest_ref(spec, value_list, as_xml=False): """ Return the Python representation of a value list as a reST reference. """ # The value must be a scoped name and we don't handle expressions. if len(value_list) != 1 or value_list[0].value_type is not ValueType.SCOPED: return None target = value_list[0].value # See if it is an attribute. for variable in spec.variables: if variable.fq_cpp_name == target: return fmt_variable_as_rest_ref(variable) # See if it is an enum member. target_scope = target.scope if target_scope is not None: target_scope.make_absolute() target_base_name = target.base_name for enum in spec.enums: # Look for the member name first before working out if it is the # correct enum. for member in enum.members: if member.cpp_name == target_base_name: if enum.is_scoped: # It's a scoped enum so the fully qualified name of the # enum must match the scope of the name. if target_scope is not None and enum.fq_cpp_name == target_scope: return fmt_enum_member_as_rest_ref(member) else: # It's a traditional enum so the scope of the enum must # match the scope of the name. if (enum.scope is None and target_scope is None) or (enum.scope is not None and target_scope is not None and enum.scope.iface_file.fq_cpp_name == target_scope): return fmt_enum_member_as_rest_ref(member) break return None def _expression(spec, value_list, as_python=False, embedded=False, as_xml=False): """ The representation of a value list as an expression. """ s = '' for value in value_list: if value.cast is not None and not as_python: s += '(' + value.cast.as_cpp + ')' if value.unary_operator is not None: s += value.unary_operator if value.value_type is ValueType.QCHAR: if value.value == '"' and embedded: s += "'\\\"'" elif value.value.isprintable(): s += "'" + value.value + "'" else: s += f"'\\{ord(value.value):03o}'" elif value.value_type is ValueType.STRING: quote = "\\\"" if embedded else "\"" s += quote for ch in value.value: escape = True if ch in "\\\"": pass elif ch == '\n': ch = 'n'; elif ch == '\r': ch = 'r' elif ch == '\t': ch = 't' else: escape = False if escape: s += "\\" s += ch s += quote elif value.value_type is ValueType.NUMERIC: s += str(value.value) if as_python else str(int(value.value)) elif value.value_type is ValueType.REAL: s += format(value.value, 'g') elif value.value_type is ValueType.SCOPED: s += value.value.as_py if as_python else value.value.as_cpp elif value.value_type is ValueType.FCALL: if as_python: s += fmt_argument_as_py_type(spec, value.value.result) else: s += fmt_argument_as_cpp_type(spec, value.value.result) args = [_expression(spec, arg, as_python=as_python, embedded=embedded, as_xml=as_xml) for arg in value.value.args] separator = ',' if as_xml else ', ' s += '(' + separator.join(args) + ')' elif value.value_type is ValueType.EMPTY: s += '{}' if value.binary_operator is not None: s += value.binary_operator return s sip-6.8.6/sipbuild/generator/outputs/formatters/variable.py000066400000000000000000000006221464421045000241770ustar00rootroot00000000000000# SPDX-License-Identifier: BSD-2-Clause # Copyright (c) 2024 Phil Thompson def fmt_variable_as_rest_ref(variable): """ Return the fully qualified Python name as a reST reference. """ module_name = variable.module.fq_py_name.name variable_name = fmt_scoped_py_name(self.scope, variable.py_name.name) return f':sip:ref:`~{module_name}.{variable_name}`' sip-6.8.6/sipbuild/generator/outputs/pyi.py000066400000000000000000000412721464421045000210330ustar00rootroot00000000000000# SPDX-License-Identifier: BSD-2-Clause # Copyright (c) 2024 Phil Thompson from ...version import SIP_VERSION_STR from ..python_slots import is_number_slot, reflected_slot from ..specification import (AccessSpecifier, ArgumentType, ArrayArgument, EnumBaseType, IfaceFileType, PyQtMethodSpecifier, PySlot, Signature) from ..utils import append_iface_file, find_method from .formatters import (fmt_argument_as_type_hint, fmt_class_as_type_hint, fmt_copying, fmt_scoped_py_name, fmt_signature_as_type_hint) def output_pyi(spec, project, pyi_filename): """ Output a .pyi file. """ with open(pyi_filename, 'w', encoding='UTF-8') as pf: # Write the header. version_info_s = f'#\n# Generated by SIP {SIP_VERSION_STR}\n' if project.version_info else '' copying_s = fmt_copying(spec.module.copying, '#') pf.write( f'''# The PEP 484 type hints stub file for the {spec.module.py_name} module. {version_info_s}{copying_s}''') if spec.is_composite: _composite_module(pf, spec) else: _module(pf, spec) def _composite_module(pf, spec): """ Output the type hints for a composite module. """ for mod in spec.module.all_imports: if mod.composite is spec.module: pf.write(f'from {mod.fq_py_name.name} import *\n') def _module(pf, spec): """ Output the type hints for an ordinary module. """ module = spec.module first = True # Generate the imports. Note that we assume the super-types are the # standard SIP ones. if spec.abi_version >= (13, 0): for enum in spec.enums: if enum.module is spec.module: first = _separate(pf, first=first) pf.write('import enum\n') break if spec.sip_module: first = _separate(pf, first=first) pf.write( f'''import typing import {spec.sip_module} ''') imports = [] for mod in module.all_imports: parts = mod.fq_py_name.name.split('.') if mod.fq_py_name.name == mod.py_name: imports.append('import ' + mod.py_name) else: scope = mod.fq_py_name.name[:-len(mod.py_name) - 1] imports.append('from ' + scope + ' import ' + mod.py_name) if imports: first = _separate(pf, first=first, minimum=1) pf.write('\n'.join(imports) + '\n') # Generate any exported type hint code and any module-specific type hint # code. first = _type_hint_code(pf, spec.exported_type_hint_code, first) first = _type_hint_code(pf, module.type_hint_code, first) # Generate the types - global enums must be first. _enums(pf, spec) # The list of enums and classes that have been defined at any particular # point so we know if they can be referenced directly rather than by their # names as a string. defined = [] for klass in spec.classes: if klass.iface_file.module is not module: continue if klass.external: continue if klass.no_type_hint: continue # Only handle non-nested classes here. if klass.scope is not None: continue # We can't handle extenders. if klass.real_class is not None: continue _class(pf, spec, klass, defined) for mapped_type in spec.mapped_types: if mapped_type.iface_file.module is not module: continue if mapped_type.py_name is not None: _mapped_type(pf, spec, mapped_type, defined) _variables(pf, spec, defined) first = True for member in module.global_functions: if member.py_slot is None: first = _separate(pf, first=first) _callable(pf, spec, member, module.overloads, defined) def _type_hint_code(pf, type_hint_code, first=True, indent=0): """ Output handwritten type hint code. """ s = '' for block in type_hint_code: if s == '': first = _separate(pf, first=first, indent=indent, minimum=1) else: s += '\n' need_indent = True for ch in block.text: if need_indent: s += _indent(indent) need_indent = False s += ch if ch == '\n': need_indent = True pf.write(s) return first def _class(pf, spec, klass, defined, indent=0): """ Output the type hints for a class. """ nr_overloads = 0 if not klass.is_hidden_namespace: _separate(pf, indent=indent) s = _indent(indent) s += f'class {klass.py_name.name}(' if klass.superclasses: s += ', '.join( [fmt_class_as_type_hint(spec, sc, defined) for sc in klass.superclasses]) elif klass.supertype is not None: # In ABI v12 the default supertype does not contain the fully # qualified name of the sip module so we fix it here. if spec.abi_version[0] == 12 and spec.sip_module and klass.supertype.name.startswith('sip.'): s += spec.sip_module + klass.supertype.name[4:] else: s += klass.supertype.name else: simple = 'simple' if klass.iface_file.type is IfaceFileType.NAMESPACE else '' s += f'{_sip_module_name(spec)}{simple}wrapper' # See if there is anything in the class body. for ctor in klass.ctors: if ctor.access_specifier is AccessSpecifier.PRIVATE: continue if ctor.no_type_hint: continue nr_overloads += 1 no_body = (klass.type_hint_code is None and nr_overloads == 0) if no_body: for overload in klass.overloads: if overload.access_specifier is AccessSpecifier.PRIVATE: continue if overload.no_type_hint: continue no_body = False break if no_body: for enum in spec.enums: if enum.scope is klass and not enum.no_type_hint: no_body = False break if no_body: for nested in spec.classes: if nested.scope is klass and not nested.no_type_hint: no_body = False break if no_body: for variable in spec.variables: if variable.scope is klass and not variable.no_type_hint: no_body = False break suffix = ' ...' if no_body else '' s += f'):{suffix}\n' pf.write(s) indent += 1 if klass.type_hint_code is not None: _type_hint_code(pf, [klass.type_hint_code], indent=indent) _enums(pf, spec, defined=defined, scope=klass, indent=indent) # Handle any nested classes. for nested in spec.classes: if nested.scope is klass and not nested.no_type_hint: _class(pf, spec, nested, defined, indent=indent) _variables(pf, spec, defined, scope=klass, indent=indent) first = True for ctor in klass.ctors: if ctor.access_specifier is AccessSpecifier.PRIVATE: continue if ctor.no_type_hint: continue first = _separate(pf, first=first, indent=indent) _ctor(pf, spec, ctor, nr_overloads > 1, defined, indent) first = True for member in klass.members: first = _separate(pf, first=first, indent=indent) _callable(pf, spec, member, klass.overloads, defined, is_method=not klass.is_hidden_namespace, indent=indent) for prop in klass.properties: first = _separate(pf, first=first, indent=indent) getter = find_method(klass, prop.getter) if getter is not None: _property(pf, spec, prop, False, getter, klass.overloads, defined, indent) if prop.setter is not None: setter = find_method(klass, prop.setter) if setter is not None: _property(pf, spec, prop, True, setter, klass.overloads, defined, indent) if not klass.is_hidden_namespace: # Keep track of what has been defined so that forward references are no # longer required. append_iface_file(defined, klass.iface_file) def _mapped_type(pf, spec, mapped_type, defined): """ Output the type hints for a mapped type. """ # See if there is anything in the mapped type body. no_body = (len(mapped_type.members) == 0) if no_body: for enum in spec.enums: if enum.scope is mapped_type and not enum.no_type_hint: no_body = FALSE break if not no_body: _separate(pf) pf.write(f'class {mapped_type.py_name.name}({_sip_module_name(spec)}wrapper):\n') _enums(pf, spec, defined=defined, scope=mapped_type, indent=1) first = True for member in mapped_type.members: first = _separate(pf, first=first, indent=1) _callable(pf, spec, member, member.overloads, defined, is_method=True, indent=1) # Keep track of what has been defined so that forward references are no # longer required. append_iface_file(defined, mapped_type.iface_file) def _ctor(pf, spec, ctor, overloaded, defined, indent): """ Output a ctor type hint. """ if overloaded: s = _indent(indent) s += '@typing.overload\n' pf.write(s) s = _indent(indent) s += 'def __init__' s += _python_signature(spec, ctor.py_signature, defined) s += ': ...\n' pf.write(s) def _enums(pf, spec, defined=None, scope=None, indent=0): """ Output the type hints for all the enums in a scope. """ for enum in spec.enums: if enum.module is not spec.module: continue if enum.scope is not scope: continue if enum.no_type_hint: continue _separate(pf, indent=indent) if enum.py_name is not None: enum_type = fmt_scoped_py_name(enum.scope, enum.py_name.name) superclass = 'int' if spec.abi_version >= (13, 0): if enum.base_type is EnumBaseType.ENUM: superclass = 'enum.Enum' elif enum.base_type is EnumBaseType.FLAG: superclass = 'enum.Flag' elif enum.base_type in (EnumBaseType.INT_ENUM, EnumBaseType.UINT_ENUM): superclass = 'enum.IntEnum' elif enum.base_type is EnumBaseType.INT_FLAG: superclass = 'enum.IntFlag' # Handle an enum with no members. for member in enum.members: if not member.no_type_hint: trivial = '' break else: trivial = ' ...' s = _indent(indent) s+= f'class {enum.py_name.name}({superclass}):{trivial}\n' pf.write(s) indent += 1 else: enum_type = 'int' for member in enum.members: if not member.no_type_hint: s = _indent(indent) s += f'{member.py_name.name} = ... # type: {enum_type}\n' pf.write(s) if enum.py_name is not None: indent -= 1 def _variables(pf, spec, defined, scope=None, indent=0): """ Output the type hints for all the variables in a scope. """ first = True for variable in spec.variables: if variable.module is not spec.module: continue if variable.scope is not scope: continue if variable.no_type_hint: continue py_type = fmt_argument_as_type_hint(spec, variable.type, defined, arg_nr=None) first = _separate(pf, first=first, indent=indent) s = _indent(indent) s += f'{variable.py_name.name} = ... # type: {py_type}\n' pf.write(s) def _callable(pf, spec, member, overloads, defined, is_method=False, indent=0): """ Output the type hints for a callable. """ # Get the non-reflected and reflected overloads. nonreflected_overloads = [] reflected_overloads = [] for overload in overloads: if overload.access_specifier is AccessSpecifier.PRIVATE: continue if overload.common is not member: continue if overload.no_type_hint: continue # Signals can have the same name as ordinary methods however # 'typing.overload' cannot be used with ClassVar. We choose to # generate a type hint for the signal rather than any method. if overload.pyqt_method_specifier is PyQtMethodSpecifier.SIGNAL: scope = '' if spec.module.py_name == 'QtCore' else 'QtCore.' s = _indent(indent) s += f'{overload.common.py_name.name}: typing.ClassVar[{scope}pyqtSignal]\n' pf.write(s) return if is_number_slot(overload.common.py_slot) and overload.is_reflected: reflected_overloads.append(overload) else: nonreflected_overloads.append(overload) # Handle each non-reflected overload. overloaded = len(nonreflected_overloads) > 1 first_overload = True for overload in nonreflected_overloads: _overload(pf, spec, overload, overloaded, first_overload, is_method, defined, indent) first_overload = False # Handle each reflected overload. overloaded = len(reflected_overloads) > 1 first_overload = True for overload in reflected_overloads: _overload(pf, spec, overload, overloaded, first_overload, is_method, defined, indent) first_overload = False def _property(pf, spec, prop, is_setter, member, overloads, defined, indent): """ Output the type hints for a property. """ for overload in overloads: if overload.access_specifier is AccessSpecifier.PRIVATE: continue if overload.common is not member: continue if overload.no_type_hint: continue s = _indent(indent) if is_setter: s += f'@{prop.name.name}.setter\n' else: s += '@property\n' pf.write(s) signature = _python_signature(spec, overload.py_signature, defined) s = _indent(indent) s += f'def {prop.name.name}{signature}: ...\n' pf.write(s) break def _overload(pf, spec, overload, overloaded, first_overload, is_method, defined, indent): """ Output the type hints for a single overload. """ # mypy recommends using 'object' as the argument type. is_eq_slot = (overload.common.py_slot in (PySlot.EQ, PySlot.NE)) # The recommendation means any subsequent overloads are pointless. if is_eq_slot: if not first_overload: return elif overloaded: pf.write(_indent(indent) + '@typing.overload\n') if is_method and overload.is_static: pf.write(_indent(indent) + '@staticmethod\n') py_name = overload.common.py_name.name py_signature = overload.py_signature s = _indent(indent) if is_eq_slot: signature = '(self, other: object)' else: need_self = (is_method and not overload.is_static) if is_number_slot(overload.common.py_slot): # Use the reflected name if appropriate. if overload.is_reflected: py_name = reflected_slot(overload.common.py_slot) # A global slot will still have both arguments so pick the relevant # one. if len(py_signature.args) > 1: if overload.is_reflected: arg = py_signature.args[0] else: arg = py_signature.args[1] py_signature = Signature(args=[arg], result=py_signature.result) signature = _python_signature(spec, py_signature, defined, need_self=need_self) s += f'def {py_name}{signature}: ...\n' pf.write(s) def _python_signature(spec, signature, defined, need_self=True): """ Return a Python signature. """ return fmt_signature_as_type_hint(spec, signature, need_self=need_self, defined=defined) def _indent(indent): """ Return the required indentation. """ return ' ' * (4 * indent) def _separate(pf, first=True, indent=0, minimum=None): """ Output a newline if not already done. """ if first: pf.write('\n' if indent else '\n\n') elif minimum is not None: pf.write('\n' * minimum) return False def _sip_module_name(spec): """ Return the name of the sip module to be used as a prefix to an object in the module. """ return spec.sip_module + '.' if spec.sip_module else '' sip-6.8.6/sipbuild/generator/outputs/type_hints.py000066400000000000000000000421731464421045000224210ustar00rootroot00000000000000# SPDX-License-Identifier: BSD-2-Clause # Copyright (c) 2024 Phil Thompson from dataclasses import dataclass, field from enum import auto, Enum from typing import List, Optional, Union from weakref import WeakKeyDictionary from ...exceptions import UserException from ..scoped_name import ScopedName from ..specification import MappedType, WrappedClass, WrappedEnum # The types defined in the typing module. _TYPING_MODULE = ( 'Any', 'NoReturn', 'Tuple', 'Union', 'Optional', 'Callable', 'Type', 'Literal', 'ClassVar', 'Final', 'Annotated', 'AnyStr', 'Protocol', 'NamedTuple', 'Dict', 'List', 'Set', 'FrozenSet', 'IO', 'TextIO', 'BinaryIO', 'Pattern', 'Match', 'Text', 'Iterable', 'Iterator', 'Generator', 'Mapping', 'Sequence', ) class NodeType(Enum): """ The node types. """ TYPING = auto() CLASS = auto() MAPPED_TYPE = auto() ENUM = auto() OTHER = auto() class ParseState(Enum): """ The different parse states of a type hint. """ REQUIRED = auto() PARSED = auto() @dataclass class ManagedTypeHint: """ Encapsulate a managed type hint. """ # The type hint being managed. type_hint: str # The rendered docstring. as_docstring: Optional[str] = None # The rendered reST reference. as_rest_ref: Optional[str] = None # The parse state. parse_state: ParseState = ParseState.REQUIRED # The root node. root: Optional['TypeHintNode'] = None @dataclass class TypeHintNode: """ Encapsulate a node of a parsed type hint. """ # The type. type: NodeType # The list of child nodes. children: Optional[List['TypeHintNode']] = None # The type-dependent definition. definition: Optional[Union[str, MappedType, WrappedClass, WrappedEnum]] = None class TypeHintManager: """ A manager for type hints on behalf of a Specification object. """ # The map of specification objects and the corresponding manager object. _spec_manager_map = WeakKeyDictionary() def __new__(cls, spec): """ Return the existing manager for a specification or create a new one if necessary. """ try: manager = cls._spec_manager_map[spec] except KeyError: manager = object.__new__(cls) manager._spec = spec manager._managed_type_hints = {} cls._spec_manager_map[spec] = manager return manager def as_docstring(self, type_hint, out, context): """ Return the type hint as a docstring. """ managed_type_hint = self._get_managed_type_hint(type_hint, out) # See if it needs rendering. if managed_type_hint.as_docstring is None: managed_type_hint.as_docstring = self._render(managed_type_hint, out, context=context) return managed_type_hint.as_docstring def as_rest_ref(self, type_hint, out, context, as_xml=False): """ Return the type hint with appropriate reST references. """ managed_type_hint = self._get_managed_type_hint(type_hint, out) # See if it needs rendering. if managed_type_hint.as_rest_ref is None: managed_type_hint.as_rest_ref = self._render(managed_type_hint, out, context=context, rest_ref=True, as_xml=as_xml) return managed_type_hint.as_rest_ref def as_type_hint(self, type_hint, out, context, defined): """ Return the type hint as a type hint. """ managed_type_hint = self._get_managed_type_hint(type_hint, out) # Note that we always render type hints as they can be different before # and after a class or enum is defined in the .pyi file. return self._render(managed_type_hint, out, pep484=True, context=context, defined=defined) def _get_managed_type_hint(self, type_hint, out): """ Return the unique (for the specification) managed type hint for a type hint. """ try: hint_in, hint_out = self._managed_type_hints[type_hint] except KeyError: hint_in = ManagedTypeHint(type_hint) hint_out = ManagedTypeHint(type_hint) self._managed_type_hints[type_hint] = (hint_in, hint_out) return hint_out if out else hint_in def _parse(self, managed_type_hint, out): """ Ensure a type hint has been parsed. """ if managed_type_hint.parse_state is ParseState.REQUIRED: managed_type_hint.root = self._parse_node(out, managed_type_hint.type_hint) managed_type_hint.parse_state = ParseState.PARSED def _parse_node(self, out, text, start=0, end=None): """ Return a single node of a parsed type hint. """ if end is None: end = len(text) top_level = True else: top_level = False # Find the name and any opening and closing brackets. start = self._strip_leading(text, start, end) name_start = start end = self._strip_trailing(text, start, end) name_end = end children = None i = text[start:end].find('[') if i >= 0: i += start children = [] # The last character must be a closing bracket. if text[end - 1] != ']': raise UserException( f"type hint '{text}': ']' expected at position {end}") # Find the end of any name. name_end = self._strip_trailing(text, name_start, i) while True: # Skip the opening bracket or comma. i += 1 # Find the next comma, if any. depth = 0 for part_i in range(i, end): if text[part_i] == '[': depth += 1 elif text[part_i] == ']' and depth != 0: depth -= 1 elif text[part_i] in ',]' and depth == 0: # Recursively parse this part. children.append(self._parse_node(out, text, i, part_i)) i = part_i break else: break # See if we have a name. if name_start != name_end: # Get the name. */ name = text[name_start:name_end] # See if it is an object in the typing module. if name in _TYPING_MODULE: if name == 'Union': # If there are no children assume it is because they have # been omitted. if len(children) == 0: return None # Flatten any unions. flattened = [] for child in children: if child.type is NodeType.TYPING and child.definition == 'Union': for grandchild in child.children: flattened.append(grandchild) else: flattened.append(child) children = flattened node = TypeHintNode(NodeType.TYPING, children=children, definition=name) else: # Search for the type. node = self._lookup_type(name, out, children) else: # At the top level we must have brackets and they must not be empty. if top_level and (children is None or len(children) == 0): raise UserException( f"type hint '{text}': must have non-empty brackets") # Return the representation of brackets. node = TypeHintNode(NodeType.TYPING, children=children) return node def _render(self, managed_type_hint, out, context=None, defined=None, pep484=False, rest_ref=False, as_xml=False, context_stack=None): """ Return a rendered type hint. """ self._parse(managed_type_hint, out) if managed_type_hint.root is not None: if context_stack is None: context_stack = [] # Add any outer context. if context is not None: context_stack.append(context) s = self._render_node(managed_type_hint.root, out, pep484, rest_ref, defined, as_xml, context_stack) if context is not None: context_stack.pop() else: s = self._maybe_any_object(managed_type_hint.type_hint, pep484, as_xml) return s def _render_node(self, node, out, pep484, rest_ref, defined, as_xml, context_stack): """ Render a single node. """ if node.type is NodeType.TYPING: if node.definition is None: s = '' elif pep484: s = 'typing.' + node.definition else: s = node.definition if node.children is not None: s += self._render_children(node, out, pep484, rest_ref, defined, as_xml, context_stack) return s if node.type is NodeType.CLASS: klass = node.definition # Get any managed type hint. type_hints = klass.type_hints if type_hints is not None: type_hint = type_hints.hint_out if out else type_hints.hint_in if type_hint is not None: managed_type_hint = self._get_managed_type_hint(type_hint, out) # If the type hint isn't in the current context then render # it. if klass not in context_stack: context_stack.append(klass) s = self._render(managed_type_hint, out, pep484=pep484, rest_ref=rest_ref, defined=defined, as_xml=as_xml, context_stack=context_stack) context_stack.pop() return s from .formatters import (fmt_class_as_rest_ref, fmt_class_as_type_hint, fmt_scoped_py_name) if rest_ref: return fmt_class_as_rest_ref(klass) if pep484: return fmt_class_as_type_hint(self._spec, klass, defined) return fmt_scoped_py_name(klass.scope, klass.py_name.name) if node.type is NodeType.MAPPED_TYPE: mapped_type = node.definition # Get any managed type hint. type_hints = mapped_type.type_hints if type_hints is not None: type_hint = type_hints.hint_out if out else type_hints.hint_in if type_hint is not None: managed_type_hint = self._get_managed_type_hint(type_hint, out) return self._render(managed_type_hint, out, pep484=pep484, rest_ref=rest_ref, defined=defined, as_xml=as_xml, context_stack=context_stack) # This will only happen if the mapped type doesn't have type hints. return mapped_type.cpp_name.name if node.type is NodeType.ENUM: from .formatters import (fmt_enum_as_rest_ref, fmt_enum_as_type_hint, fmt_scoped_py_name) enum = node.definition if rest_ref: return fmt_enum_as_rest_ref(enum) if pep484: return fmt_enum_as_type_hint(self._spec, enum, defined) return fmt_scoped_py_name(enum.scope, enum.py_name.name) # We only render children for docstrings. if node.children is not None and defined is None: return node.definition + self._render_children(node, out, pep484, rest_ref, defined, as_xml, context_stack) return self._maybe_any_object(node.definition, pep484, as_xml) def _render_children(self, node, out, pep484, rest_ref, defined, as_xml, context_stack): """ Render the children of a node. """ children = [] for child in node.children: # For Callable the first argument is in and the rest (ie. the # second) is out. if node.definition == 'Callable': fixed_out = child is not node.children[0] else: fixed_out = out children.append( self._render_node(child, fixed_out, pep484, rest_ref, defined, as_xml, context_stack)) return '[' + ', '.join(children) + ']' def _lookup_enum(self, name, scopes): """ Lookup an enum using its C/C++ name. """ for enum in self._spec.enums: if enum.fq_cpp_name is not None and enum.fq_cpp_name.base_name == name and enum.scope in scopes: return enum return None def _lookup_class(self, name, scope): """ Lookup a class/struct/union using its C/C++ name. """ for klass in self._spec.classes: if klass.scope is scope and not klass.external and klass.iface_file.fq_cpp_name.base_name == name: return klass return None def _lookup_mapped_type(self, name): """ Lookup a mapped type using its C/C++ name. """ for mapped_type in self._spec.mapped_types: if mapped_type.cpp_name is not None and mapped_type.cpp_name.name == name: return mapped_type return None def _lookup_type(self, name, out, children): """ Look up a qualified Python type and return the corresponding node. """ # Start searching at the global level. scope_klass = None scope_mapped_type = None # We allow both Python and C++ scope separators. scoped_name = ScopedName.parse(name.replace('.', '::')) for part_i, part in enumerate(scoped_name): is_last_part = ((part_i + 1) == len(scoped_name)) # See if it's an enum. enum = self._lookup_enum(part, (scope_klass, scope_mapped_type)) if enum is not None: if is_last_part: return TypeHintNode(NodeType.ENUM, definition=enum) # There is some left so the whole lookup has failed. break # If we have a mapped type scope then we must be looking for an # enum, which we have failed to find. if scope_mapped_type is not None: break if scope_klass is None: # We are looking at the global level, so see if it is a mapped # type. mapped_type = self._lookup_mapped_type(part) if mapped_type is not None: # If we have used the whole name then the lookup has # succeeded. if is_last_part: return TypeHintNode(NodeType.MAPPED_TYPE, definition=mapped_type) # Otherwise this is the scope for the next part. scope_mapped_type = mapped_type if scope_mapped_type is None: # If we get here then it must be a class. klass = self._lookup_class(part, scope_klass) if klass is None: break # If we have used the whole name then the lookup has succeeded. if is_last_part: return TypeHintNode(NodeType.CLASS, definition=klass) # Otherwise this is the scope for the next part. scope_klass = klass # If we have run out of name then the lookup has failed. if is_last_part: break # Nothing was found. return TypeHintNode(NodeType.OTHER, definition=name, children=children) def _maybe_any_object(self, hint, pep484, as_xml): """ Return a hint taking into account that it may be any sort of object. """ if hint == 'Any': return self._any_object(pep484) # Don't worry if the voidptr name is qualified in any way. if hint.endswith('voidptr'): return format_voidptr(self._spec, as_xml) return hint @staticmethod def _any_object(pep484): """ Return a hint taking into account that it may be any sort of object. """ return 'typing.Any' if pep484 else 'object' @staticmethod def _strip_leading(text, start, end): """ Return the index of the first non-space of a string. """ while start < end and text[start] == ' ': start += 1 return start @staticmethod def _strip_trailing(text, start, end): """ Return the index after the last non-space of a string. """ while end > start and text[end - 1] == ' ': end -= 1 return end def format_voidptr(spec, as_xml): """ Return the representation of a voidptr in the context of either a type hint, XML or a docstring. """ voidptr = spec.sip_module + ('.' if spec.sip_module else '') + 'voidptr' if as_xml: return f':py:class:`~{voidptr}`' return voidptr sip-6.8.6/sipbuild/generator/outputs/xml.py000066400000000000000000000300071464421045000210240ustar00rootroot00000000000000# SPDX-License-Identifier: BSD-2-Clause # Copyright (c) 2024 Phil Thompson from xml.etree.ElementTree import Element, SubElement from ..python_slots import is_number_slot, reflected_slot from ..scoped_name import ScopedName, STRIP_GLOBAL from ..specification import (AccessSpecifier, ArgumentType, ArrayArgument, IfaceFileType, KwArgs, PyQtMethodSpecifier, PySlot, Transfer) from .formatters import (fmt_argument_as_py_default_value, fmt_argument_as_rest_ref, fmt_class_as_rest_ref, fmt_scoped_py_name, fmt_signature_as_cpp_declaration, fmt_value_list_as_rest_ref) # The schema version number. _XML_VERSION_NR = '0' def output_xml(spec, module_name): """ Return the root Module element of the XML for a module. """ # Note that we don't yet handle mapped types, templates or exceptions. module = spec.module if module.py_name != module_name: return None root = Element('Module', version=_XML_VERSION_NR, name=module.py_name) for klass in spec.classes: if klass.iface_file.module is module and not klass.external: _class(root, spec, module, klass) for klass in module.proxies: _class(root, spec, module, klass) _enums(root, spec, module) _variables(root, spec, module) for member in module.global_functions: _function(root, spec, member, module.overloads) return root def _realname(scope, member=None): """ Return a 'realname' attribute containing a fully qualified C/C++ name. """ if scope is None: # The member should have a name in this context. fq_cpp_name = member else: if isinstance(scope, ScopedName): fq_cpp_name = scope else: fq_cpp_name = scope.iface_file.fq_cpp_name fq_cpp_name = fq_cpp_name.cpp_stripped(STRIP_GLOBAL) if member is not None: fq_cpp_name += '::' + member return fq_cpp_name def _class(parent, spec, module, klass): """ Output the XML for a class. """ if klass.is_opaque: return SubElement(parent, 'OpaqueClass', name=fmt_scoped_py_name(klass.scope, klass.py_name.name)) if klass.is_hidden_namespace: parent_klass = parent else: attrib = {} if klass.pickle_code is not None: attrib['pickle'] = '1' if klass.convert_to_type_code is not None: attrib['convert'] = '1' if klass.convert_from_type_code is not None: attrib['convertfrom'] = '1' if klass.real_class is not None: attrib['extends'] = klass.real_class.iface_file.module.py_name if klass.pyqt_flags_enums is not None: attrib['flagsenums'] = ' '.join(klass.pyqt_flags_enums) if len(klass.superclasses) != 0: attrib['inherits'] = ' '.join( [fmt_class_as_rest_ref(sc) for sc in klass.superclasses]) parent_klass = SubElement(parent, 'Class', attrib, name=fmt_scoped_py_name(klass.scope, klass.py_name.name), realname=_realname(klass.iface_file.fq_cpp_name)) for ctor in klass.ctors: if ctor.access_specifier is not AccessSpecifier.PRIVATE: _ctor(parent_klass, spec, klass, ctor) _enums(parent_klass, spec, module, klass) _variables(parent_klass, spec, module, klass) for member in klass.members: _function(parent_klass, spec, member, klass.overloads, klass) def _enums(parent, spec, module, scope=None): """ Output the XML for all the enums in a scope. """ for enum in spec.enums: if enum.module is module and enum.scope is scope: if enum.py_name is None: for member in enum.members: SubElement(parent, 'Member', name=fmt_scoped_py_name(enum.scope, member.py_name.name), realname=_realname(enum.scope, member.cpp_name), const='1', typename='int') else: enum_el = SubElement(parent, 'Enum', name=fmt_scoped_py_name(enum.scope, enum.py_name.name), realname=_realname(enum.fq_cpp_name)) for member in enum.members: name = fmt_scoped_py_name(enum.scope, enum.py_name.name) + '.' + member.py_name.name SubElement(enum_el, 'EnumMember', name=name, realname=_realname(enum.fq_cpp_name, member.cpp_name)) def _variables(parent, spec, module, scope=None): """ Output the XML for all the variables in a scope. """ for variable in spec.variables: if variable.module is module and variable.scope is scope: attrib = {} if variable.type.is_const or scope is None: attrib['const'] = '1' if variable.is_static: attrib['static'] = '1' SubElement(parent, 'Member', attrib, name=fmt_scoped_py_name(variable.scope, variable.py_name.name), realname=_realname(variable.fq_cpp_name), typename=_typename(spec, variable.type)) def _ctor(parent, spec, scope, ctor): """ Output the XML for a ctor. """ attrib = {} if _has_cpp_signature(ctor.cpp_signature): attrib['cppsig'] = _cpp_signature(spec, ctor.cpp_signature) function_el = SubElement(parent, 'Function', attrib, name=fmt_scoped_py_name(scope, '__init__'), realname=_realname(scope, '__init__')) for arg in ctor.py_signature.args: if arg.is_in: _argument(function_el, spec, arg, ctor.kw_args) if arg.is_out: _argument(function_el, spec, arg, ctor.kw_args, out=True) def _function(parent, spec, member, overloads, scope=None): """ Output the XML for a function. """ for overload in overloads: if overload.common is member and overload.access_specifier is not AccessSpecifier.PRIVATE: if overload.pyqt_method_specifier is PyQtMethodSpecifier.SIGNAL: attrib = {} if _has_cpp_signature(overload.cpp_signature): attrib['cppsig'] = _cpp_signature(spec, overload.cpp_signature) signal_el = SubElement(parent, 'Signal', attrib, name=fmt_scoped_py_name(scope, member.py_name.name), realname=_realname(scope, overload.cpp_name)) for arg in overload.py_signature.args: _argument(signal_el, spec, arg, overload.kw_args) else: extends = None is_static = (scope is None or scope.iface_file.type is IfaceFileType.NAMESPACE or overload.is_static) if scope is None and member.py_slot is not None and overload.py_signature.args[0].type is ArgumentType.CLASS: extends = overload.py_signature.args[0].definition is_static = False _overload(parent, spec, scope, overload, extends, is_static) def _overload(parent, spec, scope, overload, extends, is_static): """ Output the XML for an overload. """ if overload.is_reflected: name = reflected_slot(overload.common.py_slot) else: name = None if name is None: name = overload.common.py_name.name cpp_name = overload.cpp_name else: cpp_name = name attrib = {} if _has_cpp_signature(overload.cpp_signature): attrib['cppsig'] = _cpp_signature(spec, overload.cpp_signature, is_const=overload.is_const) if overload.is_abstract: attrib['abstract'] = '1' if is_static: attrib['static'] = '1' if overload.pyqt_method_specifier is PyQtMethodSpecifier.SLOT: attrib['slot'] = '1' if overload.is_virtual: attrib['virtual'] = '1' if extends is not None: attrib['extends'] = fmt_scoped_py_name(extends.scope, extends.py_name.name) function_el = SubElement(parent, 'Function', attrib, name=fmt_scoped_py_name(scope, name), realname=_realname(scope, cpp_name)) # An empty type hint specifies a void return. result = overload.py_signature.result if result.type_hints is not None and result.type_hints.hint_out is not None and result.type_hints.hint_out == '': no_result = True else: no_result = (result.type is ArgumentType.VOID and len(result.derefs) == 0) if not no_result: _argument(function_el, spec, overload.py_signature.result, KwArgs.NONE, out=True, transfer_result=overload.transfer is Transfer.TRANSFER_BACK) # Ignore the first argument of non-reflected number slots and the second # argument of reflected number slots. might_ignore = (is_number_slot(overload.common.py_slot) and len(overload.py_signature.args) == 2) for a, arg in enumerate(overload.py_signature.args): if might_ignore: if a == 0 and not overload.is_reflected: continue if a == 1 and overload.is_reflected: continue if arg.is_in: _argument(function_el, spec, arg, overload.kw_args) if arg.is_out: _argument(function_el, spec, arg, overload.kw_args, out=True) # Argument types that imply handwritten code. _HANDWRITTEN_CODE_TYPES = ( ArgumentType.PYOBJECT, ArgumentType.PYTUPLE, ArgumentType.PYLIST, ArgumentType.PYDICT, ArgumentType.PYCALLABLE, ArgumentType.PYSLICE, ArgumentType.PYTYPE, ArgumentType.PYBUFFER, ArgumentType.PYENUM, ArgumentType.CAPSULE, ) def _has_cpp_signature(signature): """ Return True if there is a C/C++ signature. """ if signature is None: return False # See if there are any arguments that could only have come from handwritten # code. for arg in signature.args: if arg.type in _HANDWRITTEN_CODE_TYPES: return False return True def _cpp_signature(spec, cpp_signature, is_const=False): """ Return the XML for a C++ signature. """ args = fmt_signature_as_cpp_declaration(spec, cpp_signature, strip=STRIP_GLOBAL, make_public=True, as_xml=True) const = ' const' if is_const else '' return f'({args}){const}' def _argument(parent, spec, arg, kw_args, out=False, transfer_result=False): """ Ouput the XML for an argument. """ if arg.array is not ArrayArgument.ARRAY_SIZE: attrib = {} if not out: if arg.allow_none: attrib['allownone'] = '1' if arg.disallow_none: attrib['disallownone'] = '1' if arg.transfer is Transfer.TRANSFER: attrib['transfer'] = 'to' elif arg.transfer is Transfer.TRANSFER_THIS: attrib['transfer'] = 'this' if transfer_result or arg.transfer is Transfer.TRANSFER_BACK: attrib['transfer'] = 'back' SubElement(parent, 'Return' if out else 'Argument', attrib, typename=_typename(spec, arg, kw_args=kw_args, out=out)) def _typename(spec, arg, kw_args=KwArgs.NONE, out=False): """ Return the XML for a type. """ s = '' # Handle the argument name. if not out and arg.name is not None: if kw_args is KwArgs.ALL or (kw_args is KwArgs.OPTIONAL and arg.default_value is not None): s += arg.name.name + ': ' arg_rest_ref = fmt_argument_as_rest_ref(spec, arg, out, as_xml=True) s += arg_rest_ref if not out and arg.name is not None and arg.default_value is not None: s += ' = ' # Try and convert the value to a reST reference. We don't try very # hard but will get most cases. rest_ref = fmt_value_list_as_rest_ref(spec, arg.default_value) if rest_ref is None: rest_ref = fmt_argument_as_py_default_value(spec, arg, arg_rest_ref, as_xml=True) s += rest_ref return s sip-6.8.6/sipbuild/generator/parser/000077500000000000000000000000001464421045000174235ustar00rootroot00000000000000sip-6.8.6/sipbuild/generator/parser/__init__.py000066400000000000000000000002761464421045000215410ustar00rootroot00000000000000# SPDX-License-Identifier: BSD-2-Clause # Copyright (c) 2024 Phil Thompson # Publish the API. This is private to the rest of sip. from .parser import parse sip-6.8.6/sipbuild/generator/parser/annotations.py000066400000000000000000000166421464421045000223430ustar00rootroot00000000000000# SPDX-License-Identifier: BSD-2-Clause # Copyright (c) 2024 Phil Thompson class DottedName(str): """ Encapsulate a dotted name. A dedicated type is used (rather than a str) because we need to be able to distinguish it from a quoted string when used as the value of an annotation. """ pass class InvalidAnnotation(Exception): """ An invalid annotation. """ def __init__(self, name, message, use): """ Initialise the exception. """ self._text = "{0} {1}".format(name, message) # The value to use for the annotation. self.use = use def __str__(self): """ Return the exception as a user friendly string. """ return self._text class RequiredAnnotation(InvalidAnnotation): """ A required annotation. """ def __init__(name, use): """ Initialise the exception. """ super().__init__(name, "requires a value", use=use) def validate_annotation_value(pm, p, symbol, name, value): """ Return a valid value for the annotation or raise an InvalidAnnotation exception. """ try: validator = _ANNOTATION_TYPES[name] except KeyError: raise InvalidAnnotation(name, "is not a known annotation", use=None) return validator(pm, p, symbol, name, value) def bind(validator, **proto_kw): """ Return a function that when called with a validator function and prototype keyword arguments will itself return a function that will create an annotation-specific validator. """ # This takes the prototype validator-specific keyword arguments and returns # a function that will itself create a validator with annotation-specific # arguments based on the prototypes. def proto_validator(**bound_kw): # Create the annotation specific keyword arguments by taking the # prototypes and updating them with the ones bound to the specific # annotation. kw = proto_kw.copy() kw.update(bound_kw) # This takes the name and value of the annotation and calls the # validator along with the annotation-specific keyword arguments. def bound_validator(pm, p, symbol, name, value): return validator(pm, p, symbol, name, value, **kw) return bound_validator return proto_validator def validate_boolean(pm, p, symbol, name, value): """ Return a valid boolean value. """ if value is None: return True raise InvalidAnnotation(name, "must not have a value", use=False) boolean = bind(validate_boolean) def validate_integer(pm, p, symbol, name, value, *, optional): """ Return a valid, possibly optional, integer. """ if value is None: if optional: return None raise RequiredAnnotation(name, use=0) if not isinstance(value, int): raise InvalidAnnotation(name, "must be an integer", use=0) return value integer = bind(validate_integer, optional=False) def validate_name(pm, p, symbol, name, value, *, allow_dots, optional): """ Return a valid, possibly optional, possibly dotted name. """ if value is None: if optional: return '' raise RequiredAnnotation(name, use='') if not isinstance(value, DottedName): raise InvalidAnnotation(name, "must be an unquoted name", use='') if '.' in value and not allow_dots: raise InvalidAnnotation(name, "cannot contain '.'", use='') return value name = bind(validate_name, allow_dots=False, optional=False) def validate_string(pm, p, symbol, name, value): """ Return a valid string value. """ if not isinstance(value, str): raise InvalidAnnotation(name, "must be a quoted string", use='') # Handle any embedded selectors. for part in value.split(';'): if ':' not in part: return part.strip() selector, subvalue = part.split(':', maxsplit=1) if selector.startswith('!'): selector = selector[1:] inverted = True else: inverted = False if pm.evaluate_feature_or_platform(p, symbol, selector, inverted): return subvalue.strip() # No value was selected so ignore the annotation completely. return None string = bind(validate_string) def validate_string_list(pm, p, symbol, name, value): """ Return a valid string list value. """ if not isinstance(value, str): raise InvalidAnnotation(name, "must be a quoted string", use=[]) return value.split(' ') string_list = bind(validate_string_list) # The annotations and the type of their values. _ANNOTATION_TYPES = { '__imatmul__': boolean(), '__len__': boolean(), '__matmul__': boolean(), 'AbortOnException': boolean(), 'Abstract': boolean(), 'AllowNone': boolean(), 'Array': boolean(), 'ArraySize': boolean(), 'AutoGen': name(optional=True), 'BaseType': name(), 'Capsule': boolean(), 'Constrained': boolean(), 'Deprecated': boolean(), 'Default': boolean(), 'DelayDtor': boolean(), 'DisallowNone': boolean(), 'ExportDerived': boolean(), 'External': boolean(), 'Encoding': string(), 'Factory': boolean(), 'FileExtension': string(), 'GetWrapper': boolean(), 'HoldGIL': boolean(), 'In': boolean(), 'KeepReference': integer(optional=True), 'KeywordArgs': string(), 'Metatype': name(allow_dots=True), 'Mixin': boolean(), 'NewThread': boolean(), 'NoArgParser': boolean(), 'NoAssignmentOperator': boolean(), 'NoCopy': boolean(), 'NoCopyCtor': boolean(), 'NoDefaultCtor': boolean(), 'NoDefaultCtors': boolean(), 'NoDerived': boolean(), 'NoRaisesPyException': boolean(), 'NoRelease': boolean(), 'NoScope': boolean(), 'NoSetter': boolean(), 'NoTypeHint': boolean(), 'NoTypeName': boolean(), 'NoVirtualErrorHandler': boolean(), 'Numeric': boolean(), 'Out': boolean(), 'PostHook': name(), 'PreHook': name(), 'PyInt': boolean(), 'PyName': name(), 'PyQtFlags': integer(), 'PyQtFlagsEnums': string_list(), 'PyQtInterface': string(), 'PyQtNoQMetaObject': boolean(), 'RaisesPyException': boolean(), 'ReleaseGIL': boolean(), 'ResultSize': boolean(), 'ScopesStripped': integer(), 'Sequence': boolean(), 'Supertype': name(allow_dots=True), 'Transfer': boolean(), 'TransferBack': boolean(), 'TransferThis': boolean(), 'TypeHint': string(), 'TypeHintIn': string(), 'TypeHintOut': string(), 'TypeHintValue': string(), 'VirtualErrorHandler': name(), } sip-6.8.6/sipbuild/generator/parser/parser.py000066400000000000000000000013631464421045000212740ustar00rootroot00000000000000# SPDX-License-Identifier: BSD-2-Clause # Copyright (c) 2024 Phil Thompson from .parser_manager import ParserManager def parse(sip_file, hex_version, encoding, abi_version, tags, disabled_features, protected_is_public, include_dirs, sip_module, is_strict=True): """ Parse a .sip file and return a 3-tuple of a Specification object, a list of Module objects and a list of the .sip files that specify the module to be generated. A UserException is raised if there was an error. """ return ParserManager( hex_version, encoding, abi_version, tags, disabled_features, protected_is_public, include_dirs, sip_module, is_strict).parse( sip_file) sip-6.8.6/sipbuild/generator/parser/parser_manager.py000066400000000000000000002361141464421045000227720ustar00rootroot00000000000000# SPDX-License-Identifier: BSD-2-Clause # Copyright (c) 2024 Phil Thompson from functools import partial import os from ...bindings_configuration import get_bindings_configuration from ...exceptions import deprecated, UserException from ..error_log import ErrorLog from ..instantiations import instantiate_class from ..python_slots import invalid_global_slot, slot_name_detail_map from ..scoped_name import ScopedName from ..specification import (AccessSpecifier, Argument, ArgumentType, ArrayArgument, CachedName, ClassKey, CodeBlock, Constructor, DocstringFormat, DocstringSignature, EnumBaseType, GILAction, IfaceFile, IfaceFileType, KwArgs, MappedType, Member, Module, Overload, PyQtMethodSpecifier, PySlot, Qualifier, QualifierType, Signature, SourceLocation, Specification, Transfer, TypeHints, WrappedClass, WrappedException, WrappedEnum, WrappedEnumMember) from ..templates import encoded_template_name, same_template_signature from ..utils import (argument_as_str, cached_name, find_iface_file, normalised_scoped_name, same_base_type) from . import rules from . import tokens from .annotations import InvalidAnnotation, validate_annotation_value from .ply import lex, yacc class ParserManager: """ This object manages the actual lexer and parser objects providing them with state and utility functions. """ def __init__(self, hex_version, encoding, abi_version, tags, disabled_features, protected_is_public, include_dirs, sip_module, is_strict): """ Initialise the manager. """ # Create the lexer. self._lexer = lex.lex(module=tokens) self._lexer.pm = self # Create the parser. self._parser = yacc.yacc(module=rules, debug=False) self._parser.pm = self # This is a hack to give p_error() access to the current parser object. rules.parser = self._parser # The list of class templates. Each element is a 2-tuple of the # template arguments and the class itself. self.class_templates = [] # Public state. self.tags = tags self.spec = Specification( tuple([int(v) for v in abi_version.split('.')]), is_strict, sip_module) # The module is initially unnamed. self.modules = [self.spec.module] self.c_bindings = None self.code_block = None self.module_state = None self.paren_depth = 0 self.parsing_template = False self.parsing_virtual = False self.raw_sip_file = None self.skip_stack = [False] # Private state. self._hex_version = hex_version self._encoding = encoding self._disabled_features = disabled_features self._protected_is_public = protected_is_public self._include_dirs = include_dirs self._template_arg_classes = [] self._scope_stack = [] self._error_log = ErrorLog() self._file_stack = [] self._pending_module_state = None self._input = None self._all_sip_files = [] self._sip_file = None self._sip_files = [] def complete_class(self, p, symbol, annotations, has_body): """ Complete the definition of the class that is the current scope, pop it and return it. """ klass = self.scope if has_body: if klass.scope is not None: if klass.iface_file.fq_cpp_name.scope != klass.scope.iface_file.fq_cpp_name: self.parser_error(p, symbol, "a scoped name cannot be specified in the definition of a class/struct/union") elif len(klass.superclasses) != 0: self.parser_error(p, symbol, "the class/struct has super-classes but no definition"); else: klass.is_opaque = True # Get the Python name and see if it is different to the C++ name. py_name = self.get_py_name(klass.iface_file.fq_cpp_name.base_name, annotations) klass.py_name = cached_name(self.spec, py_name) klass.no_type_hint = annotations.get('NoTypeHint', False) metatype = annotations.get('Metatype') if metatype is not None: klass.metatype = cached_name(self.spec, metatype) supertype = annotations.get('Supertype') if supertype is not None: klass.supertype = cached_name(self.spec, supertype) klass.export_derived = annotations.get('ExportDerived', False) klass.mixin = annotations.get('Mixin', False) file_extension = annotations.get('FileExtension') if file_extension is not None: klass.iface_file.file_extension = file_extension pyqt_flags_enums = self._get_plugin_annotation(p, symbol, annotations, 'PyQtFlagsEnums', 'PyQt5') if pyqt_flags_enums is not None: klass.pyqt_flags_enums = pyqt_flags_enums klass.pyqt_flags = 1 pyqt_flags = self._get_plugin_annotation(p, symbol, annotations, 'PyQtFlags', 'PyQt5') if pyqt_flags is not None: klass.pyqt_flags = pyqt_flags klass.pyqt_no_qmetaobject = annotations.get('PyQtNoQMetaObject', False) klass.pyqt_interface = annotations.get('PyQtInterface') if klass.is_opaque: klass.external = annotations.get('External', False) else: # A default dtor is public. if klass.dtor is None: klass.dtor = AccessSpecifier.PUBLIC klass.no_default_ctors = annotations.get('NoDefaultCtors', False) # Provide a default ctor if required. if len(klass.ctors) == 0 and not klass.no_default_ctors: signature = Signature(result=Argument(ArgumentType.VOID)) ctor = Constructor(AccessSpecifier.PUBLIC, py_signature=signature, cpp_signature=signature) klass.default_ctor = ctor klass.ctors.append(ctor) klass.can_create = True # Determine the default ctor if required. if klass.default_ctor is None: last_resort = None for ctor in klass.ctors: if ctor.access_specifier is not AccessSpecifier.PUBLIC: continue if len(ctor.py_signature.args) == 0 or ctor.py_signature.args[0].default_value is not None: klass.default_ctor = ctor break # The last resort is the first public ctor. if last_resort is None: last_resort = ctor else: klass.default_ctor = last_resort klass.deprecated = annotations.get('Deprecated', False) if klass.convert_to_type_code is not None and annotations.get('AllowNone', False): klass.handles_none = True if annotations.get('Abstract', False): klass.is_abstract = True klass.is_incomplete = True klass.can_create = False klass.delay_dtor = annotations.get('DelayDtor', False) if klass.delay_dtor: self.module_state.module.has_delayed_dtors = True # There are subtle differences between the add and concat methods # and the multiply and repeat methods. The number versions can # have their operands swapped and may return NotImplemented. If # the user has used the /Numeric/ annotation or there are other # numeric operators then we use add/multiply. Otherwise, if the # user has used the /Sequence/ annotation or there are indexing # operators then we use concat/repeat. seq_might = seq_not = False for md in klass.members: if md.py_slot in (PySlot.GETITEM, PySlot.SETITEM, PySlot.DELITEM): # This might be a sequence. seq_might = True if md.py_slot in (PySlot.SUB, PySlot.ISUB, PySlot.MOD, PySlot.IMOD, PySlot.FLOORDIV, PySlot.IFLOORDIV, PySlot.TRUEDIV, PySlot.ITRUEDIV, PySlot.POS, PySlot.NEG): # This is definately not a sequence. seq_not = True default_to_sequence = (not seq_not and seq_might) for md in klass.members: # Ignore if it is explicitly numeric. if md.is_numeric: continue if md.is_sequence or default_to_sequence: if md.py_slot is PySlot.ADD: md.py_slot = PySlot.CONCAT elif md.py_slot is PySlot.IADD: md.py_slot = PySlot.ICONCAT elif md.py_slot is PySlot.MUL: md.py_slot = PySlot.REPEAT elif md.py_slot is PySlot.IMUL: md.py_slot = PySlot.IREPEAT if self.in_main_module: klass.iface_file.cpp_name.used = True klass.py_name.used = True self.pop_scope() # Check the name in the current scope (ie. the class's parent scope). self.check_attributes(p, symbol, py_name, ignore=klass) # Check that external classes have only been declared at the global # scope. if klass.external and self.scope is not None: self.parser_error(p, symbol, "/External/ classes/structs/unions can only be declared in the global scope") return klass def define_class(self, p, symbol, class_key, scoped_name, annotations, superclasses=None): """ Create a new class and make it the current scope. """ klass = self.new_class(p, symbol, IfaceFileType.CLASS, normalised_scoped_name(scoped_name, self.scope), virtual_error_handler=annotations.get('VirtualErrorHandler'), type_hints=self.get_type_hints(p, symbol, annotations)) klass.class_key = class_key klass.superclasses = superclasses self.push_scope(klass, AccessSpecifier.PRIVATE if class_key is ClassKey.CLASS else AccessSpecifier.PUBLIC) def disambiguate_token(self, value, keywords): """ Disambiguate a token by inspecting its value. """ # This seems to be needed because it's not possible to get lex() to do # it. The problem seems to be that you can't control the order in # which lex() applies its regular expressions despite what the # documentation says. It seems that tokens that are specific to a # state are always added after everything else no matter where they # appear in the file. if value in keywords: token_type = value elif value == '...': token_type = 'ELLIPSIS' elif value.startswith('.'): token_type = 'FILE_PATH' else: for marker in ('/', '..', '-'): if marker in value: token_type = 'FILE_PATH' break else: if '.' in value: token_type = 'DOTTED_NAME' else: token_type = 'NAME' return token_type def find_class(self, p, symbol, iface_file_type, fq_cpp_name, tmpl_arg=False): """ Return a WrappedClass object for a C++ name creating it if necessary. """ return self._find_class_with_iface_file( self.find_iface_file(p, symbol, fq_cpp_name, iface_file_type), tmpl_arg=tmpl_arg) def find_exception(self, p, symbol, fq_cpp_name, raise_code=None): """ Find an exception, optionally creating a new one. """ # Note that we don't normalise the name. iface_file = self.find_iface_file(p, symbol, fq_cpp_name, IfaceFileType.EXCEPTION) # See if it is an existing one. for w_exception in self.spec.exceptions: if w_exception.iface_file is iface_file: return w_exception # If it is an exception interface file then we have never seen this # name before. We require that exceptions are defined before being # used, but don't make the same requirement of classes (for reasons of # backwards compatibility). Therefore the name must be reinterpreted # as a (as yet undefined) class. if raise_code is not None: if iface_file.type is not IfaceFileType.EXCEPTION: self.parser_error(p, symbol, "there is already a class with the same name or the exception has been used before being defined") class_exception = None else: # The C++ name must be the name of a class which implements an # exception. if iface_file.type is IfaceFileType.EXCEPTION: iface_file.type = IfaceFileType.CLASS class_exception = self._find_class_with_iface_file(iface_file) # Create a new one. w_exception = WrappedException(iface_file, raise_code, class_exception=class_exception) self.spec.exceptions.append(w_exception) return w_exception def new_class(self, p, symbol, iface_file_type, fq_cpp_name, virtual_error_handler=None, type_hints=None): """ Create a new, unannotated class and add it to the current scope. """ if self.scope_access_specifier is AccessSpecifier.PRIVATE: self.parser_error(p, symbol, "classes, structs, unions and namespaces must be in the public or protected sections") scope = self.scope if scope is not None: if self.scope_access_specifier is AccessSpecifier.PROTECTED and not self._protected_is_public: scope.is_protected = True if iface_file_type is IfaceFileType.CLASS: scope.needs_shadow = True # Header code from outer scopes is also included. type_header_code = scope.iface_file.type_header_code else: type_header_code = None if bool(self.c_bindings): # C structs and unions are always global types no matter where they # are defined. fq_cpp_name = ScopedName(fq_cpp_name.base_name) scope = None klass = self.find_class(p, symbol, iface_file_type, fq_cpp_name) # Check it hasn't already been defined. if iface_file_type is not IfaceFileType.NAMESPACE and klass.iface_file.module is not None: self.parser_error(p, symbol, "the class/struct/union has already been defined") # Complete the initialisation. klass.scope = scope klass.iface_file.module = self.module_state.module klass.virtual_error_handler = virtual_error_handler klass.type_hints = type_hints if type_header_code is not None: klass.iface_file.type_header_code.extend(type_header_code) # See if it is a namespace extender. if iface_file_type is IfaceFileType.NAMESPACE: for ns in self.spec.classes: if ns is klass: continue if ns.iface_file.type is not IfaceFileType.NAMESPACE: continue if ns.iface_file.fq_cpp_name != klass.iface_file.fq_cpp_name: continue klass.real_class = ns if self.in_main_module: ns.iface_file.needed = True return klass def _find_class_with_iface_file(self, iface_file, tmpl_arg=False): """ Return a WrappedClass object for an interface file creating it if necessary. """ # See if it already exists. for klass in self.spec.classes: if klass.iface_file is iface_file: if not self.parsing_template: try: self._template_arg_classes.remove(klass) except ValueError: pass return klass # Create a new one. klass = WrappedClass(iface_file, cached_name(self.spec, iface_file.fq_cpp_name.base_name), None) if tmpl_arg: self._template_arg_classes.append(klass) # Use the same ordering as the old parser. self.spec.classes.insert(0, klass) return klass def add_ctor(self, p, symbol, arg_list, annotations, *, exceptions, cpp_signature, docstring, premethod_code, method_code): """ Create a Constructor and add it to the current scope. """ scope = self.scope # Error checks. if p[symbol] != scope.iface_file.fq_cpp_name.base_name: self.parser_error(p, symbol, "constructor doesn't have the same name as its class") if bool(self.c_bindings) and len(arg_list) != 0 and method_code is None: self.parser_error(p, symbol, "constructors with arguments in C modules must specify %MethodCode") if self.scope_pyqt_method_specifier is not None: self.parser_error(p, symbol, "constructors must be in the public, protected or private sections") # Handle the access specifier. access_specifier = self.scope_access_specifier if access_specifier is AccessSpecifier.PROTECTED and self._protected_is_public: access_specifier = AccessSpecifier.PUBLIC # Handle the signatures allowing it to be used like a function # signature. py_signature = Signature(args=arg_list, result=Argument(ArgumentType.VOID)) self._check_ellipsis(p, symbol, py_signature) # Configure the constructor. ctor = Constructor(access_specifier, py_signature) if annotations.get("NoDerived", False): if cpp_signature is not None: self.parser_error(p, symbol, "/NoDerived/ may not be specified with an explicit C++ signature") if method_code is None: self.parser_error(p, symbol, "%MethodCode must also be specified if /NoDerived/ is specified") elif cpp_signature is None: ctor.cpp_signature = py_signature else: self._check_ellipsis(p, symbol, cpp_signature) ctor.cpp_signature = cpp_signature if annotations.get("Default", False): if scope.default_ctor is None: scope.default_ctor = ctor else: self.parser_error(p, symbol, "/Default/ has already been specified for another constructor") ctor.docstring = docstring ctor.gil_action = self._get_gil_action(p, symbol, annotations) ctor.deprecated = annotations.get('Deprecated', False) if access_specifier is not AccessSpecifier.PRIVATE: ctor.kw_args = self._get_kw_args(p, symbol, annotations, py_signature) scope.can_create = True if access_specifier is AccessSpecifier.PROTECTED: scope.needs_shadow = True ctor.method_code = method_code ctor.no_type_hint = annotations.get('NoTypeHint', False) ctor.posthook = annotations.get('PostHook') ctor.prehook = annotations.get('PreHook') ctor.premethod_code = premethod_code ctor.throw_args = exceptions if method_code is None and annotations.get('NoRaisesPyException') is None: if self.module_state.all_raise_py_exception or annotations.get('RaisesPyException', False): ctor.raises_py_exception = True ctor.transfer = self.get_transfer(p, symbol, annotations) scope.ctors.append(ctor) def add_dtor(self, p, symbol, name, annotations, *, exceptions, abstract, premethod_code, method_code, virtual_catcher_code): """ Add a dtor to the current scope. """ scope = self.scope # Error checks. if name != scope.iface_file.fq_cpp_name.base_name: self.parser_error(p, symbol, "destructor doesn't have the same name as its class") if scope.dtor is not None: self.parser_error(p, symbol, "destructor has already been defined") if bool(self.c_bindings) and method_code is None: self.parser_error(p, symbol, "destructors in C modules must specify %MethodCode") if self.scope_pyqt_method_specifier is not None: self.parser_error(p, symbol, "destructors must be in the public, protected or private sections") if self.parsing_virtual: if self.scope.class_key is ClassKey.UNION: self.parser_error(p, symbol, "a union cannot have a virtual destructor") if virtual_catcher_code is not None: self.parser_error(p, symbol, "destructors in C modules cannot specify %VirtualCatcherCode") elif abstract: self.parser_error(p, symbol, "an abstract destructor must be virtual") # Configure the destructor. scope.dtor = self.scope_access_specifier scope.dtor_gil_action = self._get_gil_action(p, symbol, annotations) scope.dtor_throw_args = exceptions scope.dtor_virtual_catcher_code = virtual_catcher_code if premethod_code is not None: scope.dealloc_code.append(premethod_code) if method_code is not None: scope.dealloc_code.append(method_code) if abstract: scope.is_abstract = True if self.parsing_virtual or len(scope.dealloc_code) != 0: scope.needs_shadow = True def add_enum(self, p, symbol, cpp_name, is_scoped, annotations, members): """ Create a new enum and add it to the current scope. """ if self.scope_access_specifier is AccessSpecifier.PRIVATE: self.parser_error(p, symbol, "class enums cannot be private") if is_scoped: self.cpp_only(p, symbol, "scoped enums") # Determine the base type. base_type_s = annotations.get('BaseType') base_type = EnumBaseType.ENUM if base_type_s is not None: if self.spec.abi_version < (13, 0): self.parser_error(p, symbol, "/BaseType/ is only supported for ABI v13.0 and later") if base_type_s == 'Enum': base_type = EnumBaseType.ENUM elif base_type_s == 'Flag': base_type = EnumBaseType.FLAG elif base_type_s == 'IntEnum': base_type = EnumBaseType.INT_ENUM elif base_type_s == 'UIntEnum': base_type = EnumBaseType.UINT_ENUM elif base_type_s == 'IntFlag': base_type = EnumBaseType.INT_FLAG else: self.parser_error(p, 1, "'{0}' is an invalid value of /BaseType/".format( base_type_s)) if cpp_name is None: fq_cpp_name = None cached_fq_cpp_name = None py_name = None else: fq_cpp_name = normalised_scoped_name(cpp_name, self.scope) cached_fq_cpp_name = cached_name(self.spec, str(fq_cpp_name)) py_name = cached_name(self.spec, self.get_py_name(cpp_name, annotations)) self.check_attributes(p, symbol, py_name) if self.in_main_module: cached_fq_cpp_name.used = True py_name.used = True w_enum = WrappedEnum(base_type, fq_cpp_name, self.module_state.module, cached_fq_cpp_name=cached_fq_cpp_name, is_scoped=is_scoped, py_name=py_name, scope=self.scope) if self.scope_access_specifier is AccessSpecifier.PROTECTED: if not self._protected_is_public: w_enum.is_protected = True self.scope.needs_shadow = True w_enum.no_scope = annotations.get('NoScope', False) w_enum.no_type_hint = annotations.get('NoTypeHint', False) # Create the members. for m_cpp_name, m_py_name, m_no_type_hint in members: if not is_scoped: self.check_attributes(p, symbol, m_py_name.name) w_enum.members.append( WrappedEnumMember(m_cpp_name, m_py_name, w_enum, no_type_hint=m_no_type_hint)) if self.in_main_module: m_py_name.used = True self.spec.enums.insert(0, w_enum) def add_function(self, p, symbol, cpp_name, result, arg_list, annotations, *, const=False, final=False, exceptions=None, abstract=False, cpp_signature=None, docstring=None, premethod_code=None, method_code=None, virtual_catcher_code=None, virtual_call_code=None): """ Create and return an Overload and add it to the current scope. """ # Get the Python name. py_name = self.get_py_name(cpp_name, annotations) # Handle the signatures. py_signature = Signature(args=arg_list, result=result) self._check_ellipsis(p, symbol, py_signature) if cpp_signature is None: cpp_signature = py_signature else: self._check_ellipsis(p, symbol, cpp_signature) # Find (or create) the member shared by overloads with the same Python # name. member = self._find_member(p, symbol, py_name, arg_list, annotations, method_code) # Create the overload. overload = Overload(self.scope_access_specifier, member, cpp_name, cpp_signature, py_signature) for m in self.module_state.module.global_functions: if m is member: self.module_state.module.overloads.append(overload) break else: self.scope.overloads.append(overload) overload.pyqt_method_specifier = self.scope_pyqt_method_specifier if overload.access_specifier is AccessSpecifier.PROTECTED and self._protected_is_public: overload.access_specifier = AccessSpecifier.PUBLIC overload.access_is_really_protected = True if overload.access_specifier is AccessSpecifier.PROTECTED: self.scope.needs_shadow = True member.has_protected = True if overload.access_specifier is AccessSpecifier.PUBLIC: if overload.pyqt_method_specifier is PyQtMethodSpecifier.SIGNAL: self.scope.needs_shadow = True overload.docstring = docstring overload.is_abstract = abstract overload.is_const = const overload.is_delattr = (py_name == '__delattr__') overload.is_final = final overload.premethod_code = premethod_code overload.throw_args = exceptions overload.virtual_call_code = virtual_call_code overload.virtual_catcher_code = virtual_catcher_code overload.source_location = self.get_source_location(p, symbol) # See if the function is a non-lazy method. These are methods that # Python expects to see defined in the type before any instance of the # type is created. if self.scope is not None: NONLAZY_METHOD_NAMES = ( '__getattribute__', '__getattr__', '__enter__', '__exit__', '__aenter__', '__aexit__', ) if cpp_name in NONLAZY_METHOD_NAMES: self.scope.has_nonlazy_method = True # Handle any annotations. overload.abort_on_exception = annotations.get('AbortOnException', False) auto_gen = annotations.get('AutoGen') if auto_gen is not None: overload.is_auto_generated = self.evaluate_feature_or_platform(p, symbol, name=auto_gen) overload.gil_action = self._get_gil_action(p, symbol, annotations) overload.factory = annotations.get('Factory', False) overload.deprecated = annotations.get('Deprecated', False) overload.new_thread = annotations.get('NewThread', False) overload.transfer = self.get_transfer(p, symbol, annotations) if overload.access_specifier is not AccessSpecifier.PRIVATE: if member.py_slot is None or member.py_slot is PySlot.CALL: overload.kw_args = self._get_kw_args(p, symbol, annotations, py_signature, need_name=member.has_protected) if overload.kw_args is not KwArgs.NONE: member.allow_keyword_args = True # If the overload is protected and defined in an imported # module then we need to make sure that any other overloads' # keyword argument names are marked as used. if overload.pyqt_method_specifier is not PyQtMethodSpecifier.SIGNAL and overload.access_specifier is AccessSpecifier.PROTECTED and not self.in_main_module: for kwod in self.scope.overloads: if kwod.common is not member: continue if kwod.kw_args is KwArgs.NONE: continue for arg in kwod.py_signature.args: if kwod.kw_args is KwArgs.OPTIONAL and arg.default_value is None: continue if arg.name is not None: arg.name.used = True overload.no_type_hint = annotations.get('NoTypeHint', False) overload.posthook = annotations.get('PostHook') overload.prehook = annotations.get('PreHook') if method_code is None and annotations.get('NoRaisesPyException') is None: if self.module_state.all_raise_py_exception or annotations.get('RaisesPyException', False): overload.raises_py_exception = True overload.virtual_error_handler = annotations.get('VirtualErrorHandler') overload.no_virtual_error_handler = annotations.get( 'NoVirtualErrorHandler', False) if annotations.get('Numeric', False): if member.is_sequence: self.parser_error(p, symbol, "an overload has already specified /Sequence/") else: member.is_numeric = True if annotations.get('Sequence', False): if member.is_numeric: self.parser_error(p, symbol, "an overload has already specified /Numeric/") else: member.is_sequence = True self.apply_common_argument_annotations(p, symbol, overload.py_signature.result, annotations) overload.method_code = method_code # Add some auto-generated slots if required. if '__len__' in annotations: len_method_code = method_code if len_method_code is None: len_method_code = CodeBlock("Auto-generated", text=' sipRes = (Py_ssize_t)sipCpp->{0}();\n'.format(cpp_name)) # Note that we can't currently use any %MethodCode because it # does too much (ie. setting sipRes). self.scope.len_cpp_name = cpp_name len_py_signature = Signature(result=Argument(ArgumentType.SSIZE)) self._add_auto_slot(p, symbol, annotations, '__len__', len_py_signature, len_py_signature, len_method_code) if '__matmul__' in annotations: self._add_auto_slot(p, symbol, annotations, '__matmul__', py_signature, cpp_signature, method_code) if '__imatmul__' in annotations: self._add_auto_slot(p, symbol, annotations, '__imatmul__', py_signature, cpp_signature, method_code) return overload def add_mapped_type(self, p, symbol, cpp_type, annotations): """ Create a new mapped type and add it to the current scope. """ # Check the type is one we want to map. if cpp_type.type is ArgumentType.DEFINED: fq_cpp_name = cpp_type.definition = normalised_scoped_name( cpp_type.definition, self.scope) cpp_name = fq_cpp_name.base_name elif cpp_type.type is ArgumentType.TEMPLATE: cpp_type.definition.cpp_name = normalised_scoped_name( cpp_type.definition.cpp_name, self.scope) fq_cpp_name = encoded_template_name(cpp_type.definition) cpp_name = None elif cpp_type.type is ArgumentType.STRUCT: fq_cpp_name = cpp_type.definition = normalised_scoped_name( cpp_type.definition, self.scope) cpp_name = fq_cpp_name.base_name else: self.parser_error(p, symbol, "invalid type for %MappedType") fq_cpp_name = '' iface_file = self.find_iface_file(p, symbol, fq_cpp_name, IfaceFileType.MAPPED_TYPE, cpp_type=cpp_type) # Check it hasn't already been defined. for mtd in self.spec.mapped_types: if mtd.iface_file is iface_file: # We allow types based on the same template but with different # arguments. if cpp_type.type is not ArgumentType.TEMPLATE or same_base_type(cpp_type, mtd.type): self.parser_error(p, symbol, "the mapped type has already been defined in this module") # The module may not have been set yet. iface_file.module = self.module_state.module # Create a new mapped type. mapped_type = MappedType(iface_file, cpp_type) mapped_type.cpp_name = cached_name(self.spec, argument_as_str(cpp_type)) if cpp_name is not None: mapped_type.py_name = cached_name(self.spec, annotations.get('PyName', cpp_name)) self.annotate_mapped_type(p, symbol, mapped_type, annotations) self.spec.mapped_types.insert(0, mapped_type) if self.in_main_module: mapped_type.cpp_name.used = True if mapped_type.py_name is not None: mapped_type.py_name.used = True self.push_scope(mapped_type) def add_qualifier(self, p, symbol, name, type, order=0, timeline=0): """ Create a Qualifier and add it to the current module. """ module = self.module_state.module # See if it already exists. qualifier = self.find_qualifier(p, symbol, name, required=False) if qualifier is not None: # We allow versions to be defined more than once so long as they # are in different timelines. It is sometimes necessary to define # the same timeline in multiple modules if a module that others # depend on is added during the timeline. if qualifier.type is not QualifierType.TIME or type is not QualifierType.TIME or (qualifier.module is module and qualifier.timeline == timeline): self.parser_error(p, symbol, "'{0}' has already been defined as a qualifier".format( name)) return qualifier = Qualifier(module, name, type, order=order, timeline=timeline) if type is QualifierType.TIME or not self.skipping: qualifier.enabled_by_default = True module.qualifiers.insert(0, qualifier) def add_typedef(self, p, symbol, typedef): """ Add a typedef to the current scope. """ if self.spec.is_strict: for td in self.spec.typedefs: if td.fq_cpp_name == typedef.fq_cpp_name: self.parser_error(p, symbol, "'{0}' has already been defined".format( typedef.fq_cpp_name)) self.module_state.module.nr_typedefs += 1 self.spec.typedefs.append(typedef) def annotate_mapped_type(self, p, symbol, mapped_type, annotations): """ Apply annotations to a mapped type. """ mapped_type.handles_none = annotations.get('AllowNone', False) mapped_type.no_assignment_operator = annotations.get( 'NoAssignmentOperator', False) mapped_type.no_copy_ctor = annotations.get('NoCopyCtor', False) mapped_type.no_default_ctor = annotations.get('NoDefaultCtor', False) mapped_type.no_release = annotations.get('NoRelease', False) mapped_type.type_hints = self.get_type_hints(p, symbol, annotations) if mapped_type.no_release: mapped_type.no_assignment_operator = True mapped_type.no_copy_ctor = True mapped_type.no_default_ctor = True pyqt_flags = self._get_plugin_annotation(p, symbol, annotations, 'PyQtFlags', 'PyQt6') if pyqt_flags is not None: mapped_type.pyqt_flags = pyqt_flags def apply_common_argument_annotations(self, p, symbol, arg, annotations): """ Apply the annotations common to callable arguments and return type. """ arg.allow_none = annotations.get('AllowNone', False) arg.disallow_none = annotations.get('DisallowNone', False) arg.no_copy = annotations.get('NoCopy', False) # We need to use an exception because we have to distinguish between # a missing annotation and one without a value specified. try: key = annotations['KeepReference'] if key is None: key = self.module_state.module.next_key self.module_state.module.next_key -= 1 elif key < 0: self.parser_error(p, symbol, "a /KeepReference/ key cannot be negative") arg.key = key except KeyError: pass def apply_type_annotations(self, p, symbol, type, annotations): """ Apply the annotations for an argument type. """ # The type annotations. type.type_hints = self.get_type_hints(p, symbol, annotations) # The PyInt annotation. if annotations.get('PyInt') is not None: if type.type is ArgumentType.STRING: type.type = ArgumentType.BYTE elif type.type is ArgumentType.SSTRING: type.type = ArgumentType.SBYTE elif type.type is ArgumentType.USTRING: type.type = ArgumentType.UBYTE # The Encoding annotation. can_be_encoded = type.type is ArgumentType.STRING and type.array is ArrayArgument.NONE and not type.is_reference if can_be_encoded: encoding = annotations.get('Encoding') if encoding is None: default_encoding = self.module_state.default_encoding if default_encoding is not None: type.type = default_encoding else: type.type = self.convert_encoding(p, symbol, encoding) def check_annotations(self, p, symbol, context, annotations): """ Check that all the annotations provided as a dict of name/values are valid in a given context. """ for name in p[symbol]: if name not in annotations: self.parser_error(p, symbol, "{0} is not a valid {1} annotation".format(name, context)) def check_attributes(self, p, symbol, py_name, is_function=False, ignore=None): """ Check that a Python name will not clash with another object in the same Python scope. """ # We don't do any check for a non-strict parse. if not self.spec.is_strict: return # Report a name clash with something. def clash(thing): self.parser_error(p, symbol, "there is already {0} in scope called '{1}'".format(thing, py_name)) # Check the enums. for ed in self.spec.enums: if ed.py_name is None: continue if ed.scope is not self.scope: continue if ed.py_name.name == py_name: clash("an enum") if not ed.is_scoped: for emd in ed.members: if emd.py_name.name == py_name: clash("an enum member") # Only check the members if this attribute isn't a member because we # can handle members with the same name in the same scope. if not is_function: if self.scope is None: members = self.module_state.module.global_functions thing = "a function" else: members = self.scope.members thing = "a method" for md in members: if md.py_name.name == py_name: clash(thing) break # There is nothing more to check in mapped types. if isinstance(self.scope, MappedType): return # Check the variables. for vd in self.spec.variables: if vd.scope is not self.scope: continue if vd.py_name.name == py_name: clash("a variable") break # Check the classes. for cd in self.spec.classes: if cd.scope is not self.scope: continue # A class will have already been added to the scope and this will # tell us to ignore it. if cd is ignore: continue if cd.external: continue if cd.py_name.name == py_name: clash("a class or namespace") if self.scope is None: # Check the exceptions. for xd in self.spec.exceptions: if xd.py_name is not None and xd.py_name == py_name: clash("an exception") break else: # Check the properties. for pd in self.scope.properties: if pd.name.name == py_name: clash("a property") break def cpp_only(self, p, symbol, feature): """ Check that a C++ feature isn't being used in a C module. """ if bool(self.c_bindings): self.parser_error(p, symbol, feature + " are not allowed in a C module") def convert_docstring_format(self, p, symbol): """ Convert a string to the corresponding DocstringFormat member. """ value = p[symbol] if value == 'deindented': return DocstringFormat.DEINDENTED if value == 'raw': return DocstringFormat.RAW self.parser_error(p, symbol, "'{0}' is not a valid value for a docstring format".format( value)) # Return any value of the right type. return DocstringFormat.RAW def convert_docstring_signature(self, p, symbol): """ Convert a string to the corresponding DocstringSignature member. """ value = p[symbol] if value == 'appended': return DocstringSignature.APPENDED if value == 'discarded': return DocstringSignature.DISCARDED if value == 'prepended': return DocstringSignature.PREPENDED self.parser_error(p, symbol, "'{0}' is not a valid value for a docstring signature".format( value)) # Return any value of the right type. return DocstringSignature.PREPENDED def convert_encoding(self, p, symbol, value=None): """ Convert a string to the corresponding ArgumentType member. """ if value is None: value = p[symbol] if value == 'ASCII': return ArgumentType.ASCII_STRING if value == 'Latin-1': return ArgumentType.LATIN1_STRING if value == 'None': return ArgumentType.STRING if value == 'UTF-8': return ArgumentType.UTF8_STRING self.parser_error(p, symbol, "'{0}' is not a valid encoding".format(value)) # Return any value of the right type. return ArgumentType.NONE def convert_kw_args(self, p, symbol, value=None): """ Convert a string to the corresponding KwArgs member. """ if value is None: value = p[symbol] if value == 'All': return KwArgs.ALL if value == 'None': return KwArgs.NONE if value == 'Optional': return KwArgs.OPTIONAL self.parser_error(p, symbol, "'{0}' is not a valid value for keyword arguments".format( value)) # Return any value of the right type. return KwArgs.OPTIONAL def deprecated(self, p, symbol, instead=None): """ Issue a deprecation message about a symbol. """ deprecated(f"'{p[symbol]}'", instead=instead, filename=self._sip_file, line_nr=p.lineno(symbol)) def ensure_import(self): """ We allow %Modules that are part of a %CompositeModule to be either %Imported or %Included. In the case of the latter we need to adjust things so that it appears like the former. """ sip_file, raw_sip_file, input, lineno, lexpos, module_state = self._file_stack.pop() if module_state is None: self._import_module(self._sip_file) module_state = self.module_state self._file_stack.append( (sip_file, raw_sip_file, input, lineno, lexpos, module_state)) def evaluate_feature_or_platform(self, p, symbol, name=None, inverted=False): """ Evaluate a feature or platform qualifier. """ if name is None: name = p[symbol] qual = self.find_qualifier(p, symbol, name) if qual is None: return False if qual.type is QualifierType.FEATURE: value = qual.name not in self._disabled_features elif qual.type is QualifierType.PLATFORM: # The platform is always ignored in non-strict mode. if not self.spec.is_strict: return True value = qual.name in self.tags else: self.parser_error(p, symbol, "'{0}' is a %Timeline qualifier which can only be used in a range".format(name)) return False if inverted: value = not value return value def evaluate_timeline(self, p, symbol_lower, symbol_upper): """ Evaluate a timeline qualifier. """ # Get the lower and upper qualifiers if specified. lower_name = p[symbol_lower] if lower_name: lower_qual = self._find_timeline_qualifier(p, symbol_lower) if lower_qual is None: return False else: lower_qual = None upper_name = p[symbol_upper] if upper_name: upper_qual = self._find_timeline_qualifier(p, symbol_upper) if upper_qual is None: return False else: upper_qual = None if lower_qual is None and upper_qual is None: self.parser_error(p, symbol_lower, "the lower and upper bounds cannot both be omitted") return False if lower_qual is not None and upper_qual is not None: if lower_qual.module is not upper_qual.module or lower_qual.timeline != upper_qual.timeline: self.parser_error(p, symbol_lower, "'{0}' and '{1}' are defined in different %Timelines".format(lower_name, upper_name)) return False if lower_qual is upper_qual: self.parser_error(p, symbol_lower, "the lower and upper bounds must be different") return False if lower_qual.order > upper_qual.order: self.parser_error(p, symbol_lower, "'{0}' is later in the %Timeline than '{1}'".format(lower_name, upper_name)) return False # Get the module and timeline. if lower_qual is not None: module = lower_qual.module timeline = lower_qual.timeline else: module = upper_qual.module timeline = upper_qual.timeline # Handle the SIP version number pseudo-timeline. if timeline < 0: if lower_qual is not None and self._hex_version < lower_qual.order: return False if upper_qual is not None and self._hex_version >= upper_qual.order: return False return True # See if there is a selected qualifier withing range. for qual in module.qualifiers: if qual.type is QualifierType.TIME and qual.timeline == timeline and qual.name in self.tags: if lower_qual is not None and qual.order < lower_qual.order: return False if upper_qual is not None and qual.order >= upper_qual.order: return False return True return upper_qual is None def find_qualifier(self, p, symbol, name, required=True): """ Return a Qualifier or None if one doesn't exist. """ for module in self.modules: for qual in module.qualifiers: if qual.name == name: return qual # Qualifiers corresponding to the SIP version are created on the fly. if name.startswith('SIP_'): parts = name.split('_')[1:] if len(parts) > 3: order = -1 else: while len(parts) < 3: parts.append('0') order = 0 for part in parts: try: order = (order << 8) + int(part) except ValueError: order = -1 break if order >= 0: module = self.module_state.module qualifier = Qualifier(module, name, QualifierType.TIME, order=order) module.qualifiers.append(qualifier) return qualifier if required: self.parser_error(p, symbol, "'{0}' is not a known qualifier".format(name)) return None def get_py_name(self, cpp_name, annotations): """ Return a valid Python name given a C/C++ name. """ # Use any name specified by annotation. try: return annotations['PyName'] except KeyError: pass # Use the C/C++ name. py_name = cpp_name # Apply any automatic naming rules. for rule in self.module_state.auto_py_name_rules: if rule[0] == 'REMOVE_LEADING': leading = rule[1] if py_name.startswith(leading): py_name = py_name[len(leading):] # Fix any Python keywords. if py_name in self._PYTHON_KEYWORDS: py_name += '_' return py_name def get_transfer(self, p, symbol, annotations): """ Return the a Transfer value from a dict of annotations. """ has_transfer = annotations.get('Transfer', False) has_transfer_back = annotations.get('TransferBack', False) has_transfer_this = annotations.get('TransferThis', False) transfer = None if has_transfer: if has_transfer_back or has_transfer_this: pass else: transfer = Transfer.TRANSFER elif has_transfer_back: if has_transfer or has_transfer_this: pass else: transfer = Transfer.TRANSFER_BACK elif has_transfer_this: if has_transfer or has_transfer_back: pass else: transfer = Transfer.TRANSFER_THIS else: transfer = Transfer.NONE if transfer is None: self.parser_error(p, symbol, "Only one transfer annotation may be specified") transfer = Transfer.NONE return transfer def get_type_hints(self, p, symbol, annotations): """ Return a TypeHints object constructed from a dict of annotations or None if none were specified. """ th = annotations.get('TypeHint') th_in = annotations.get('TypeHintIn') th_out = annotations.get('TypeHintOut') th_value = annotations.get('TypeHintValue') if th_in is None: th_in = th elif th is not None: self.parser_error(p, symbol, "'TypeHint' and 'TypeHintIn' cannot both be specified") return None if th_out is None: th_out = th elif th is not None: self.parser_error(p, symbol, "'TypeHint' and 'TypeHintOut' cannot both be specified") return None if th_in is not None or th_out is not None or th_value is not None: # Check that type hints haven't been suppressed. if annotations.get('NoTypeHint') is not None: self.parser_error(p, symbol, "'NoTypeHint' cannot be specified with a type hint") return TypeHints(th_in, th_out, th_value) return None @property def in_main_module(self): """ Set if the current module is the main one, ie. the one for which code will be generated for. """ return self.module_state.module is self.spec.module def instantiate_class_template(self, p, symbol, fq_cpp_name, template, py_name, no_type_name, docstring): """ Try and instantiate a class template and return True if one was found. """ # Look for an appropriate class template. for tmpl_names, proto_class in self.class_templates: if proto_class.iface_file.fq_cpp_name.matches(template.cpp_name, scope=self.scope) and same_template_signature(tmpl_names, template.types): break else: # There was no class template to instantiate. return False instantiate_class(p, symbol, fq_cpp_name, tmpl_names, proto_class, template, py_name, no_type_name, docstring, self) return True def lexer_error(self, t, text): """ Record an error caused by a token. """ self._error_log.log(text, SourceLocation(self._sip_file, line=t.lineno, column=self._get_column_from_lexpos(t.lexpos))) def parse(self, sip_file): """ Parse a .sip file and return a 3-tuple of a Specification object, a list of Module objects and a list of the .sip files that specify the module to be generated. A UserException is raised if there was an error. """ # Note that the retention of the 'raw' filename, ie. that which was # specified by the user is only done so that generated '#line' # directives match those from older versions of SIP. raw_sip_file = sip_file sip_file = os.path.abspath(sip_file) self.module_state = ModuleState(self.spec.module, sip_file) try: self._parser.parse(self._read(sip_file, raw_sip_file), lexer=self._lexer, tracking=True) except UnexpectedEOF: self._unexpected_eof_error() self._handle_eom() self._error_log.as_exception() self.spec.c_bindings = bool(self.c_bindings) self.spec.typedefs.sort(key=lambda k: k.fq_cpp_name) self.spec.variables.sort(key=lambda k: k.py_name.name) # Remove all template classes and anything they contain. template_classes = [k for _, k in self.class_templates] for enum in list(self.spec.enums): if enum.scope in template_classes: spec.spec.enums.remove(enum) for typedef in list(self.spec.typedefs): if typedef.scope in template_classes: spec.spec.typedefs.remove(typedef) for variable in list(self.spec.variables): if variable.scope in template_classes: spec.spec.variables.remove(variable) for klass in template_classes: self.spec.classes.remove(klass) self.spec.iface_files.remove(klass.iface_file) # Remove all classes that are only template arguments. for klass in self._template_arg_classes: self.spec.classes.remove(klass) return self.spec, self.modules, self._sip_files def parser_error(self, p, symbol, text): """ Record an error caused by a symbol in a production. """ self._error_log.log(text, self.get_source_location(p, symbol)) def pop_file(self): """ Restore the current .sip file from the stack and make it current. An IndexError is raised if the stack is empty. """ # Restore the state of the previous .sip file. Note that we don't # restore the module state until after the EOF has been seen. self._sip_file, self.raw_sip_file, self._input, self._lexer.lineno, lexpos, self._pending_module_state = self._file_stack.pop() self._lexer.input(self._input) self._lexer.lexpos = lexpos def pop_module_state(self): """ Restore the current module state. """ if self._pending_module_state is None: return self._handle_eom() # Inherit any default encoding. if self._pending_module_state.default_encoding is None: self._pending_module_state.default_encoding = self.module_state.default_encoding # Inherit any call_super_init. if self._pending_module_state.call_super_init is None: self._pending_module_state.call_super_init = self.module_state.call_super_init self.module_state = self._pending_module_state self._pending_module_state = None def pop_scope(self): """ Pop the current scope. """ self._scope_stack.pop() def push_file(self, p, symbol, sip_file=None, new_module=False, optional=False): """ Push the current .sip file onto the stack and make the new one current. The new .sip file may be part of a new module (ie. %Import rather than %Include). """ if sip_file is None: sip_file = p[symbol] raw_sip_file = sip_file # Make the name platform-native. sip_file = sip_file.replace('/', os.sep) # See if the file can be found. if os.path.isfile(sip_file): pass else: found = None # If the name is relative then check the directory containing the # current file and any include directories. if not os.path.isabs(sip_file): inc_dirs = [os.path.dirname(self._sip_file)] inc_dirs.extend(self._include_dirs) for inc_dir in inc_dirs: fn = os.path.join(inc_dir, sip_file) if os.path.isfile(fn): found = fn break if found is None: if not optional: self.parser_error(p, symbol, "'{0}' could not be found".format(raw_sip_file)) return # For historic reasons we keep the absolute name for the raw name. raw_sip_file = sip_file = found sip_file = os.path.abspath(sip_file) # Check we aren't reading the file recursively. for detail in self._file_stack: if detail[0] == sip_file: self.parser_error(p, symbol, "'{0}' is being read recursively".format(sip_file)) return # Ignore the file if we have already read it. if sip_file in self._all_sip_files: return if new_module: old_module_state = self.module_state self._import_module(sip_file) else: # This means that the file was %Included rather than %Imported. old_module_state = None # Save the state of the current .sip file. self._file_stack.append( (self._sip_file, self.raw_sip_file, self._input, self._lexer.lineno, self._lexer.lexpos, old_module_state)) # Make the new one current and give it's content to the lexer. self._lexer.lineno = 1 self._lexer.input(self._read(sip_file, raw_sip_file)) def push_scope(self, scope, access_specifier=None): """ Push a new scope. """ self._scope_stack.append(ScopeState(scope, access_specifier)) def set_lexer_state(self, state='INITIAL'): """ Set the lexer state. """ self._lexer.begin(state) @property def scope(self): """ The current scope if any. """ try: return self._scope_stack[-1].scope except IndexError: pass return None @property def scope_access_specifier(self): """ The current access specifier. """ return None if len(self._scope_stack) == 0 else self._scope_stack[-1].access_specifier @scope_access_specifier.setter def scope_access_specifier(self, access_specifier): """ Set the current access specifier. """ self._scope_stack[-1].access_specifier = access_specifier @property def scope_pyqt_method_specifier(self): """ The current method specifier. """ return None if len(self._scope_stack) == 0 else self._scope_stack[-1].pyqt_method_specifier @scope_pyqt_method_specifier.setter def scope_pyqt_method_specifier(self, pyqt_method_specifier): """ Set the current method specifier. """ self._scope_stack[-1].pyqt_method_specifier = pyqt_method_specifier @property def skipping(self): """ True if symbols are currently being skipped. """ return self.skip_stack[-1] def validate_annotation(self, p, symbol, value): """ Validate an annotation and its value and return a valid version of the value. """ try: value = validate_annotation_value(self, p, symbol, p[symbol], value) except InvalidAnnotation as e: self.parser_error(p, symbol, str(e)) value = e.use return value def validate_function(self, p, symbol, overload): """ Validate a completed function. """ # Shortcuts. cpp_only = partial(self.cpp_only, p, symbol) error = partial(self.parser_error, p, symbol) if overload.access_specifier is None: if overload.is_abstract: error("abstract non-member functions cannot be specified") else: cpp_only("struct/union member functions") if overload.new_thread: # This is an arbitary limitation to make the code generator # slightly easier - laziness on my part. result = overload.cpp_signature.result if result.type is not ArgumentType.VOID or len(result.derefs) != 0: error("/NewThread/ may only be specified for void functions") if overload.is_static: cpp_only("static struct/union data members") if overload.pyqt_method_specifier is PyQtMethodSpecifier.SIGNAL: error("signals cannot be static") if overload.throw_args is not None: cpp_only("exceptions") if overload.is_virtual: if overload.virtual_error_handler is not None and overload.no_virtual_error_handler: error("/VirtualErrorHandler/ and /NoVirtualErrorHandler/ cannot both be specified") else: if overload.abort_on_exception: error("/AbortOnException/ cannot be specified for non-virtual member functions") if overload.new_thread: error("/NewThread/ cannot be specified for non-virtual member functions") if overload.virtual_call_code is not None: error("%VirtualCallCode cannot be specified for non-virtual member functions") if overload.virtual_catcher_code is not None: error("%VirtualCatcherCode cannot be specified for non-virtual member functions") if overload.virtual_error_handler is not None: error("/VirtualErrorHandler/ cannot be specified for non-virtual member functions") if overload.no_virtual_error_handler: error("/NoVirtualErrorHandler/ cannot be specified for non-virtual member functions") if overload.factory: if overload.transfer is Transfer.TRANSFER_BACK: error("/TransferBack/ and /Factory/ cannot both be specified") elif self.scope_access_specifier is None or overload.is_static: for arg in overload.py_signature.args: if arg.transfer is Transfer.TRANSFER_THIS: error("/TransferThis/ may only be specified for constructors and member functions") break if overload.common.no_arg_parser and overload.method_code is None: error("%MethodCode must be specified when /NoArgParser/ is specified") def validate_mapped_type(self, p, symbol, mapped_type): """ Validate a completed mapped type. """ if self.spec.abi_version >= (13, 0): convert_to_us = mapped_type.convert_to_type_code is not None and 'sipUserState' in mapped_type.convert_to_type_code.text release_us = mapped_type.release_code is not None and 'sipUserState' in mapped_type.release_code.text if convert_to_us != release_us: self.parser_error(p, symbol, "both %ConvertToTypeCode and %ReleaseCode must use user state or neither must") mapped_type.needs_user_state = convert_to_us or release_us else: if mapped_type.convert_to_type_code is None: self.parser_error(p, symbol, "%MappedType must have a %ConvertToTypeCode directive") if mapped_type.convert_from_type_code is None: self.parser_error(p, symbol, "%MappedType must have a %ConvertFromTypeCode directive") def validate_variable(self, p, symbol, variable): """ Validate a completed variable. """ if variable.type.type is ArgumentType.CAPSULE: self.parser_error(p, symbol, "capsule variables are not yet supported") access_specifier = self.scope_access_specifier if access_specifier is not None: if access_specifier is not AccessSpecifier.PUBLIC: self.parser_error(p, symbol, "class variables must be in the public section") if variable.is_static: self.cpp_only(p, symbol, "static members in C structures") elif variable.access_code is not None: self.parser_error(p, symbol, "%AccessCode cannot be specified for non-static class variables") if variable.get_code is not None or variable.set_code is not None: if variable.access_code is not None: self.parser_error(p, symbol, "%AccessCode cannot be specified with %GetCode or %SetCode") if self.scope is None: # TODO: this can be supported for versions of Python that # support module descriptors. self.parser_error(p, symbol, "%GetCode or %SetCode cannot be specified for global variables") if self.scope is not None and self.scope.iface_file.type is IfaceFileType.NAMESPACE: variable.is_static = True self.check_attributes(p, symbol, variable.py_name) def _add_auto_slot(self, p, symbol, annotations, py_name, py_signature, cpp_signature, method_code): """ Add an automatically generated slot. """ member = self._find_member(p, symbol, py_name, py_signature.args, annotations, method_code) overload = Overload(AccessSpecifier.PUBLIC, member, py_name, cpp_signature, py_signature, method_code=method_code) if self.scope is None: self.module_state.module.overloads.append(overload) else: self.scope.overloads.append(overload) def _check_ellipsis(self, p, symbol, signature): """ Check any ellipsis in a signature. """ seen_ellipsis = False for arg in signature.args: if arg.type is ArgumentType.ELLIPSIS: # Give the argument the standard name so that it appears in # docstrings and type hints. It is never used in generated # code so there is no need to add it to the module cache. arg.name = CachedName('*args') if seen_ellipsis: self.parser_error(p, symbol, "'...' may only be specified once") break seen_ellipsis = True elif seen_ellipsis and arg.default_value is None: self.parser_error(p, symbol, "'...' must be at the end of the positional arguments") break def find_iface_file(self, p, symbol, fq_cpp_name, iface_file_type, cpp_type=None): """ Return an interface file for a fully qualified C/C++ name and type creating it if necessary. """ def error_logger(text): self.parser_error(p, symbol, text) return find_iface_file(self.spec, self.module_state.module, fq_cpp_name, iface_file_type, error_logger, cpp_type=cpp_type, scope=self.scope) def _find_member(self, p, symbol, py_name, args, annotations, method_code): """ Return (creating if necessary) the member for a Python name. """ # See if it is a Python slot rather than an ordinary member function # and check its requirements. slot_detail = slot_name_detail_map.get(py_name) if slot_detail is None: py_slot = None else: py_slot, needs_method_code, nr_args_needed = slot_detail if needs_method_code and method_code is None: self.parser_error("'{0}' requires %MethodCode".format(py_name)) if nr_args_needed >= 0: # Global operators need an extra argument. if self.scope_access_specifier is None: nr_args_needed += 1 # Global operators can only be numeric or comparision # slots. if invalid_global_slot(py_slot): self.parser_error(p, symbol, "'{0}' cannot be specified at the module level".format(py_name)) nr_args = len(args) if nr_args_needed != nr_args: self.parser_error(p, symbol, "{0} arguments are needed but {1} are provided".format( nr_args_needed, nr_args)) # __delattr__ is implemented as __setattr__. if py_slot is PySlot.DELATTR: if self.in_main_module: cached_name(self.spec, py_name).used = True py_slot = PySlot.SETATTR py_name = '__setattr__' # Check for name clashes. self.check_attributes(p, symbol, py_name, is_function=True) # Create a new member if necessary. no_arg_parser = annotations.get('NoArgParser', False) if self.scope is None: members = self.module_state.module.global_functions namespace_iface_file = None elif self.scope.iface_file.type is IfaceFileType.NAMESPACE and py_slot is not None: # The scope is a namespace and the function is an operator so # handle it as a global, but remember it's C++ scope. members = self.module_state.module.global_functions namespace_iface_file = self.scope.iface_file else: members = self.scope.members namespace_iface_file = None for member in members: if member.py_name.name == py_name: # If /NoArgParser/ has been specified then there should only be # one overload. if member.no_arg_parser: self.parser_error(p, symbol, "an overloaded member function has already been specified with /NoArgParser/") break else: member = Member(self.module_state.module, cached_name(self.spec, py_name)) member.namespace_iface_file = namespace_iface_file member.no_arg_parser = no_arg_parser member.py_slot = py_slot if self.in_main_module: member.py_name.used = True members.insert(0, member) return member def _find_timeline_qualifier(self, p, symbol): """ Return an optional timeline qualifier. """ name = p[symbol] qual = self.find_qualifier(p, symbol, name) if qual is None: return None if qual.type is not QualifierType.TIME: self.parser_error(p, symbol, "'{0}' is not a %Timeline qualifier".format(name)) return None return qual def _get_column_from_lexpos(self, lexpos): """ Return the column within the current line corresponding to a lexer position. """ line_start = self._input.rfind('\n', 0, lexpos) + 1 return lexpos - line_start + 1 def _get_gil_action(self, p, symbol, annotations): """ Return an appropriate GILAction according to the annotations. """ hold = annotations.get('HoldGIL', False) release = annotations.get('ReleaseGIL', False) if hold: if release: self.parser_error(p, symbol, "/HoldGIL/ and /ReleaseGIL' cannot both be specified") return GILAction.HOLD if release: return GILAction.RELEASE return GILAction.DEFAULT # The Python keywords. _PYTHON_KEYWORDS = ( 'False', 'None', 'True', 'and', 'as', 'assert', 'async', 'await', 'break', 'class', 'continue', 'def', 'del', 'elif', 'else', 'except', 'finally', 'for', 'from', 'global', 'if', 'import', 'in', 'is', 'lambda', 'nonlocal', 'not', 'or', 'pass', 'raise', 'return', 'try', 'while', 'with', 'yield', ) def _get_kw_args(self, p, symbol, annotations, signature, need_name=False): """ Return the keyword argument support. """ kw_args = annotations.get('KeywordArgs') if kw_args is not None: kw_args = self.convert_kw_args(p, symbol, kw_args) else: kw_args = self.module_state.kw_args # An ellipsis cannot be used with keyword arguments. if len(signature.args) > 0 and signature.args[-1].type is ArgumentType.ELLIPSIS: kw_args = KwArgs.NONE # Check that there is at least one optional argument. if kw_args is not KwArgs.NONE: no_name = True for arg in signature.args: if kw_args is KwArgs.OPTIONAL and arg.default_value is None: continue if arg.name is not None: if need_name or self.in_main_module: arg.name.used = True no_name = False if no_name: kw_args = KwArgs.NONE return kw_args def _get_plugin_annotation(self, p, symbol, annotations, name, plugin): """ Return an annotation that is only supported by a plugin. """ anno = annotations.get(name) if anno is not None and plugin not in self.spec.plugins: self.parser_error(p, symbol, "/{0}/ is only supported for {1}".format(name, plugin)) return anno def get_source_location(self, p, symbol): """ Return a SourceLocation object for a symbol. """ return SourceLocation(self._sip_file, line=p.lineno(symbol), column=self._get_column_from_lexpos(p.lexpos(symbol))) def _handle_eom(self): """ Check that the current module is complete. """ module_state = self.module_state module = module_state.module if module.fq_py_name is None: self._error_log.log("%Module has not been specified", SourceLocation(self._sip_file)) # call_super_init defaults to False if it wasn't specified. module.call_super_init = bool(module_state.call_super_init) def _import_module(self, sip_file): """ Create a new Module object and corresponding ModuleState object for a .sip file and make it current. """ importing_from = self.module_state.module module = Module() self.modules.append(module) module.default_exception = self.module_state.module.default_exception self.module_state = ModuleState(module, sip_file) # Get the configuration of the new module. mod_tags, mod_disabled = get_bindings_configuration( self.spec.abi_version[0], sip_file, self._include_dirs) for tag in mod_tags: if tag not in self.tags: self.tags.append(tag) for feature in mod_disabled: if feature not in self._disabled_features: self._disabled_features.append(feature) # Add the new import. importing_from.imports.append(module) def _read(self, sip_file, raw_sip_file): """ Return the contents of the current .sip file. """ try: with open(sip_file, encoding=self._encoding) as f: self._input = f.read() except FileNotFoundError: raise UserException("unable to read '{0}'".format(sip_file)) except UnicodeDecodeError as e: raise UserException( "'{0}' doesn't appear to use the '{1}' encoding".format( sip_file, self._encoding), detail=str(e)) self.raw_sip_file = raw_sip_file self._sip_file = sip_file self._all_sip_files.append(sip_file) if self.in_main_module: self._sip_files.append(sip_file) return self._input def _unexpected_eof_error(self): """ Record an error caused by an unexpected EOF. """ self._error_log.log("unexpected end of file", SourceLocation(self._sip_file)) class ModuleState: """ Encapsulate the parser-related state for a module. """ def __init__(self, module, sip_file): """ Initialise the state. """ self.module = module self.sip_file = sip_file self.all_raise_py_exception = False self.auto_py_name_rules = [] self.call_super_init = None self.default_encoding = None self.kw_args = KwArgs.NONE self.nr_timelines = 0 class ScopeState: """ Encapsulate the parser-related state for a scope. """ def __init__(self, scope, access_specifier): """ Initialise the state. """ self.scope = scope self.access_specifier = access_specifier self.pyqt_method_specifier = None class UnexpectedEOF(Exception): """ This is raised by p_error() when an unexpected EOF is seen. """ sip-6.8.6/sipbuild/generator/parser/ply/000077500000000000000000000000001464421045000202275ustar00rootroot00000000000000sip-6.8.6/sipbuild/generator/parser/ply/__init__.py000066400000000000000000000001641464421045000223410ustar00rootroot00000000000000# PLY package # Author: David Beazley (dave@dabeaz.com) # https://github.com/dabeaz/ply __version__ = '2022.10.27' sip-6.8.6/sipbuild/generator/parser/ply/lex.py000066400000000000000000001046511464421045000214000ustar00rootroot00000000000000# ----------------------------------------------------------------------------- # ply: lex.py # # Copyright (C) 2001-2022 # David M. Beazley (Dabeaz LLC) # All rights reserved. # # Latest version: https://github.com/dabeaz/ply # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are # met: # # * Redistributions of source code must retain the above copyright notice, # this list of conditions and the following disclaimer. # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions and the following disclaimer in the documentation # and/or other materials provided with the distribution. # * Neither the name of David Beazley or Dabeaz LLC may be used to # endorse or promote products derived from this software without # specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # ----------------------------------------------------------------------------- import re import sys import types import copy import os import inspect # This tuple contains acceptable string types StringTypes = (str, bytes) # This regular expression is used to match valid token names _is_identifier = re.compile(r'^[a-zA-Z0-9_]+$') # Exception thrown when invalid token encountered and no default error # handler is defined. class LexError(Exception): def __init__(self, message, s): self.args = (message,) self.text = s # Token class. This class is used to represent the tokens produced. class LexToken(object): def __repr__(self): return f'LexToken({self.type},{self.value!r},{self.lineno},{self.lexpos})' # This object is a stand-in for a logging object created by the # logging module. class PlyLogger(object): def __init__(self, f): self.f = f def critical(self, msg, *args, **kwargs): self.f.write((msg % args) + '\n') def warning(self, msg, *args, **kwargs): self.f.write('WARNING: ' + (msg % args) + '\n') def error(self, msg, *args, **kwargs): self.f.write('ERROR: ' + (msg % args) + '\n') info = critical debug = critical # ----------------------------------------------------------------------------- # === Lexing Engine === # # The following Lexer class implements the lexer runtime. There are only # a few public methods and attributes: # # input() - Store a new string in the lexer # token() - Get the next token # clone() - Clone the lexer # # lineno - Current line number # lexpos - Current position in the input string # ----------------------------------------------------------------------------- class Lexer: def __init__(self): self.lexre = None # Master regular expression. This is a list of # tuples (re, findex) where re is a compiled # regular expression and findex is a list # mapping regex group numbers to rules self.lexretext = None # Current regular expression strings self.lexstatere = {} # Dictionary mapping lexer states to master regexs self.lexstateretext = {} # Dictionary mapping lexer states to regex strings self.lexstaterenames = {} # Dictionary mapping lexer states to symbol names self.lexstate = 'INITIAL' # Current lexer state self.lexstatestack = [] # Stack of lexer states self.lexstateinfo = None # State information self.lexstateignore = {} # Dictionary of ignored characters for each state self.lexstateerrorf = {} # Dictionary of error functions for each state self.lexstateeoff = {} # Dictionary of eof functions for each state self.lexreflags = 0 # Optional re compile flags self.lexdata = None # Actual input data (as a string) self.lexpos = 0 # Current position in input text self.lexlen = 0 # Length of the input text self.lexerrorf = None # Error rule (if any) self.lexeoff = None # EOF rule (if any) self.lextokens = None # List of valid tokens self.lexignore = '' # Ignored characters self.lexliterals = '' # Literal characters that can be passed through self.lexmodule = None # Module self.lineno = 1 # Current line number def clone(self, object=None): c = copy.copy(self) # If the object parameter has been supplied, it means we are attaching the # lexer to a new object. In this case, we have to rebind all methods in # the lexstatere and lexstateerrorf tables. if object: newtab = {} for key, ritem in self.lexstatere.items(): newre = [] for cre, findex in ritem: newfindex = [] for f in findex: if not f or not f[0]: newfindex.append(f) continue newfindex.append((getattr(object, f[0].__name__), f[1])) newre.append((cre, newfindex)) newtab[key] = newre c.lexstatere = newtab c.lexstateerrorf = {} for key, ef in self.lexstateerrorf.items(): c.lexstateerrorf[key] = getattr(object, ef.__name__) c.lexmodule = object return c # ------------------------------------------------------------ # input() - Push a new string into the lexer # ------------------------------------------------------------ def input(self, s): self.lexdata = s self.lexpos = 0 self.lexlen = len(s) # ------------------------------------------------------------ # begin() - Changes the lexing state # ------------------------------------------------------------ def begin(self, state): if state not in self.lexstatere: raise ValueError(f'Undefined state {state!r}') self.lexre = self.lexstatere[state] self.lexretext = self.lexstateretext[state] self.lexignore = self.lexstateignore.get(state, '') self.lexerrorf = self.lexstateerrorf.get(state, None) self.lexeoff = self.lexstateeoff.get(state, None) self.lexstate = state # ------------------------------------------------------------ # push_state() - Changes the lexing state and saves old on stack # ------------------------------------------------------------ def push_state(self, state): self.lexstatestack.append(self.lexstate) self.begin(state) # ------------------------------------------------------------ # pop_state() - Restores the previous state # ------------------------------------------------------------ def pop_state(self): self.begin(self.lexstatestack.pop()) # ------------------------------------------------------------ # current_state() - Returns the current lexing state # ------------------------------------------------------------ def current_state(self): return self.lexstate # ------------------------------------------------------------ # skip() - Skip ahead n characters # ------------------------------------------------------------ def skip(self, n): self.lexpos += n # ------------------------------------------------------------ # token() - Return the next token from the Lexer # # Note: This function has been carefully implemented to be as fast # as possible. Don't make changes unless you really know what # you are doing # ------------------------------------------------------------ def token(self): # Make local copies of frequently referenced attributes lexpos = self.lexpos lexlen = self.lexlen lexignore = self.lexignore lexdata = self.lexdata while lexpos < lexlen: # This code provides some short-circuit code for whitespace, tabs, and other ignored characters if lexdata[lexpos] in lexignore: lexpos += 1 continue # Look for a regular expression match for lexre, lexindexfunc in self.lexre: m = lexre.match(lexdata, lexpos) if not m: continue # Create a token for return tok = LexToken() tok.value = m.group() tok.lineno = self.lineno tok.lexpos = lexpos i = m.lastindex func, tok.type = lexindexfunc[i] if not func: # If no token type was set, it's an ignored token if tok.type: self.lexpos = m.end() return tok else: lexpos = m.end() break lexpos = m.end() # If token is processed by a function, call it tok.lexer = self # Set additional attributes useful in token rules self.lexmatch = m self.lexpos = lexpos newtok = func(tok) del tok.lexer del self.lexmatch # Every function must return a token, if nothing, we just move to next token if not newtok: lexpos = self.lexpos # This is here in case user has updated lexpos. lexignore = self.lexignore # This is here in case there was a state change break return newtok else: # No match, see if in literals if lexdata[lexpos] in self.lexliterals: tok = LexToken() tok.value = lexdata[lexpos] tok.lineno = self.lineno tok.type = tok.value tok.lexpos = lexpos self.lexpos = lexpos + 1 return tok # No match. Call t_error() if defined. if self.lexerrorf: tok = LexToken() tok.value = self.lexdata[lexpos:] tok.lineno = self.lineno tok.type = 'error' tok.lexer = self tok.lexpos = lexpos self.lexpos = lexpos newtok = self.lexerrorf(tok) if lexpos == self.lexpos: # Error method didn't change text position at all. This is an error. raise LexError(f"Scanning error. Illegal character {lexdata[lexpos]!r}", lexdata[lexpos:]) lexpos = self.lexpos if not newtok: continue return newtok self.lexpos = lexpos raise LexError(f"Illegal character {lexdata[lexpos]!r} at index {lexpos}", lexdata[lexpos:]) if self.lexeoff: tok = LexToken() tok.type = 'eof' tok.value = '' tok.lineno = self.lineno tok.lexpos = lexpos tok.lexer = self self.lexpos = lexpos newtok = self.lexeoff(tok) return newtok self.lexpos = lexpos + 1 if self.lexdata is None: raise RuntimeError('No input string given with input()') return None # Iterator interface def __iter__(self): return self def __next__(self): t = self.token() if t is None: raise StopIteration return t # ----------------------------------------------------------------------------- # ==== Lex Builder === # # The functions and classes below are used to collect lexing information # and build a Lexer object from it. # ----------------------------------------------------------------------------- # ----------------------------------------------------------------------------- # _get_regex(func) # # Returns the regular expression assigned to a function either as a doc string # or as a .regex attribute attached by the @TOKEN decorator. # ----------------------------------------------------------------------------- def _get_regex(func): return getattr(func, 'regex', func.__doc__) # ----------------------------------------------------------------------------- # get_caller_module_dict() # # This function returns a dictionary containing all of the symbols defined within # a caller further down the call stack. This is used to get the environment # associated with the yacc() call if none was provided. # ----------------------------------------------------------------------------- def get_caller_module_dict(levels): f = sys._getframe(levels) return { **f.f_globals, **f.f_locals } # ----------------------------------------------------------------------------- # _form_master_re() # # This function takes a list of all of the regex components and attempts to # form the master regular expression. Given limitations in the Python re # module, it may be necessary to break the master regex into separate expressions. # ----------------------------------------------------------------------------- def _form_master_re(relist, reflags, ldict, toknames): if not relist: return [], [], [] regex = '|'.join(relist) try: lexre = re.compile(regex, reflags) # Build the index to function map for the matching engine lexindexfunc = [None] * (max(lexre.groupindex.values()) + 1) lexindexnames = lexindexfunc[:] for f, i in lexre.groupindex.items(): handle = ldict.get(f, None) if type(handle) in (types.FunctionType, types.MethodType): lexindexfunc[i] = (handle, toknames[f]) lexindexnames[i] = f elif handle is not None: lexindexnames[i] = f if f.find('ignore_') > 0: lexindexfunc[i] = (None, None) else: lexindexfunc[i] = (None, toknames[f]) return [(lexre, lexindexfunc)], [regex], [lexindexnames] except Exception: m = (len(relist) // 2) + 1 llist, lre, lnames = _form_master_re(relist[:m], reflags, ldict, toknames) rlist, rre, rnames = _form_master_re(relist[m:], reflags, ldict, toknames) return (llist+rlist), (lre+rre), (lnames+rnames) # ----------------------------------------------------------------------------- # def _statetoken(s,names) # # Given a declaration name s of the form "t_" and a dictionary whose keys are # state names, this function returns a tuple (states,tokenname) where states # is a tuple of state names and tokenname is the name of the token. For example, # calling this with s = "t_foo_bar_SPAM" might return (('foo','bar'),'SPAM') # ----------------------------------------------------------------------------- def _statetoken(s, names): parts = s.split('_') for i, part in enumerate(parts[1:], 1): if part not in names and part != 'ANY': break if i > 1: states = tuple(parts[1:i]) else: states = ('INITIAL',) if 'ANY' in states: states = tuple(names) tokenname = '_'.join(parts[i:]) return (states, tokenname) # ----------------------------------------------------------------------------- # LexerReflect() # # This class represents information needed to build a lexer as extracted from a # user's input file. # ----------------------------------------------------------------------------- class LexerReflect(object): def __init__(self, ldict, log=None, reflags=0): self.ldict = ldict self.error_func = None self.tokens = [] self.reflags = reflags self.stateinfo = {'INITIAL': 'inclusive'} self.modules = set() self.error = False self.log = PlyLogger(sys.stderr) if log is None else log # Get all of the basic information def get_all(self): self.get_tokens() self.get_literals() self.get_states() self.get_rules() # Validate all of the information def validate_all(self): self.validate_tokens() self.validate_literals() self.validate_rules() return self.error # Get the tokens map def get_tokens(self): tokens = self.ldict.get('tokens', None) if not tokens: self.log.error('No token list is defined') self.error = True return if not isinstance(tokens, (list, tuple)): self.log.error('tokens must be a list or tuple') self.error = True return if not tokens: self.log.error('tokens is empty') self.error = True return self.tokens = tokens # Validate the tokens def validate_tokens(self): terminals = {} for n in self.tokens: if not _is_identifier.match(n): self.log.error(f"Bad token name {n!r}") self.error = True if n in terminals: self.log.warning(f"Token {n!r} multiply defined") terminals[n] = 1 # Get the literals specifier def get_literals(self): self.literals = self.ldict.get('literals', '') if not self.literals: self.literals = '' # Validate literals def validate_literals(self): try: for c in self.literals: if not isinstance(c, StringTypes) or len(c) > 1: self.log.error(f'Invalid literal {c!r}. Must be a single character') self.error = True except TypeError: self.log.error('Invalid literals specification. literals must be a sequence of characters') self.error = True def get_states(self): self.states = self.ldict.get('states', None) # Build statemap if self.states: if not isinstance(self.states, (tuple, list)): self.log.error('states must be defined as a tuple or list') self.error = True else: for s in self.states: if not isinstance(s, tuple) or len(s) != 2: self.log.error("Invalid state specifier %r. Must be a tuple (statename,'exclusive|inclusive')", s) self.error = True continue name, statetype = s if not isinstance(name, StringTypes): self.log.error('State name %r must be a string', name) self.error = True continue if not (statetype == 'inclusive' or statetype == 'exclusive'): self.log.error("State type for state %r must be 'inclusive' or 'exclusive'", name) self.error = True continue if name in self.stateinfo: self.log.error("State %r already defined", name) self.error = True continue self.stateinfo[name] = statetype # Get all of the symbols with a t_ prefix and sort them into various # categories (functions, strings, error functions, and ignore characters) def get_rules(self): tsymbols = [f for f in self.ldict if f[:2] == 't_'] # Now build up a list of functions and a list of strings self.toknames = {} # Mapping of symbols to token names self.funcsym = {} # Symbols defined as functions self.strsym = {} # Symbols defined as strings self.ignore = {} # Ignore strings by state self.errorf = {} # Error functions by state self.eoff = {} # EOF functions by state for s in self.stateinfo: self.funcsym[s] = [] self.strsym[s] = [] if len(tsymbols) == 0: self.log.error('No rules of the form t_rulename are defined') self.error = True return for f in tsymbols: t = self.ldict[f] states, tokname = _statetoken(f, self.stateinfo) self.toknames[f] = tokname if hasattr(t, '__call__'): if tokname == 'error': for s in states: self.errorf[s] = t elif tokname == 'eof': for s in states: self.eoff[s] = t elif tokname == 'ignore': line = t.__code__.co_firstlineno file = t.__code__.co_filename self.log.error("%s:%d: Rule %r must be defined as a string", file, line, t.__name__) self.error = True else: for s in states: self.funcsym[s].append((f, t)) elif isinstance(t, StringTypes): if tokname == 'ignore': for s in states: self.ignore[s] = t if '\\' in t: self.log.warning("%s contains a literal backslash '\\'", f) elif tokname == 'error': self.log.error("Rule %r must be defined as a function", f) self.error = True else: for s in states: self.strsym[s].append((f, t)) else: self.log.error('%s not defined as a function or string', f) self.error = True # Sort the functions by line number for f in self.funcsym.values(): f.sort(key=lambda x: x[1].__code__.co_firstlineno) # Sort the strings by regular expression length for s in self.strsym.values(): s.sort(key=lambda x: len(x[1]), reverse=True) # Validate all of the t_rules collected def validate_rules(self): for state in self.stateinfo: # Validate all rules defined by functions for fname, f in self.funcsym[state]: line = f.__code__.co_firstlineno file = f.__code__.co_filename module = inspect.getmodule(f) self.modules.add(module) tokname = self.toknames[fname] if isinstance(f, types.MethodType): reqargs = 2 else: reqargs = 1 nargs = f.__code__.co_argcount if nargs > reqargs: self.log.error("%s:%d: Rule %r has too many arguments", file, line, f.__name__) self.error = True continue if nargs < reqargs: self.log.error("%s:%d: Rule %r requires an argument", file, line, f.__name__) self.error = True continue if not _get_regex(f): self.log.error("%s:%d: No regular expression defined for rule %r", file, line, f.__name__) self.error = True continue try: c = re.compile('(?P<%s>%s)' % (fname, _get_regex(f)), self.reflags) if c.match(''): self.log.error("%s:%d: Regular expression for rule %r matches empty string", file, line, f.__name__) self.error = True except re.error as e: self.log.error("%s:%d: Invalid regular expression for rule '%s'. %s", file, line, f.__name__, e) if '#' in _get_regex(f): self.log.error("%s:%d. Make sure '#' in rule %r is escaped with '\\#'", file, line, f.__name__) self.error = True # Validate all rules defined by strings for name, r in self.strsym[state]: tokname = self.toknames[name] if tokname == 'error': self.log.error("Rule %r must be defined as a function", name) self.error = True continue if tokname not in self.tokens and tokname.find('ignore_') < 0: self.log.error("Rule %r defined for an unspecified token %s", name, tokname) self.error = True continue try: c = re.compile('(?P<%s>%s)' % (name, r), self.reflags) if (c.match('')): self.log.error("Regular expression for rule %r matches empty string", name) self.error = True except re.error as e: self.log.error("Invalid regular expression for rule %r. %s", name, e) if '#' in r: self.log.error("Make sure '#' in rule %r is escaped with '\\#'", name) self.error = True if not self.funcsym[state] and not self.strsym[state]: self.log.error("No rules defined for state %r", state) self.error = True # Validate the error function efunc = self.errorf.get(state, None) if efunc: f = efunc line = f.__code__.co_firstlineno file = f.__code__.co_filename module = inspect.getmodule(f) self.modules.add(module) if isinstance(f, types.MethodType): reqargs = 2 else: reqargs = 1 nargs = f.__code__.co_argcount if nargs > reqargs: self.log.error("%s:%d: Rule %r has too many arguments", file, line, f.__name__) self.error = True if nargs < reqargs: self.log.error("%s:%d: Rule %r requires an argument", file, line, f.__name__) self.error = True for module in self.modules: self.validate_module(module) # ----------------------------------------------------------------------------- # validate_module() # # This checks to see if there are duplicated t_rulename() functions or strings # in the parser input file. This is done using a simple regular expression # match on each line in the source code of the given module. # ----------------------------------------------------------------------------- def validate_module(self, module): try: lines, linen = inspect.getsourcelines(module) except IOError: return fre = re.compile(r'\s*def\s+(t_[a-zA-Z_0-9]*)\(') sre = re.compile(r'\s*(t_[a-zA-Z_0-9]*)\s*=') counthash = {} linen += 1 for line in lines: m = fre.match(line) if not m: m = sre.match(line) if m: name = m.group(1) prev = counthash.get(name) if not prev: counthash[name] = linen else: filename = inspect.getsourcefile(module) self.log.error('%s:%d: Rule %s redefined. Previously defined on line %d', filename, linen, name, prev) self.error = True linen += 1 # ----------------------------------------------------------------------------- # lex(module) # # Build all of the regular expression rules from definitions in the supplied module # ----------------------------------------------------------------------------- def lex(*, module=None, object=None, debug=False, reflags=int(re.VERBOSE), debuglog=None, errorlog=None): global lexer ldict = None stateinfo = {'INITIAL': 'inclusive'} lexobj = Lexer() global token, input if errorlog is None: errorlog = PlyLogger(sys.stderr) if debug: if debuglog is None: debuglog = PlyLogger(sys.stderr) # Get the module dictionary used for the lexer if object: module = object # Get the module dictionary used for the parser if module: _items = [(k, getattr(module, k)) for k in dir(module)] ldict = dict(_items) # If no __file__ attribute is available, try to obtain it from the __module__ instead if '__file__' not in ldict: ldict['__file__'] = sys.modules[ldict['__module__']].__file__ else: ldict = get_caller_module_dict(2) # Collect parser information from the dictionary linfo = LexerReflect(ldict, log=errorlog, reflags=reflags) linfo.get_all() if linfo.validate_all(): raise SyntaxError("Can't build lexer") # Dump some basic debugging information if debug: debuglog.info('lex: tokens = %r', linfo.tokens) debuglog.info('lex: literals = %r', linfo.literals) debuglog.info('lex: states = %r', linfo.stateinfo) # Build a dictionary of valid token names lexobj.lextokens = set() for n in linfo.tokens: lexobj.lextokens.add(n) # Get literals specification if isinstance(linfo.literals, (list, tuple)): lexobj.lexliterals = type(linfo.literals[0])().join(linfo.literals) else: lexobj.lexliterals = linfo.literals lexobj.lextokens_all = lexobj.lextokens | set(lexobj.lexliterals) # Get the stateinfo dictionary stateinfo = linfo.stateinfo regexs = {} # Build the master regular expressions for state in stateinfo: regex_list = [] # Add rules defined by functions first for fname, f in linfo.funcsym[state]: regex_list.append('(?P<%s>%s)' % (fname, _get_regex(f))) if debug: debuglog.info("lex: Adding rule %s -> '%s' (state '%s')", fname, _get_regex(f), state) # Now add all of the simple rules for name, r in linfo.strsym[state]: regex_list.append('(?P<%s>%s)' % (name, r)) if debug: debuglog.info("lex: Adding rule %s -> '%s' (state '%s')", name, r, state) regexs[state] = regex_list # Build the master regular expressions if debug: debuglog.info('lex: ==== MASTER REGEXS FOLLOW ====') for state in regexs: lexre, re_text, re_names = _form_master_re(regexs[state], reflags, ldict, linfo.toknames) lexobj.lexstatere[state] = lexre lexobj.lexstateretext[state] = re_text lexobj.lexstaterenames[state] = re_names if debug: for i, text in enumerate(re_text): debuglog.info("lex: state '%s' : regex[%d] = '%s'", state, i, text) # For inclusive states, we need to add the regular expressions from the INITIAL state for state, stype in stateinfo.items(): if state != 'INITIAL' and stype == 'inclusive': lexobj.lexstatere[state].extend(lexobj.lexstatere['INITIAL']) lexobj.lexstateretext[state].extend(lexobj.lexstateretext['INITIAL']) lexobj.lexstaterenames[state].extend(lexobj.lexstaterenames['INITIAL']) lexobj.lexstateinfo = stateinfo lexobj.lexre = lexobj.lexstatere['INITIAL'] lexobj.lexretext = lexobj.lexstateretext['INITIAL'] lexobj.lexreflags = reflags # Set up ignore variables lexobj.lexstateignore = linfo.ignore lexobj.lexignore = lexobj.lexstateignore.get('INITIAL', '') # Set up error functions lexobj.lexstateerrorf = linfo.errorf lexobj.lexerrorf = linfo.errorf.get('INITIAL', None) if not lexobj.lexerrorf: errorlog.warning('No t_error rule is defined') # Set up eof functions lexobj.lexstateeoff = linfo.eoff lexobj.lexeoff = linfo.eoff.get('INITIAL', None) # Check state information for ignore and error rules for s, stype in stateinfo.items(): if stype == 'exclusive': if s not in linfo.errorf: errorlog.warning("No error rule is defined for exclusive state %r", s) if s not in linfo.ignore and lexobj.lexignore: errorlog.warning("No ignore rule is defined for exclusive state %r", s) elif stype == 'inclusive': if s not in linfo.errorf: linfo.errorf[s] = linfo.errorf.get('INITIAL', None) if s not in linfo.ignore: linfo.ignore[s] = linfo.ignore.get('INITIAL', '') # Create global versions of the token() and input() functions token = lexobj.token input = lexobj.input lexer = lexobj return lexobj # ----------------------------------------------------------------------------- # runmain() # # This runs the lexer as a main program # ----------------------------------------------------------------------------- def runmain(lexer=None, data=None): if not data: try: filename = sys.argv[1] with open(filename) as f: data = f.read() except IndexError: sys.stdout.write('Reading from standard input (type EOF to end):\n') data = sys.stdin.read() if lexer: _input = lexer.input else: _input = input _input(data) if lexer: _token = lexer.token else: _token = token while True: tok = _token() if not tok: break sys.stdout.write(f'({tok.type},{tok.value!r},{tok.lineno},{tok.lexpos})\n') # ----------------------------------------------------------------------------- # @TOKEN(regex) # # This decorator function can be used to set the regex expression on a function # when its docstring might need to be set in an alternative way # ----------------------------------------------------------------------------- def TOKEN(r): def set_regex(f): if hasattr(r, '__call__'): f.regex = _get_regex(r) else: f.regex = r return f return set_regex sip-6.8.6/sipbuild/generator/parser/ply/yacc.py000066400000000000000000003002061464421045000215210ustar00rootroot00000000000000# ----------------------------------------------------------------------------- # ply: yacc.py # # Copyright (C) 2001-2022 # David M. Beazley (Dabeaz LLC) # All rights reserved. # # Latest version: https://github.com/dabeaz/ply # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are # met: # # * Redistributions of source code must retain the above copyright notice, # this list of conditions and the following disclaimer. # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions and the following disclaimer in the documentation # and/or other materials provided with the distribution. # * Neither the name of David Beazley or Dabeaz LLC may be used to # endorse or promote products derived from this software without # specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # ----------------------------------------------------------------------------- # # This implements an LR parser that is constructed from grammar rules defined # as Python functions. The grammar is specified by supplying the BNF inside # Python documentation strings. The inspiration for this technique was borrowed # from John Aycock's Spark parsing system. PLY might be viewed as cross between # Spark and the GNU bison utility. # # The current implementation is only somewhat object-oriented. The # LR parser itself is defined in terms of an object (which allows multiple # parsers to co-exist). However, most of the variables used during table # construction are defined in terms of global variables. Users shouldn't # notice unless they are trying to define multiple parsers at the same # time using threads (in which case they should have their head examined). # # This implementation supports both SLR and LALR(1) parsing. LALR(1) # support was originally implemented by Elias Ioup (ezioup@alumni.uchicago.edu), # using the algorithm found in Aho, Sethi, and Ullman "Compilers: Principles, # Techniques, and Tools" (The Dragon Book). LALR(1) has since been replaced # by the more efficient DeRemer and Pennello algorithm. # # :::::::: WARNING ::::::: # # Construction of LR parsing tables is fairly complicated and expensive. # To make this module run fast, a *LOT* of work has been put into # optimization---often at the expensive of readability and what might # consider to be good Python "coding style." Modify the code at your # own risk! # ---------------------------------------------------------------------------- import re import types import sys import inspect #----------------------------------------------------------------------------- # === User configurable parameters === # # Change these to modify the default behavior of yacc (if you wish) #----------------------------------------------------------------------------- yaccdebug = False # Debugging mode. If set, yacc generates a # a 'parser.out' file in the current directory debug_file = 'parser.out' # Default name of the debugging file error_count = 3 # Number of symbols that must be shifted to leave recovery mode resultlimit = 40 # Size limit of results when running in debug mode. MAXINT = sys.maxsize # This object is a stand-in for a logging object created by the # logging module. PLY will use this by default to create things # such as the parser.out file. If a user wants more detailed # information, they can create their own logging object and pass # it into PLY. class PlyLogger(object): def __init__(self, f): self.f = f def debug(self, msg, *args, **kwargs): self.f.write((msg % args) + '\n') info = debug def warning(self, msg, *args, **kwargs): self.f.write('WARNING: ' + (msg % args) + '\n') def error(self, msg, *args, **kwargs): self.f.write('ERROR: ' + (msg % args) + '\n') critical = debug # Null logger is used when no output is generated. Does nothing. class NullLogger(object): def __getattribute__(self, name): return self def __call__(self, *args, **kwargs): return self # Exception raised for yacc-related errors class YaccError(Exception): pass # Format the result message that the parser produces when running in debug mode. def format_result(r): repr_str = repr(r) if '\n' in repr_str: repr_str = repr(repr_str) if len(repr_str) > resultlimit: repr_str = repr_str[:resultlimit] + ' ...' result = '<%s @ 0x%x> (%s)' % (type(r).__name__, id(r), repr_str) return result # Format stack entries when the parser is running in debug mode def format_stack_entry(r): repr_str = repr(r) if '\n' in repr_str: repr_str = repr(repr_str) if len(repr_str) < 16: return repr_str else: return '<%s @ 0x%x>' % (type(r).__name__, id(r)) #----------------------------------------------------------------------------- # === LR Parsing Engine === # # The following classes are used for the LR parser itself. These are not # used during table construction and are independent of the actual LR # table generation algorithm #----------------------------------------------------------------------------- # This class is used to hold non-terminal grammar symbols during parsing. # It normally has the following attributes set: # .type = Grammar symbol type # .value = Symbol value # .lineno = Starting line number # .endlineno = Ending line number (optional, set automatically) # .lexpos = Starting lex position # .endlexpos = Ending lex position (optional, set automatically) class YaccSymbol: def __str__(self): return self.type def __repr__(self): return str(self) # This class is a wrapper around the objects actually passed to each # grammar rule. Index lookup and assignment actually assign the # .value attribute of the underlying YaccSymbol object. # The lineno() method returns the line number of a given # item (or 0 if not defined). The linespan() method returns # a tuple of (startline,endline) representing the range of lines # for a symbol. The lexspan() method returns a tuple (lexpos,endlexpos) # representing the range of positional information for a symbol. class YaccProduction: def __init__(self, s, stack=None): self.slice = s self.stack = stack self.lexer = None self.parser = None def __getitem__(self, n): if isinstance(n, slice): return [s.value for s in self.slice[n]] elif n >= 0: return self.slice[n].value else: return self.stack[n].value def __setitem__(self, n, v): self.slice[n].value = v def __getslice__(self, i, j): return [s.value for s in self.slice[i:j]] def __len__(self): return len(self.slice) def lineno(self, n): return getattr(self.slice[n], 'lineno', 0) def set_lineno(self, n, lineno): self.slice[n].lineno = lineno def linespan(self, n): startline = getattr(self.slice[n], 'lineno', 0) endline = getattr(self.slice[n], 'endlineno', startline) return startline, endline def lexpos(self, n): return getattr(self.slice[n], 'lexpos', 0) def set_lexpos(self, n, lexpos): self.slice[n].lexpos = lexpos def lexspan(self, n): startpos = getattr(self.slice[n], 'lexpos', 0) endpos = getattr(self.slice[n], 'endlexpos', startpos) return startpos, endpos def error(self): raise SyntaxError # ----------------------------------------------------------------------------- # == LRParser == # # The LR Parsing engine. # ----------------------------------------------------------------------------- class LRParser: def __init__(self, lrtab, errorf): self.productions = lrtab.lr_productions self.action = lrtab.lr_action self.goto = lrtab.lr_goto self.errorfunc = errorf self.set_defaulted_states() self.errorok = True def errok(self): self.errorok = True def restart(self): del self.statestack[:] del self.symstack[:] sym = YaccSymbol() sym.type = '$end' self.symstack.append(sym) self.statestack.append(0) # Defaulted state support. # This method identifies parser states where there is only one possible reduction action. # For such states, the parser can make a choose to make a rule reduction without consuming # the next look-ahead token. This delayed invocation of the tokenizer can be useful in # certain kinds of advanced parsing situations where the lexer and parser interact with # each other or change states (i.e., manipulation of scope, lexer states, etc.). # # See: http://www.gnu.org/software/bison/manual/html_node/Default-Reductions.html#Default-Reductions def set_defaulted_states(self): self.defaulted_states = {} for state, actions in self.action.items(): rules = list(actions.values()) if len(rules) == 1 and rules[0] < 0: self.defaulted_states[state] = rules[0] def disable_defaulted_states(self): self.defaulted_states = {} # parse(). # # This is the core parsing engine. To operate, it requires a lexer object. # Two options are provided. The debug flag turns on debugging so that you can # see the various rule reductions and parsing steps. tracking turns on position # tracking. In this mode, symbols will record the starting/ending line number and # character index. def parse(self, input=None, lexer=None, debug=False, tracking=False): # If debugging has been specified as a flag, turn it into a logging object if isinstance(debug, int) and debug: debug = PlyLogger(sys.stderr) lookahead = None # Current lookahead symbol lookaheadstack = [] # Stack of lookahead symbols actions = self.action # Local reference to action table (to avoid lookup on self.) goto = self.goto # Local reference to goto table (to avoid lookup on self.) prod = self.productions # Local reference to production list (to avoid lookup on self.) defaulted_states = self.defaulted_states # Local reference to defaulted states pslice = YaccProduction(None) # Production object passed to grammar rules errorcount = 0 # Used during error recovery if debug: debug.info('PLY: PARSE DEBUG START') # If no lexer was given, we will try to use the lex module if not lexer: from . import lex lexer = lex.lexer # Set up the lexer and parser objects on pslice pslice.lexer = lexer pslice.parser = self # If input was supplied, pass to lexer if input is not None: lexer.input(input) # Set the token function get_token = self.token = lexer.token # Set up the state and symbol stacks statestack = self.statestack = [] # Stack of parsing states symstack = self.symstack = [] # Stack of grammar symbols pslice.stack = symstack # Put in the production errtoken = None # Err token # The start state is assumed to be (0,$end) statestack.append(0) sym = YaccSymbol() sym.type = '$end' symstack.append(sym) state = 0 while True: # Get the next symbol on the input. If a lookahead symbol # is already set, we just use that. Otherwise, we'll pull # the next token off of the lookaheadstack or from the lexer if debug: debug.debug('State : %s', state) if state not in defaulted_states: if not lookahead: if not lookaheadstack: lookahead = get_token() # Get the next token else: lookahead = lookaheadstack.pop() if not lookahead: lookahead = YaccSymbol() lookahead.type = '$end' # Check the action table ltype = lookahead.type t = actions[state].get(ltype) else: t = defaulted_states[state] if debug: debug.debug('Defaulted state %s: Reduce using %d', state, -t) if debug: debug.debug('Stack : %s', ('%s . %s' % (' '.join([xx.type for xx in symstack][1:]), str(lookahead))).lstrip()) if t is not None: if t > 0: # shift a symbol on the stack statestack.append(t) state = t if debug: debug.debug('Action : Shift and goto state %s', t) symstack.append(lookahead) lookahead = None # Decrease error count on successful shift if errorcount: errorcount -= 1 continue if t < 0: # reduce a symbol on the stack, emit a production p = prod[-t] pname = p.name plen = p.len # Get production function sym = YaccSymbol() sym.type = pname # Production name sym.value = None if debug: if plen: debug.info('Action : Reduce rule [%s] with %s and goto state %d', p.str, '['+','.join([format_stack_entry(_v.value) for _v in symstack[-plen:]])+']', goto[statestack[-1-plen]][pname]) else: debug.info('Action : Reduce rule [%s] with %s and goto state %d', p.str, [], goto[statestack[-1]][pname]) if plen: targ = symstack[-plen-1:] targ[0] = sym if tracking: t1 = targ[1] sym.lineno = t1.lineno sym.lexpos = t1.lexpos t1 = targ[-1] sym.endlineno = getattr(t1, 'endlineno', t1.lineno) sym.endlexpos = getattr(t1, 'endlexpos', t1.lexpos) # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # The code enclosed in this section is duplicated # below as a performance optimization. Make sure # changes get made in both locations. pslice.slice = targ try: # Call the grammar rule with our special slice object del symstack[-plen:] self.state = state p.callable(pslice) del statestack[-plen:] if debug: debug.info('Result : %s', format_result(pslice[0])) symstack.append(sym) state = goto[statestack[-1]][pname] statestack.append(state) except SyntaxError: # If an error was set. Enter error recovery state lookaheadstack.append(lookahead) # Save the current lookahead token symstack.extend(targ[1:-1]) # Put the production slice back on the stack statestack.pop() # Pop back one state (before the reduce) state = statestack[-1] sym.type = 'error' sym.value = 'error' lookahead = sym errorcount = error_count self.errorok = False continue else: if tracking: sym.lineno = lexer.lineno sym.lexpos = lexer.lexpos targ = [sym] # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # The code enclosed in this section is duplicated # above as a performance optimization. Make sure # changes get made in both locations. pslice.slice = targ try: # Call the grammar rule with our special slice object self.state = state p.callable(pslice) if debug: debug.info('Result : %s', format_result(pslice[0])) symstack.append(sym) state = goto[statestack[-1]][pname] statestack.append(state) except SyntaxError: # If an error was set. Enter error recovery state lookaheadstack.append(lookahead) # Save the current lookahead token statestack.pop() # Pop back one state (before the reduce) state = statestack[-1] sym.type = 'error' sym.value = 'error' lookahead = sym errorcount = error_count self.errorok = False continue if t == 0: n = symstack[-1] result = getattr(n, 'value', None) if debug: debug.info('Done : Returning %s', format_result(result)) debug.info('PLY: PARSE DEBUG END') return result if t is None: if debug: debug.error('Error : %s', ('%s . %s' % (' '.join([xx.type for xx in symstack][1:]), str(lookahead))).lstrip()) # We have some kind of parsing error here. To handle # this, we are going to push the current token onto # the tokenstack and replace it with an 'error' token. # If there are any synchronization rules, they may # catch it. # # In addition to pushing the error token, we call call # the user defined p_error() function if this is the # first syntax error. This function is only called if # errorcount == 0. if errorcount == 0 or self.errorok: errorcount = error_count self.errorok = False errtoken = lookahead if errtoken.type == '$end': errtoken = None # End of file! if self.errorfunc: if errtoken and not hasattr(errtoken, 'lexer'): errtoken.lexer = lexer self.state = state tok = self.errorfunc(errtoken) if self.errorok: # User must have done some kind of panic # mode recovery on their own. The # returned token is the next lookahead lookahead = tok errtoken = None continue else: if errtoken: if hasattr(errtoken, 'lineno'): lineno = lookahead.lineno else: lineno = 0 if lineno: sys.stderr.write('yacc: Syntax error at line %d, token=%s\n' % (lineno, errtoken.type)) else: sys.stderr.write('yacc: Syntax error, token=%s' % errtoken.type) else: sys.stderr.write('yacc: Parse error in input. EOF\n') return else: errorcount = error_count # case 1: the statestack only has 1 entry on it. If we're in this state, the # entire parse has been rolled back and we're completely hosed. The token is # discarded and we just keep going. if len(statestack) <= 1 and lookahead.type != '$end': lookahead = None errtoken = None state = 0 # Nuke the pushback stack del lookaheadstack[:] continue # case 2: the statestack has a couple of entries on it, but we're # at the end of the file. nuke the top entry and generate an error token # Start nuking entries on the stack if lookahead.type == '$end': # Whoa. We're really hosed here. Bail out return if lookahead.type != 'error': sym = symstack[-1] if sym.type == 'error': # Hmmm. Error is on top of stack, we'll just nuke input # symbol and continue if tracking: sym.endlineno = getattr(lookahead, 'lineno', sym.lineno) sym.endlexpos = getattr(lookahead, 'lexpos', sym.lexpos) lookahead = None continue # Create the error symbol for the first time and make it the new lookahead symbol t = YaccSymbol() t.type = 'error' if hasattr(lookahead, 'lineno'): t.lineno = t.endlineno = lookahead.lineno if hasattr(lookahead, 'lexpos'): t.lexpos = t.endlexpos = lookahead.lexpos t.value = lookahead lookaheadstack.append(lookahead) lookahead = t else: sym = symstack.pop() if tracking: lookahead.lineno = sym.lineno lookahead.lexpos = sym.lexpos statestack.pop() state = statestack[-1] continue # If we'r here, something really bad happened raise RuntimeError('yacc: internal parser error!!!\n') # ----------------------------------------------------------------------------- # === Grammar Representation === # # The following functions, classes, and variables are used to represent and # manipulate the rules that make up a grammar. # ----------------------------------------------------------------------------- # regex matching identifiers _is_identifier = re.compile(r'^[a-zA-Z0-9_-]+$') # ----------------------------------------------------------------------------- # class Production: # # This class stores the raw information about a single production or grammar rule. # A grammar rule refers to a specification such as this: # # expr : expr PLUS term # # Here are the basic attributes defined on all productions # # name - Name of the production. For example 'expr' # prod - A list of symbols on the right side ['expr','PLUS','term'] # prec - Production precedence level # number - Production number. # func - Function that executes on reduce # file - File where production function is defined # lineno - Line number where production function is defined # # The following attributes are defined or optional. # # len - Length of the production (number of symbols on right hand side) # usyms - Set of unique symbols found in the production # ----------------------------------------------------------------------------- class Production(object): reduced = 0 def __init__(self, number, name, prod, precedence=('right', 0), func=None, file='', line=0): self.name = name self.prod = tuple(prod) self.number = number self.func = func self.callable = None self.file = file self.line = line self.prec = precedence # Internal settings used during table construction self.len = len(self.prod) # Length of the production # Create a list of unique production symbols used in the production self.usyms = [] for s in self.prod: if s not in self.usyms: self.usyms.append(s) # List of all LR items for the production self.lr_items = [] self.lr_next = None # Create a string representation if self.prod: self.str = '%s -> %s' % (self.name, ' '.join(self.prod)) else: self.str = '%s -> ' % self.name def __str__(self): return self.str def __repr__(self): return 'Production(' + str(self) + ')' def __len__(self): return len(self.prod) def __nonzero__(self): return 1 def __getitem__(self, index): return self.prod[index] # Return the nth lr_item from the production (or None if at the end) def lr_item(self, n): if n > len(self.prod): return None p = LRItem(self, n) # Precompute the list of productions immediately following. try: p.lr_after = self.Prodnames[p.prod[n+1]] except (IndexError, KeyError): p.lr_after = [] try: p.lr_before = p.prod[n-1] except IndexError: p.lr_before = None return p # Bind the production function name to a callable def bind(self, pdict): if self.func: self.callable = pdict[self.func] # ----------------------------------------------------------------------------- # class LRItem # # This class represents a specific stage of parsing a production rule. For # example: # # expr : expr . PLUS term # # In the above, the "." represents the current location of the parse. Here # basic attributes: # # name - Name of the production. For example 'expr' # prod - A list of symbols on the right side ['expr','.', 'PLUS','term'] # number - Production number. # # lr_next Next LR item. Example, if we are ' expr -> expr . PLUS term' # then lr_next refers to 'expr -> expr PLUS . term' # lr_index - LR item index (location of the ".") in the prod list. # lookaheads - LALR lookahead symbols for this item # len - Length of the production (number of symbols on right hand side) # lr_after - List of all productions that immediately follow # lr_before - Grammar symbol immediately before # ----------------------------------------------------------------------------- class LRItem(object): def __init__(self, p, n): self.name = p.name self.prod = list(p.prod) self.number = p.number self.lr_index = n self.lookaheads = {} self.prod.insert(n, '.') self.prod = tuple(self.prod) self.len = len(self.prod) self.usyms = p.usyms def __str__(self): if self.prod: s = '%s -> %s' % (self.name, ' '.join(self.prod)) else: s = '%s -> ' % self.name return s def __repr__(self): return 'LRItem(' + str(self) + ')' # ----------------------------------------------------------------------------- # rightmost_terminal() # # Return the rightmost terminal from a list of symbols. Used in add_production() # ----------------------------------------------------------------------------- def rightmost_terminal(symbols, terminals): i = len(symbols) - 1 while i >= 0: if symbols[i] in terminals: return symbols[i] i -= 1 return None # ----------------------------------------------------------------------------- # === GRAMMAR CLASS === # # The following class represents the contents of the specified grammar along # with various computed properties such as first sets, follow sets, LR items, etc. # This data is used for critical parts of the table generation process later. # ----------------------------------------------------------------------------- class GrammarError(YaccError): pass class Grammar(object): def __init__(self, terminals): self.Productions = [None] # A list of all of the productions. The first # entry is always reserved for the purpose of # building an augmented grammar self.Prodnames = {} # A dictionary mapping the names of nonterminals to a list of all # productions of that nonterminal. self.Prodmap = {} # A dictionary that is only used to detect duplicate # productions. self.Terminals = {} # A dictionary mapping the names of terminal symbols to a # list of the rules where they are used. for term in terminals: self.Terminals[term] = [] self.Terminals['error'] = [] self.Nonterminals = {} # A dictionary mapping names of nonterminals to a list # of rule numbers where they are used. self.First = {} # A dictionary of precomputed FIRST(x) symbols self.Follow = {} # A dictionary of precomputed FOLLOW(x) symbols self.Precedence = {} # Precedence rules for each terminal. Contains tuples of the # form ('right',level) or ('nonassoc', level) or ('left',level) self.UsedPrecedence = set() # Precedence rules that were actually used by the grammer. # This is only used to provide error checking and to generate # a warning about unused precedence rules. self.Start = None # Starting symbol for the grammar def __len__(self): return len(self.Productions) def __getitem__(self, index): return self.Productions[index] # ----------------------------------------------------------------------------- # set_precedence() # # Sets the precedence for a given terminal. assoc is the associativity such as # 'left','right', or 'nonassoc'. level is a numeric level. # # ----------------------------------------------------------------------------- def set_precedence(self, term, assoc, level): assert self.Productions == [None], 'Must call set_precedence() before add_production()' if term in self.Precedence: raise GrammarError('Precedence already specified for terminal %r' % term) if assoc not in ['left', 'right', 'nonassoc']: raise GrammarError("Associativity must be one of 'left','right', or 'nonassoc'") self.Precedence[term] = (assoc, level) # ----------------------------------------------------------------------------- # add_production() # # Given an action function, this function assembles a production rule and # computes its precedence level. # # The production rule is supplied as a list of symbols. For example, # a rule such as 'expr : expr PLUS term' has a production name of 'expr' and # symbols ['expr','PLUS','term']. # # Precedence is determined by the precedence of the right-most non-terminal # or the precedence of a terminal specified by %prec. # # A variety of error checks are performed to make sure production symbols # are valid and that %prec is used correctly. # ----------------------------------------------------------------------------- def add_production(self, prodname, syms, func=None, file='', line=0): if prodname in self.Terminals: raise GrammarError('%s:%d: Illegal rule name %r. Already defined as a token' % (file, line, prodname)) if prodname == 'error': raise GrammarError('%s:%d: Illegal rule name %r. error is a reserved word' % (file, line, prodname)) if not _is_identifier.match(prodname): raise GrammarError('%s:%d: Illegal rule name %r' % (file, line, prodname)) # Look for literal tokens for n, s in enumerate(syms): if s[0] in "'\"": try: c = eval(s) if (len(c) > 1): raise GrammarError('%s:%d: Literal token %s in rule %r may only be a single character' % (file, line, s, prodname)) if c not in self.Terminals: self.Terminals[c] = [] syms[n] = c continue except SyntaxError: pass if not _is_identifier.match(s) and s != '%prec': raise GrammarError('%s:%d: Illegal name %r in rule %r' % (file, line, s, prodname)) # Determine the precedence level if '%prec' in syms: if syms[-1] == '%prec': raise GrammarError('%s:%d: Syntax error. Nothing follows %%prec' % (file, line)) if syms[-2] != '%prec': raise GrammarError('%s:%d: Syntax error. %%prec can only appear at the end of a grammar rule' % (file, line)) precname = syms[-1] prodprec = self.Precedence.get(precname) if not prodprec: raise GrammarError('%s:%d: Nothing known about the precedence of %r' % (file, line, precname)) else: self.UsedPrecedence.add(precname) del syms[-2:] # Drop %prec from the rule else: # If no %prec, precedence is determined by the rightmost terminal symbol precname = rightmost_terminal(syms, self.Terminals) prodprec = self.Precedence.get(precname, ('right', 0)) # See if the rule is already in the rulemap map = '%s -> %s' % (prodname, syms) if map in self.Prodmap: m = self.Prodmap[map] raise GrammarError('%s:%d: Duplicate rule %s. ' % (file, line, m) + 'Previous definition at %s:%d' % (m.file, m.line)) # From this point on, everything is valid. Create a new Production instance pnumber = len(self.Productions) if prodname not in self.Nonterminals: self.Nonterminals[prodname] = [] # Add the production number to Terminals and Nonterminals for t in syms: if t in self.Terminals: self.Terminals[t].append(pnumber) else: if t not in self.Nonterminals: self.Nonterminals[t] = [] self.Nonterminals[t].append(pnumber) # Create a production and add it to the list of productions p = Production(pnumber, prodname, syms, prodprec, func, file, line) self.Productions.append(p) self.Prodmap[map] = p # Add to the global productions list try: self.Prodnames[prodname].append(p) except KeyError: self.Prodnames[prodname] = [p] # ----------------------------------------------------------------------------- # set_start() # # Sets the starting symbol and creates the augmented grammar. Production # rule 0 is S' -> start where start is the start symbol. # ----------------------------------------------------------------------------- def set_start(self, start=None): if not start: start = self.Productions[1].name if start not in self.Nonterminals: raise GrammarError('start symbol %s undefined' % start) self.Productions[0] = Production(0, "S'", [start]) self.Nonterminals[start].append(0) self.Start = start # ----------------------------------------------------------------------------- # find_unreachable() # # Find all of the nonterminal symbols that can't be reached from the starting # symbol. Returns a list of nonterminals that can't be reached. # ----------------------------------------------------------------------------- def find_unreachable(self): # Mark all symbols that are reachable from a symbol s def mark_reachable_from(s): if s in reachable: return reachable.add(s) for p in self.Prodnames.get(s, []): for r in p.prod: mark_reachable_from(r) reachable = set() mark_reachable_from(self.Productions[0].prod[0]) return [s for s in self.Nonterminals if s not in reachable] # ----------------------------------------------------------------------------- # infinite_cycles() # # This function looks at the various parsing rules and tries to detect # infinite recursion cycles (grammar rules where there is no possible way # to derive a string of only terminals). # ----------------------------------------------------------------------------- def infinite_cycles(self): terminates = {} # Terminals: for t in self.Terminals: terminates[t] = True terminates['$end'] = True # Nonterminals: # Initialize to false: for n in self.Nonterminals: terminates[n] = False # Then propagate termination until no change: while True: some_change = False for (n, pl) in self.Prodnames.items(): # Nonterminal n terminates iff any of its productions terminates. for p in pl: # Production p terminates iff all of its rhs symbols terminate. for s in p.prod: if not terminates[s]: # The symbol s does not terminate, # so production p does not terminate. p_terminates = False break else: # didn't break from the loop, # so every symbol s terminates # so production p terminates. p_terminates = True if p_terminates: # symbol n terminates! if not terminates[n]: terminates[n] = True some_change = True # Don't need to consider any more productions for this n. break if not some_change: break infinite = [] for (s, term) in terminates.items(): if not term: if s not in self.Prodnames and s not in self.Terminals and s != 'error': # s is used-but-not-defined, and we've already warned of that, # so it would be overkill to say that it's also non-terminating. pass else: infinite.append(s) return infinite # ----------------------------------------------------------------------------- # undefined_symbols() # # Find all symbols that were used the grammar, but not defined as tokens or # grammar rules. Returns a list of tuples (sym, prod) where sym in the symbol # and prod is the production where the symbol was used. # ----------------------------------------------------------------------------- def undefined_symbols(self): result = [] for p in self.Productions: if not p: continue for s in p.prod: if s not in self.Prodnames and s not in self.Terminals and s != 'error': result.append((s, p)) return result # ----------------------------------------------------------------------------- # unused_terminals() # # Find all terminals that were defined, but not used by the grammar. Returns # a list of all symbols. # ----------------------------------------------------------------------------- def unused_terminals(self): unused_tok = [] for s, v in self.Terminals.items(): if s != 'error' and not v: unused_tok.append(s) return unused_tok # ------------------------------------------------------------------------------ # unused_rules() # # Find all grammar rules that were defined, but not used (maybe not reachable) # Returns a list of productions. # ------------------------------------------------------------------------------ def unused_rules(self): unused_prod = [] for s, v in self.Nonterminals.items(): if not v: p = self.Prodnames[s][0] unused_prod.append(p) return unused_prod # ----------------------------------------------------------------------------- # unused_precedence() # # Returns a list of tuples (term,precedence) corresponding to precedence # rules that were never used by the grammar. term is the name of the terminal # on which precedence was applied and precedence is a string such as 'left' or # 'right' corresponding to the type of precedence. # ----------------------------------------------------------------------------- def unused_precedence(self): unused = [] for termname in self.Precedence: if not (termname in self.Terminals or termname in self.UsedPrecedence): unused.append((termname, self.Precedence[termname][0])) return unused # ------------------------------------------------------------------------- # _first() # # Compute the value of FIRST1(beta) where beta is a tuple of symbols. # # During execution of compute_first1, the result may be incomplete. # Afterward (e.g., when called from compute_follow()), it will be complete. # ------------------------------------------------------------------------- def _first(self, beta): # We are computing First(x1,x2,x3,...,xn) result = [] for x in beta: x_produces_empty = False # Add all the non- symbols of First[x] to the result. for f in self.First[x]: if f == '': x_produces_empty = True else: if f not in result: result.append(f) if x_produces_empty: # We have to consider the next x in beta, # i.e. stay in the loop. pass else: # We don't have to consider any further symbols in beta. break else: # There was no 'break' from the loop, # so x_produces_empty was true for all x in beta, # so beta produces empty as well. result.append('') return result # ------------------------------------------------------------------------- # compute_first() # # Compute the value of FIRST1(X) for all symbols # ------------------------------------------------------------------------- def compute_first(self): if self.First: return self.First # Terminals: for t in self.Terminals: self.First[t] = [t] self.First['$end'] = ['$end'] # Nonterminals: # Initialize to the empty set: for n in self.Nonterminals: self.First[n] = [] # Then propagate symbols until no change: while True: some_change = False for n in self.Nonterminals: for p in self.Prodnames[n]: for f in self._first(p.prod): if f not in self.First[n]: self.First[n].append(f) some_change = True if not some_change: break return self.First # --------------------------------------------------------------------- # compute_follow() # # Computes all of the follow sets for every non-terminal symbol. The # follow set is the set of all symbols that might follow a given # non-terminal. See the Dragon book, 2nd Ed. p. 189. # --------------------------------------------------------------------- def compute_follow(self, start=None): # If already computed, return the result if self.Follow: return self.Follow # If first sets not computed yet, do that first. if not self.First: self.compute_first() # Add '$end' to the follow list of the start symbol for k in self.Nonterminals: self.Follow[k] = [] if not start: start = self.Productions[1].name self.Follow[start] = ['$end'] while True: didadd = False for p in self.Productions[1:]: # Here is the production set for i, B in enumerate(p.prod): if B in self.Nonterminals: # Okay. We got a non-terminal in a production fst = self._first(p.prod[i+1:]) hasempty = False for f in fst: if f != '' and f not in self.Follow[B]: self.Follow[B].append(f) didadd = True if f == '': hasempty = True if hasempty or i == (len(p.prod)-1): # Add elements of follow(a) to follow(b) for f in self.Follow[p.name]: if f not in self.Follow[B]: self.Follow[B].append(f) didadd = True if not didadd: break return self.Follow # ----------------------------------------------------------------------------- # build_lritems() # # This function walks the list of productions and builds a complete set of the # LR items. The LR items are stored in two ways: First, they are uniquely # numbered and placed in the list _lritems. Second, a linked list of LR items # is built for each production. For example: # # E -> E PLUS E # # Creates the list # # [E -> . E PLUS E, E -> E . PLUS E, E -> E PLUS . E, E -> E PLUS E . ] # ----------------------------------------------------------------------------- def build_lritems(self): for p in self.Productions: lastlri = p i = 0 lr_items = [] while True: if i > len(p): lri = None else: lri = LRItem(p, i) # Precompute the list of productions immediately following try: lri.lr_after = self.Prodnames[lri.prod[i+1]] except (IndexError, KeyError): lri.lr_after = [] try: lri.lr_before = lri.prod[i-1] except IndexError: lri.lr_before = None lastlri.lr_next = lri if not lri: break lr_items.append(lri) lastlri = lri i += 1 p.lr_items = lr_items # ----------------------------------------------------------------------------- # === LR Generator === # # The following classes and functions are used to generate LR parsing tables on # a grammar. # ----------------------------------------------------------------------------- # ----------------------------------------------------------------------------- # digraph() # traverse() # # The following two functions are used to compute set valued functions # of the form: # # F(x) = F'(x) U U{F(y) | x R y} # # This is used to compute the values of Read() sets as well as FOLLOW sets # in LALR(1) generation. # # Inputs: X - An input set # R - A relation # FP - Set-valued function # ------------------------------------------------------------------------------ def digraph(X, R, FP): N = {} for x in X: N[x] = 0 stack = [] F = {} for x in X: if N[x] == 0: traverse(x, N, stack, F, X, R, FP) return F def traverse(x, N, stack, F, X, R, FP): stack.append(x) d = len(stack) N[x] = d F[x] = FP(x) # F(X) <- F'(x) rel = R(x) # Get y's related to x for y in rel: if N[y] == 0: traverse(y, N, stack, F, X, R, FP) N[x] = min(N[x], N[y]) for a in F.get(y, []): if a not in F[x]: F[x].append(a) if N[x] == d: N[stack[-1]] = MAXINT F[stack[-1]] = F[x] element = stack.pop() while element != x: N[stack[-1]] = MAXINT F[stack[-1]] = F[x] element = stack.pop() class LALRError(YaccError): pass # ----------------------------------------------------------------------------- # == LRTable == # # This class implements the LR table generation algorithm. There are no # public methods. # ----------------------------------------------------------------------------- class LRTable: def __init__(self, grammar, log=None): self.grammar = grammar # Set up the logger if not log: log = NullLogger() self.log = log # Internal attributes self.lr_action = {} # Action table self.lr_goto = {} # Goto table self.lr_productions = grammar.Productions # Copy of grammar Production array self.lr_goto_cache = {} # Cache of computed gotos self.lr0_cidhash = {} # Cache of closures self._add_count = 0 # Internal counter used to detect cycles # Diagnostic information filled in by the table generator self.sr_conflict = 0 self.rr_conflict = 0 self.conflicts = [] # List of conflicts self.sr_conflicts = [] self.rr_conflicts = [] # Build the tables self.grammar.build_lritems() self.grammar.compute_first() self.grammar.compute_follow() self.lr_parse_table() # Bind all production function names to callable objects in pdict def bind_callables(self, pdict): for p in self.lr_productions: p.bind(pdict) # Compute the LR(0) closure operation on I, where I is a set of LR(0) items. def lr0_closure(self, I): self._add_count += 1 # Add everything in I to J J = I[:] didadd = True while didadd: didadd = False for j in J: for x in j.lr_after: if getattr(x, 'lr0_added', 0) == self._add_count: continue # Add B --> .G to J J.append(x.lr_next) x.lr0_added = self._add_count didadd = True return J # Compute the LR(0) goto function goto(I,X) where I is a set # of LR(0) items and X is a grammar symbol. This function is written # in a way that guarantees uniqueness of the generated goto sets # (i.e. the same goto set will never be returned as two different Python # objects). With uniqueness, we can later do fast set comparisons using # id(obj) instead of element-wise comparison. def lr0_goto(self, I, x): # First we look for a previously cached entry g = self.lr_goto_cache.get((id(I), x)) if g: return g # Now we generate the goto set in a way that guarantees uniqueness # of the result s = self.lr_goto_cache.get(x) if not s: s = {} self.lr_goto_cache[x] = s gs = [] for p in I: n = p.lr_next if n and n.lr_before == x: s1 = s.get(id(n)) if not s1: s1 = {} s[id(n)] = s1 gs.append(n) s = s1 g = s.get('$end') if not g: if gs: g = self.lr0_closure(gs) s['$end'] = g else: s['$end'] = gs self.lr_goto_cache[(id(I), x)] = g return g # Compute the LR(0) sets of item function def lr0_items(self): C = [self.lr0_closure([self.grammar.Productions[0].lr_next])] i = 0 for I in C: self.lr0_cidhash[id(I)] = i i += 1 # Loop over the items in C and each grammar symbols i = 0 while i < len(C): I = C[i] i += 1 # Collect all of the symbols that could possibly be in the goto(I,X) sets asyms = {} for ii in I: for s in ii.usyms: asyms[s] = None for x in asyms: g = self.lr0_goto(I, x) if not g or id(g) in self.lr0_cidhash: continue self.lr0_cidhash[id(g)] = len(C) C.append(g) return C # ----------------------------------------------------------------------------- # ==== LALR(1) Parsing ==== # # LALR(1) parsing is almost exactly the same as SLR except that instead of # relying upon Follow() sets when performing reductions, a more selective # lookahead set that incorporates the state of the LR(0) machine is utilized. # Thus, we mainly just have to focus on calculating the lookahead sets. # # The method used here is due to DeRemer and Pennelo (1982). # # DeRemer, F. L., and T. J. Pennelo: "Efficient Computation of LALR(1) # Lookahead Sets", ACM Transactions on Programming Languages and Systems, # Vol. 4, No. 4, Oct. 1982, pp. 615-649 # # Further details can also be found in: # # J. Tremblay and P. Sorenson, "The Theory and Practice of Compiler Writing", # McGraw-Hill Book Company, (1985). # # ----------------------------------------------------------------------------- # ----------------------------------------------------------------------------- # compute_nullable_nonterminals() # # Creates a dictionary containing all of the non-terminals that might produce # an empty production. # ----------------------------------------------------------------------------- def compute_nullable_nonterminals(self): nullable = set() num_nullable = 0 while True: for p in self.grammar.Productions[1:]: if p.len == 0: nullable.add(p.name) continue for t in p.prod: if t not in nullable: break else: nullable.add(p.name) if len(nullable) == num_nullable: break num_nullable = len(nullable) return nullable # ----------------------------------------------------------------------------- # find_nonterminal_trans(C) # # Given a set of LR(0) items, this functions finds all of the non-terminal # transitions. These are transitions in which a dot appears immediately before # a non-terminal. Returns a list of tuples of the form (state,N) where state # is the state number and N is the nonterminal symbol. # # The input C is the set of LR(0) items. # ----------------------------------------------------------------------------- def find_nonterminal_transitions(self, C): trans = [] for stateno, state in enumerate(C): for p in state: if p.lr_index < p.len - 1: t = (stateno, p.prod[p.lr_index+1]) if t[1] in self.grammar.Nonterminals: if t not in trans: trans.append(t) return trans # ----------------------------------------------------------------------------- # dr_relation() # # Computes the DR(p,A) relationships for non-terminal transitions. The input # is a tuple (state,N) where state is a number and N is a nonterminal symbol. # # Returns a list of terminals. # ----------------------------------------------------------------------------- def dr_relation(self, C, trans, nullable): state, N = trans terms = [] g = self.lr0_goto(C[state], N) for p in g: if p.lr_index < p.len - 1: a = p.prod[p.lr_index+1] if a in self.grammar.Terminals: if a not in terms: terms.append(a) # This extra bit is to handle the start state if state == 0 and N == self.grammar.Productions[0].prod[0]: terms.append('$end') return terms # ----------------------------------------------------------------------------- # reads_relation() # # Computes the READS() relation (p,A) READS (t,C). # ----------------------------------------------------------------------------- def reads_relation(self, C, trans, empty): # Look for empty transitions rel = [] state, N = trans g = self.lr0_goto(C[state], N) j = self.lr0_cidhash.get(id(g), -1) for p in g: if p.lr_index < p.len - 1: a = p.prod[p.lr_index + 1] if a in empty: rel.append((j, a)) return rel # ----------------------------------------------------------------------------- # compute_lookback_includes() # # Determines the lookback and includes relations # # LOOKBACK: # # This relation is determined by running the LR(0) state machine forward. # For example, starting with a production "N : . A B C", we run it forward # to obtain "N : A B C ." We then build a relationship between this final # state and the starting state. These relationships are stored in a dictionary # lookdict. # # INCLUDES: # # Computes the INCLUDE() relation (p,A) INCLUDES (p',B). # # This relation is used to determine non-terminal transitions that occur # inside of other non-terminal transition states. (p,A) INCLUDES (p', B) # if the following holds: # # B -> LAT, where T -> epsilon and p' -L-> p # # L is essentially a prefix (which may be empty), T is a suffix that must be # able to derive an empty string. State p' must lead to state p with the string L. # # ----------------------------------------------------------------------------- def compute_lookback_includes(self, C, trans, nullable): lookdict = {} # Dictionary of lookback relations includedict = {} # Dictionary of include relations # Make a dictionary of non-terminal transitions dtrans = {} for t in trans: dtrans[t] = 1 # Loop over all transitions and compute lookbacks and includes for state, N in trans: lookb = [] includes = [] for p in C[state]: if p.name != N: continue # Okay, we have a name match. We now follow the production all the way # through the state machine until we get the . on the right hand side lr_index = p.lr_index j = state while lr_index < p.len - 1: lr_index = lr_index + 1 t = p.prod[lr_index] # Check to see if this symbol and state are a non-terminal transition if (j, t) in dtrans: # Yes. Okay, there is some chance that this is an includes relation # the only way to know for certain is whether the rest of the # production derives empty li = lr_index + 1 while li < p.len: if p.prod[li] in self.grammar.Terminals: break # No forget it if p.prod[li] not in nullable: break li = li + 1 else: # Appears to be a relation between (j,t) and (state,N) includes.append((j, t)) g = self.lr0_goto(C[j], t) # Go to next set j = self.lr0_cidhash.get(id(g), -1) # Go to next state # When we get here, j is the final state, now we have to locate the production for r in C[j]: if r.name != p.name: continue if r.len != p.len: continue i = 0 # This look is comparing a production ". A B C" with "A B C ." while i < r.lr_index: if r.prod[i] != p.prod[i+1]: break i = i + 1 else: lookb.append((j, r)) for i in includes: if i not in includedict: includedict[i] = [] includedict[i].append((state, N)) lookdict[(state, N)] = lookb return lookdict, includedict # ----------------------------------------------------------------------------- # compute_read_sets() # # Given a set of LR(0) items, this function computes the read sets. # # Inputs: C = Set of LR(0) items # ntrans = Set of nonterminal transitions # nullable = Set of empty transitions # # Returns a set containing the read sets # ----------------------------------------------------------------------------- def compute_read_sets(self, C, ntrans, nullable): FP = lambda x: self.dr_relation(C, x, nullable) R = lambda x: self.reads_relation(C, x, nullable) F = digraph(ntrans, R, FP) return F # ----------------------------------------------------------------------------- # compute_follow_sets() # # Given a set of LR(0) items, a set of non-terminal transitions, a readset, # and an include set, this function computes the follow sets # # Follow(p,A) = Read(p,A) U U {Follow(p',B) | (p,A) INCLUDES (p',B)} # # Inputs: # ntrans = Set of nonterminal transitions # readsets = Readset (previously computed) # inclsets = Include sets (previously computed) # # Returns a set containing the follow sets # ----------------------------------------------------------------------------- def compute_follow_sets(self, ntrans, readsets, inclsets): FP = lambda x: readsets[x] R = lambda x: inclsets.get(x, []) F = digraph(ntrans, R, FP) return F # ----------------------------------------------------------------------------- # add_lookaheads() # # Attaches the lookahead symbols to grammar rules. # # Inputs: lookbacks - Set of lookback relations # followset - Computed follow set # # This function directly attaches the lookaheads to productions contained # in the lookbacks set # ----------------------------------------------------------------------------- def add_lookaheads(self, lookbacks, followset): for trans, lb in lookbacks.items(): # Loop over productions in lookback for state, p in lb: if state not in p.lookaheads: p.lookaheads[state] = [] f = followset.get(trans, []) for a in f: if a not in p.lookaheads[state]: p.lookaheads[state].append(a) # ----------------------------------------------------------------------------- # add_lalr_lookaheads() # # This function does all of the work of adding lookahead information for use # with LALR parsing # ----------------------------------------------------------------------------- def add_lalr_lookaheads(self, C): # Determine all of the nullable nonterminals nullable = self.compute_nullable_nonterminals() # Find all non-terminal transitions trans = self.find_nonterminal_transitions(C) # Compute read sets readsets = self.compute_read_sets(C, trans, nullable) # Compute lookback/includes relations lookd, included = self.compute_lookback_includes(C, trans, nullable) # Compute LALR FOLLOW sets followsets = self.compute_follow_sets(trans, readsets, included) # Add all of the lookaheads self.add_lookaheads(lookd, followsets) # ----------------------------------------------------------------------------- # lr_parse_table() # # This function constructs the parse tables for SLR or LALR # ----------------------------------------------------------------------------- def lr_parse_table(self): Productions = self.grammar.Productions Precedence = self.grammar.Precedence goto = self.lr_goto # Goto array action = self.lr_action # Action array log = self.log # Logger for output actionp = {} # Action production array (temporary) # Step 1: Construct C = { I0, I1, ... IN}, collection of LR(0) items # This determines the number of states C = self.lr0_items() self.add_lalr_lookaheads(C) # Build the parser table, state by state st = 0 for I in C: # Loop over each production in I actlist = [] # List of actions st_action = {} st_actionp = {} st_goto = {} log.info('') log.info('state %d', st) log.info('') for p in I: log.info(' (%d) %s', p.number, p) log.info('') for p in I: if p.len == p.lr_index + 1: if p.name == "S'": # Start symbol. Accept! st_action['$end'] = 0 st_actionp['$end'] = p else: # We are at the end of a production. Reduce! laheads = p.lookaheads[st] for a in laheads: actlist.append((a, p, 'reduce using rule %d (%s)' % (p.number, p))) r = st_action.get(a) if r is not None: # Whoa. Have a shift/reduce or reduce/reduce conflict if r > 0: # Need to decide on shift or reduce here # By default we favor shifting. Need to add # some precedence rules here. # Shift precedence comes from the token sprec, slevel = Precedence.get(a, ('right', 0)) # Reduce precedence comes from rule being reduced (p) rprec, rlevel = Productions[p.number].prec if (slevel < rlevel) or ((slevel == rlevel) and (rprec == 'left')): # We really need to reduce here. st_action[a] = -p.number st_actionp[a] = p if not slevel and not rlevel: log.info(' ! shift/reduce conflict for %s resolved as reduce', a) self.sr_conflicts.append((st, a, 'reduce')) Productions[p.number].reduced += 1 elif (slevel == rlevel) and (rprec == 'nonassoc'): st_action[a] = None else: # Hmmm. Guess we'll keep the shift if not rlevel: log.info(' ! shift/reduce conflict for %s resolved as shift', a) self.sr_conflicts.append((st, a, 'shift')) elif r < 0: # Reduce/reduce conflict. In this case, we favor the rule # that was defined first in the grammar file oldp = Productions[-r] pp = Productions[p.number] if oldp.line > pp.line: st_action[a] = -p.number st_actionp[a] = p chosenp, rejectp = pp, oldp Productions[p.number].reduced += 1 Productions[oldp.number].reduced -= 1 else: chosenp, rejectp = oldp, pp self.rr_conflicts.append((st, chosenp, rejectp)) log.info(' ! reduce/reduce conflict for %s resolved using rule %d (%s)', a, st_actionp[a].number, st_actionp[a]) else: raise LALRError('Unknown conflict in state %d' % st) else: st_action[a] = -p.number st_actionp[a] = p Productions[p.number].reduced += 1 else: i = p.lr_index a = p.prod[i+1] # Get symbol right after the "." if a in self.grammar.Terminals: g = self.lr0_goto(I, a) j = self.lr0_cidhash.get(id(g), -1) if j >= 0: # We are in a shift state actlist.append((a, p, 'shift and go to state %d' % j)) r = st_action.get(a) if r is not None: # Whoa have a shift/reduce or shift/shift conflict if r > 0: if r != j: raise LALRError('Shift/shift conflict in state %d' % st) elif r < 0: # Do a precedence check. # - if precedence of reduce rule is higher, we reduce. # - if precedence of reduce is same and left assoc, we reduce. # - otherwise we shift # Shift precedence comes from the token sprec, slevel = Precedence.get(a, ('right', 0)) # Reduce precedence comes from the rule that could have been reduced rprec, rlevel = Productions[st_actionp[a].number].prec if (slevel > rlevel) or ((slevel == rlevel) and (rprec == 'right')): # We decide to shift here... highest precedence to shift Productions[st_actionp[a].number].reduced -= 1 st_action[a] = j st_actionp[a] = p if not rlevel: log.info(' ! shift/reduce conflict for %s resolved as shift', a) self.sr_conflicts.append((st, a, 'shift')) elif (slevel == rlevel) and (rprec == 'nonassoc'): st_action[a] = None else: # Hmmm. Guess we'll keep the reduce if not slevel and not rlevel: log.info(' ! shift/reduce conflict for %s resolved as reduce', a) self.sr_conflicts.append((st, a, 'reduce')) else: raise LALRError('Unknown conflict in state %d' % st) else: st_action[a] = j st_actionp[a] = p # Print the actions associated with each terminal _actprint = {} for a, p, m in actlist: if a in st_action: if p is st_actionp[a]: log.info(' %-15s %s', a, m) _actprint[(a, m)] = 1 log.info('') # Print the actions that were not used. (debugging) not_used = 0 for a, p, m in actlist: if a in st_action: if p is not st_actionp[a]: if not (a, m) in _actprint: log.debug(' ! %-15s [ %s ]', a, m) not_used = 1 _actprint[(a, m)] = 1 if not_used: log.debug('') # Construct the goto table for this state nkeys = {} for ii in I: for s in ii.usyms: if s in self.grammar.Nonterminals: nkeys[s] = None for n in nkeys: g = self.lr0_goto(I, n) j = self.lr0_cidhash.get(id(g), -1) if j >= 0: st_goto[n] = j log.info(' %-30s shift and go to state %d', n, j) action[st] = st_action actionp[st] = st_actionp goto[st] = st_goto st += 1 # ----------------------------------------------------------------------------- # === INTROSPECTION === # # The following functions and classes are used to implement the PLY # introspection features followed by the yacc() function itself. # ----------------------------------------------------------------------------- # ----------------------------------------------------------------------------- # get_caller_module_dict() # # This function returns a dictionary containing all of the symbols defined within # a caller further down the call stack. This is used to get the environment # associated with the yacc() call if none was provided. # ----------------------------------------------------------------------------- def get_caller_module_dict(levels): f = sys._getframe(levels) ldict = f.f_globals.copy() if f.f_globals != f.f_locals: ldict.update(f.f_locals) return ldict # ----------------------------------------------------------------------------- # parse_grammar() # # This takes a raw grammar rule string and parses it into production data # ----------------------------------------------------------------------------- def parse_grammar(doc, file, line): grammar = [] # Split the doc string into lines pstrings = doc.splitlines() lastp = None dline = line for ps in pstrings: dline += 1 p = ps.split() if not p: continue try: if p[0] == '|': # This is a continuation of a previous rule if not lastp: raise SyntaxError("%s:%d: Misplaced '|'" % (file, dline)) prodname = lastp syms = p[1:] else: prodname = p[0] lastp = prodname syms = p[2:] assign = p[1] if assign != ':' and assign != '::=': raise SyntaxError("%s:%d: Syntax error. Expected ':'" % (file, dline)) grammar.append((file, dline, prodname, syms)) except SyntaxError: raise except Exception: raise SyntaxError('%s:%d: Syntax error in rule %r' % (file, dline, ps.strip())) return grammar # ----------------------------------------------------------------------------- # ParserReflect() # # This class represents information extracted for building a parser including # start symbol, error function, tokens, precedence list, action functions, # etc. # ----------------------------------------------------------------------------- class ParserReflect(object): def __init__(self, pdict, log=None): self.pdict = pdict self.start = None self.error_func = None self.tokens = None self.modules = set() self.grammar = [] self.error = False if log is None: self.log = PlyLogger(sys.stderr) else: self.log = log # Get all of the basic information def get_all(self): self.get_start() self.get_error_func() self.get_tokens() self.get_precedence() self.get_pfunctions() # Validate all of the information def validate_all(self): self.validate_start() self.validate_error_func() self.validate_tokens() self.validate_precedence() self.validate_pfunctions() self.validate_modules() return self.error # Compute a signature over the grammar def signature(self): parts = [] try: if self.start: parts.append(self.start) if self.prec: parts.append(''.join([''.join(p) for p in self.prec])) if self.tokens: parts.append(' '.join(self.tokens)) for f in self.pfuncs: if f[3]: parts.append(f[3]) except (TypeError, ValueError): pass return ''.join(parts) # ----------------------------------------------------------------------------- # validate_modules() # # This method checks to see if there are duplicated p_rulename() functions # in the parser module file. Without this function, it is really easy for # users to make mistakes by cutting and pasting code fragments (and it's a real # bugger to try and figure out why the resulting parser doesn't work). Therefore, # we just do a little regular expression pattern matching of def statements # to try and detect duplicates. # ----------------------------------------------------------------------------- def validate_modules(self): # Match def p_funcname( fre = re.compile(r'\s*def\s+(p_[a-zA-Z_0-9]*)\(') for module in self.modules: try: lines, linen = inspect.getsourcelines(module) except IOError: continue counthash = {} for linen, line in enumerate(lines): linen += 1 m = fre.match(line) if m: name = m.group(1) prev = counthash.get(name) if not prev: counthash[name] = linen else: filename = inspect.getsourcefile(module) self.log.warning('%s:%d: Function %s redefined. Previously defined on line %d', filename, linen, name, prev) # Get the start symbol def get_start(self): self.start = self.pdict.get('start') # Validate the start symbol def validate_start(self): if self.start is not None: if not isinstance(self.start, str): self.log.error("'start' must be a string") # Look for error handler def get_error_func(self): self.error_func = self.pdict.get('p_error') # Validate the error function def validate_error_func(self): if self.error_func: if isinstance(self.error_func, types.FunctionType): ismethod = 0 elif isinstance(self.error_func, types.MethodType): ismethod = 1 else: self.log.error("'p_error' defined, but is not a function or method") self.error = True return eline = self.error_func.__code__.co_firstlineno efile = self.error_func.__code__.co_filename module = inspect.getmodule(self.error_func) self.modules.add(module) argcount = self.error_func.__code__.co_argcount - ismethod if argcount != 1: self.log.error('%s:%d: p_error() requires 1 argument', efile, eline) self.error = True # Get the tokens map def get_tokens(self): tokens = self.pdict.get('tokens') if not tokens: self.log.error('No token list is defined') self.error = True return if not isinstance(tokens, (list, tuple)): self.log.error('tokens must be a list or tuple') self.error = True return if not tokens: self.log.error('tokens is empty') self.error = True return self.tokens = sorted(tokens) # Validate the tokens def validate_tokens(self): # Validate the tokens. if 'error' in self.tokens: self.log.error("Illegal token name 'error'. Is a reserved word") self.error = True return terminals = set() for n in self.tokens: if n in terminals: self.log.warning('Token %r multiply defined', n) terminals.add(n) # Get the precedence map (if any) def get_precedence(self): self.prec = self.pdict.get('precedence') # Validate and parse the precedence map def validate_precedence(self): preclist = [] if self.prec: if not isinstance(self.prec, (list, tuple)): self.log.error('precedence must be a list or tuple') self.error = True return for level, p in enumerate(self.prec): if not isinstance(p, (list, tuple)): self.log.error('Bad precedence table') self.error = True return if len(p) < 2: self.log.error('Malformed precedence entry %s. Must be (assoc, term, ..., term)', p) self.error = True return assoc = p[0] if not isinstance(assoc, str): self.log.error('precedence associativity must be a string') self.error = True return for term in p[1:]: if not isinstance(term, str): self.log.error('precedence items must be strings') self.error = True return preclist.append((term, assoc, level+1)) self.preclist = preclist # Get all p_functions from the grammar def get_pfunctions(self): p_functions = [] for name, item in self.pdict.items(): if not name.startswith('p_') or name == 'p_error': continue if isinstance(item, (types.FunctionType, types.MethodType)): line = getattr(item, 'co_firstlineno', item.__code__.co_firstlineno) module = inspect.getmodule(item) p_functions.append((line, module, name, item.__doc__)) # Sort all of the actions by line number; make sure to stringify # modules to make them sortable, since `line` may not uniquely sort all # p functions p_functions.sort(key=lambda p_function: ( p_function[0], str(p_function[1]), p_function[2], p_function[3])) self.pfuncs = p_functions # Validate all of the p_functions def validate_pfunctions(self): grammar = [] # Check for non-empty symbols if len(self.pfuncs) == 0: self.log.error('no rules of the form p_rulename are defined') self.error = True return for line, module, name, doc in self.pfuncs: file = inspect.getsourcefile(module) func = self.pdict[name] if isinstance(func, types.MethodType): reqargs = 2 else: reqargs = 1 if func.__code__.co_argcount > reqargs: self.log.error('%s:%d: Rule %r has too many arguments', file, line, func.__name__) self.error = True elif func.__code__.co_argcount < reqargs: self.log.error('%s:%d: Rule %r requires an argument', file, line, func.__name__) self.error = True elif not func.__doc__: self.log.warning('%s:%d: No documentation string specified in function %r (ignored)', file, line, func.__name__) else: try: parsed_g = parse_grammar(doc, file, line) for g in parsed_g: grammar.append((name, g)) except SyntaxError as e: self.log.error(str(e)) self.error = True # Looks like a valid grammar rule # Mark the file in which defined. self.modules.add(module) # Secondary validation step that looks for p_ definitions that are not functions # or functions that look like they might be grammar rules. for n, v in self.pdict.items(): if n.startswith('p_') and isinstance(v, (types.FunctionType, types.MethodType)): continue if n.startswith('t_'): continue if n.startswith('p_') and n != 'p_error': self.log.warning('%r not defined as a function', n) if ((isinstance(v, types.FunctionType) and v.__code__.co_argcount == 1) or (isinstance(v, types.MethodType) and v.__func__.__code__.co_argcount == 2)): if v.__doc__: try: doc = v.__doc__.split(' ') if doc[1] == ':': self.log.warning('%s:%d: Possible grammar rule %r defined without p_ prefix', v.__code__.co_filename, v.__code__.co_firstlineno, n) except IndexError: pass self.grammar = grammar # ----------------------------------------------------------------------------- # yacc(module) # # Build a parser # ----------------------------------------------------------------------------- def yacc(*, debug=yaccdebug, module=None, start=None, check_recursion=True, optimize=False, debugfile=debug_file, debuglog=None, errorlog=None): # Reference to the parsing method of the last built parser global parse if errorlog is None: errorlog = PlyLogger(sys.stderr) # Get the module dictionary used for the parser if module: _items = [(k, getattr(module, k)) for k in dir(module)] pdict = dict(_items) # If no __file__ or __package__ attributes are available, try to obtain them # from the __module__ instead if '__file__' not in pdict: pdict['__file__'] = sys.modules[pdict['__module__']].__file__ if '__package__' not in pdict and '__module__' in pdict: if hasattr(sys.modules[pdict['__module__']], '__package__'): pdict['__package__'] = sys.modules[pdict['__module__']].__package__ else: pdict = get_caller_module_dict(2) # Set start symbol if it's specified directly using an argument if start is not None: pdict['start'] = start # Collect parser information from the dictionary pinfo = ParserReflect(pdict, log=errorlog) pinfo.get_all() if pinfo.error: raise YaccError('Unable to build parser') if debuglog is None: if debug: try: debuglog = PlyLogger(open(debugfile, 'w')) except IOError as e: errorlog.warning("Couldn't open %r. %s" % (debugfile, e)) debuglog = NullLogger() else: debuglog = NullLogger() debuglog.info('Created by PLY (http://www.dabeaz.com/ply)') errors = False # Validate the parser information if pinfo.validate_all(): raise YaccError('Unable to build parser') if not pinfo.error_func: errorlog.warning('no p_error() function is defined') # Create a grammar object grammar = Grammar(pinfo.tokens) # Set precedence level for terminals for term, assoc, level in pinfo.preclist: try: grammar.set_precedence(term, assoc, level) except GrammarError as e: errorlog.warning('%s', e) # Add productions to the grammar for funcname, gram in pinfo.grammar: file, line, prodname, syms = gram try: grammar.add_production(prodname, syms, funcname, file, line) except GrammarError as e: errorlog.error('%s', e) errors = True # Set the grammar start symbols try: if start is None: grammar.set_start(pinfo.start) else: grammar.set_start(start) except GrammarError as e: errorlog.error(str(e)) errors = True if errors: raise YaccError('Unable to build parser') # Verify the grammar structure undefined_symbols = grammar.undefined_symbols() for sym, prod in undefined_symbols: errorlog.error('%s:%d: Symbol %r used, but not defined as a token or a rule', prod.file, prod.line, sym) errors = True unused_terminals = grammar.unused_terminals() if unused_terminals: debuglog.info('') debuglog.info('Unused terminals:') debuglog.info('') for term in unused_terminals: errorlog.warning('Token %r defined, but not used', term) debuglog.info(' %s', term) # Print out all productions to the debug log if debug: debuglog.info('') debuglog.info('Grammar') debuglog.info('') for n, p in enumerate(grammar.Productions): debuglog.info('Rule %-5d %s', n, p) # Find unused non-terminals unused_rules = grammar.unused_rules() for prod in unused_rules: errorlog.warning('%s:%d: Rule %r defined, but not used', prod.file, prod.line, prod.name) if len(unused_terminals) == 1: errorlog.warning('There is 1 unused token') if len(unused_terminals) > 1: errorlog.warning('There are %d unused tokens', len(unused_terminals)) if len(unused_rules) == 1: errorlog.warning('There is 1 unused rule') if len(unused_rules) > 1: errorlog.warning('There are %d unused rules', len(unused_rules)) if debug: debuglog.info('') debuglog.info('Terminals, with rules where they appear') debuglog.info('') terms = list(grammar.Terminals) terms.sort() for term in terms: debuglog.info('%-20s : %s', term, ' '.join([str(s) for s in grammar.Terminals[term]])) debuglog.info('') debuglog.info('Nonterminals, with rules where they appear') debuglog.info('') nonterms = list(grammar.Nonterminals) nonterms.sort() for nonterm in nonterms: debuglog.info('%-20s : %s', nonterm, ' '.join([str(s) for s in grammar.Nonterminals[nonterm]])) debuglog.info('') if check_recursion: unreachable = grammar.find_unreachable() for u in unreachable: errorlog.warning('Symbol %r is unreachable', u) infinite = grammar.infinite_cycles() for inf in infinite: errorlog.error('Infinite recursion detected for symbol %r', inf) errors = True unused_prec = grammar.unused_precedence() for term, assoc in unused_prec: errorlog.error('Precedence rule %r defined for unknown symbol %r', assoc, term) errors = True if errors: raise YaccError('Unable to build parser') # Run the LRTable on the grammar lr = LRTable(grammar, debuglog) if debug: num_sr = len(lr.sr_conflicts) # Report shift/reduce and reduce/reduce conflicts if num_sr == 1: errorlog.warning('1 shift/reduce conflict') elif num_sr > 1: errorlog.warning('%d shift/reduce conflicts', num_sr) num_rr = len(lr.rr_conflicts) if num_rr == 1: errorlog.warning('1 reduce/reduce conflict') elif num_rr > 1: errorlog.warning('%d reduce/reduce conflicts', num_rr) # Write out conflicts to the output file if debug and (lr.sr_conflicts or lr.rr_conflicts): debuglog.warning('') debuglog.warning('Conflicts:') debuglog.warning('') for state, tok, resolution in lr.sr_conflicts: debuglog.warning('shift/reduce conflict for %s in state %d resolved as %s', tok, state, resolution) already_reported = set() for state, rule, rejected in lr.rr_conflicts: if (state, id(rule), id(rejected)) in already_reported: continue debuglog.warning('reduce/reduce conflict in state %d resolved using rule (%s)', state, rule) debuglog.warning('rejected rule (%s) in state %d', rejected, state) errorlog.warning('reduce/reduce conflict in state %d resolved using rule (%s)', state, rule) errorlog.warning('rejected rule (%s) in state %d', rejected, state) already_reported.add((state, id(rule), id(rejected))) warned_never = [] for state, rule, rejected in lr.rr_conflicts: if not rejected.reduced and (rejected not in warned_never): debuglog.warning('Rule (%s) is never reduced', rejected) errorlog.warning('Rule (%s) is never reduced', rejected) warned_never.append(rejected) # Build the parser lr.bind_callables(pinfo.pdict) parser = LRParser(lr, pinfo.error_func) parse = parser.parse return parser sip-6.8.6/sipbuild/generator/parser/python_exceptions.py000066400000000000000000000034061464421045000235620ustar00rootroot00000000000000# SPDX-License-Identifier: BSD-2-Clause # Copyright (c) 2024 Phil Thompson # The builtin Python exceptions across all supported versions of Python. PYTHON_EXCEPTIONS = ( 'BaseException', 'Exception', 'StopIteration', 'GeneratorExit', 'ArithmeticError', 'LookupError', 'StandardError', # Python v2. 'AssertionError', 'AttributeError', 'BufferError', 'EOFError', 'FloatingPointError', 'OSError', 'ImportError', 'IndexError', 'KeyError', 'KeyboardInterrupt', 'MemoryError', 'NameError', 'OverflowError', 'RuntimeError', 'NotImplementedError', 'SyntaxError', 'IndentationError', 'TabError', 'ReferenceError', 'SystemError', 'SystemExit', 'TypeError', 'UnboundLocalError', 'UnicodeError', 'UnicodeEncodeError', 'UnicodeDecodeError', 'UnicodeTranslateError', 'ValueError', 'ZeroDivisionError', 'EnvironmentError', # Python v2. 'IOError', # Python v2. 'WindowsError', # Python v2. 'VMSError', # Python v2. 'BlockingIOError', 'BrokenPipeError', 'ChildProcessError', 'ConnectionError', 'ConnectionAbortedError', 'ConnectionRefusedError', 'ConnectionResetError', 'FileExistsError', 'FileNotFoundError', 'InterruptedError', 'IsADirectoryError', 'NotADirectoryError', 'PermissionError', 'ProcessLookupError', 'TimeoutError', 'Warning', 'UserWarning', 'DeprecationWarning', 'PendingDeprecationWarning', 'SyntaxWarning', 'RuntimeWarning', 'FutureWarning', 'ImportWarning', 'UnicodeWarning', 'BytesWarning', 'ResourceWarning', ) sip-6.8.6/sipbuild/generator/parser/rules.py000066400000000000000000002300241464421045000211300ustar00rootroot00000000000000# SPDX-License-Identifier: BSD-2-Clause # Copyright (c) 2024 Phil Thompson from ..scoped_name import ScopedName from ..specification import (AccessSpecifier, Argument, ArgumentType, ArrayArgument, ClassKey, Docstring, DocstringFormat, Extract, FunctionCall, IfaceFile, IfaceFileType, KwArgs, License, MappedType, MappedTypeTemplate, Overload, Property, PyQtMethodSpecifier, QualifierType, Signature, Template, ThrowArguments, Value, ValueType, VirtualErrorHandler, WrappedTypedef, WrappedVariable) from ..templates import same_template_signature from ..utils import cached_name, normalised_scoped_name, search_typedefs from .annotations import DottedName from .tokens import tokens # This is set externally by the parser manager. parser = None # The start symbol as we prefer to keep productions in alphabetical order. start = 'specification' def p_error(t): """ Invoked when a syntax error occurs. """ if t is None: from .parser_manager import UnexpectedEOF raise UnexpectedEOF() t.lexer.pm.lexer_error(t, "'{0}' is unexpected".format(t.value)) # Skip past the next '}', ';' or '%End' and restart. t.lexer.pm.set_lexer_state() t = parser.token() while t is not None and t.type not in ('}', ';', 'End'): t = parser.token() parser.restart() # Top-level productions. ###################################################### def p_specification(p): """specification : statement | specification statement""" def p_statement(p): """statement : eof | namespace_statement | composite_module | copying | defdocstringfmt | defdocstringsig | defencoding | defmetatype | defsupertype | exported_header_code | exported_type_hint_code | extract | feature | hidden_ns | import | include | init_code | license | mapped_type | mapped_type_template | module | module_code | module_header_code | platforms | plugin | preinit_code | postinit_code | timeline | type_hint_code | unit_code | unit_postinclude_code | virtual_error_handler""" def p_namespace_statement(p): """namespace_statement : if_start | if_end | class_decl | class_template | enum_decl | exception | function | namespace_decl | struct_decl | typedef_decl | union_decl | variable | type_header_code""" def p_eof(p): "eof : EOF" p.parser.pm.pop_module_state() # State changing productions. ################################################# def p_begin_args(p): "begin_args :" p.parser.pm.set_lexer_state('directive') def p_end_args(p): "end_args :" p.parser.pm.set_lexer_state() def p_need_eol(p): "need_eol :" p.parser.pm.set_lexer_state('needeol') # %AutoPyName ################################################################# def p_autopyname(p): "autopyname : AutoPyName begin_args '(' remove_leading '=' STRING end_args ')'" if p.parser.pm.skipping: return p[0] = ('REMOVE_LEADING', p[6]) # %BIGetBufferCode ############################################################ def p_get_buffer_code(p): "get_buffer_code : BIGetBufferCode CODE_BLOCK" pm = p.parser.pm if pm.skipping: return if pm.scope.bi_get_buffer_code is not None: pm.parser_error(p, 1, "%BIGetBufferCode can only be specified once") pm.scope.bi_get_buffer_code = p[2] # %BIReleaseBufferCode ######################################################## def p_release_buffer_code(p): "release_buffer_code : BIReleaseBufferCode CODE_BLOCK" pm = p.parser.pm if pm.skipping: return if pm.scope.bi_release_buffer_code is not None: pm.parser_error(p, 1, "%BIReleaseBufferCode can only be specified once") pm.scope.bi_release_buffer_code = p[2] # %CompositeModule ############################################################ def p_composite_module(p): """composite_module : CompositeModule dotted_name c_module_body | CompositeModule begin_args '(' c_module_args end_args ')' c_module_body""" pm = p.parser.pm if pm.skipping: return # A composite module must be the first one in the specification. if not pm.in_main_module: pm.parser_error(p, 1, "a %CompositeModule cannot be %Imported") pm.spec.is_composite = True if len(p) == 4: name = p[2] body = p[3] else: name = p[4]['name'] body = p[7] module = pm.module_state.module module.fq_py_name = cached_name(pm.spec, str(name)) for directive in body: if isinstance(directive, Docstring): module.docstring = directive def p_c_module_args(p): """c_module_args : c_module_arg | c_module_args ',' c_module_arg""" if len(p) == 4: p[1].update(p[3]) p[0] = p[1] def p_c_module_arg(p): "c_module_arg : name '=' dotted_name" p[0] = {p[1]: p[3]} def p_c_module_body(p): """c_module_body : '{' c_module_body_directives '}' ';' | empty""" p[0] = p[2] if len(p) == 4 else [] def p_c_module_body_directives(p): """c_module_body_directives : c_module_body_directive | c_module_body_directives c_module_body_directive""" if len(p) == 2: value = [] directive = p[1] else: value = p[1] directive = p[2] if directive is not None: value.append(directive) p[0] = value def p_c_module_body_directive(p): """c_module_body_directive : if_start | if_end | docstring""" p[0] = p[1] # %ConvertFromTypeCode ######################################################## def p_convert_from_type_code(p): "convert_from_type_code : ConvertFromTypeCode CODE_BLOCK" pm = p.parser.pm if pm.skipping: return if pm.scope.convert_from_type_code is not None: pm.parser_error(p, 1, "%ConvertFromTypeCode can only be specified once") pm.scope.convert_from_type_code = p[2] # %ConvertToSubClassCode ###################################################### def p_convert_to_subclass_code(p): "convert_to_subclass_code : ConvertToSubClassCode CODE_BLOCK" pm = p.parser.pm if pm.skipping: return if pm.scope.convert_to_subclass_code is not None: pm.parser_error(p, 1, "%ConvertToSubClassCode can only be specified once") pm.scope.convert_to_subclass_code = p[2] # %ConvertToTypeCode ########################################################## def p_convert_to_type_code(p): "convert_to_type_code : ConvertToTypeCode CODE_BLOCK" pm = p.parser.pm if pm.skipping: return if pm.scope.convert_to_type_code is not None: pm.parser_error(p, 1, "%ConvertToTypeCode can only be specified once") pm.scope.convert_to_type_code = p[2] # %Copying #################################################################### def p_copying(p): "copying : Copying CODE_BLOCK" pm = p.parser.pm if pm.skipping: return pm.module_state.module.copying.append(p[2]) # %DefaultDocstringFormat ##################################################### def p_defdocstringfmt(p): """defdocstringfmt : DefaultDocstringFormat STRING | DefaultDocstringFormat begin_args '(' name '=' STRING end_args ')'""" pm = p.parser.pm if pm.skipping: return symbol = 2 if len(p) == 3 else 6 pm.module_state.module.default_docstring_format = pm.convert_docstring_format(p, symbol) # %DefaultDocstringSignature ################################################## def p_defdocstringsig(p): """defdocstringsig : DefaultDocstringSignature STRING | DefaultDocstringSignature begin_args '(' name '=' STRING end_args ')'""" pm = p.parser.pm if pm.skipping: return symbol = 2 if len(p) == 3 else 6 pm.module_state.module.default_docstring_signature = pm.convert_docstring_signature(p, symbol) # %DefaultEncoding ############################################################ def p_defencoding(p): """defencoding : DefaultEncoding STRING | DefaultEncoding begin_args '(' name '=' STRING end_args ')'""" pm = p.parser.pm if pm.skipping: return symbol = 2 if len(p) == 3 else 6 pm.module_state.default_encoding = pm.convert_encoding(p, symbol) # %DefaultMetatype ############################################################ def p_defmetatype(p): """defmetatype : DefaultMetatype dotted_name | DefaultMetatype begin_args '(' name '=' dotted_name end_args ')'""" pm = p.parser.pm if pm.skipping: return module = pm.module_state.module if module.default_metatype: pm.parser_error(p, 1, "%DefaultMetatype has already been defined for this module") symbol = 2 if len(p) == 3 else 6 module.default_metatype = cached_name(pm.spec, str(p[symbol])) # %DefaultSupertype ########################################################### def p_defsupertype(p): """defsupertype : DefaultSupertype dotted_name | DefaultSupertype begin_args '(' name '=' dotted_name end_args ')'""" pm = p.parser.pm if pm.skipping: return module = pm.module_state.module if module.default_supertype: pm.parser_error(p, 1, "%DefaultSupertype has already been defined for this module") symbol = 2 if len(p) == 3 else 6 module.default_supertype = cached_name(pm.spec, str(p[symbol])) # %Docstring ################################################################## def p_docstring(p): "docstring : Docstring docstring_args CODE_BLOCK" pm = p.parser.pm if pm.skipping: return module = pm.module_state.module ds_format = p[2].get('format', module.default_docstring_format) if ds_format == DocstringFormat.DEINDENTED: from textwrap import dedent text = dedent(p[3].text) else: text = p[3].text p[0] = Docstring(p[2].get('signature', module.default_docstring_signature), text) def p_docstring_args(p): """docstring_args : empty | STRING | begin_args '(' docstring_arg_list end_args ')'""" if len(p) == 2: pm = p.parser.pm if p[1] is None: value = {} else: value = {'format': pm.convert_docstring_format(p, 1)} else: value = p[3] p[0] = value def p_docstring_arg_list(p): """docstring_arg_list : docstring_arg | docstring_arg_list ',' docstring_arg""" if len(p) == 4: p[1].update(p[3]) p[0] = p[1] def p_docstring_arg(p): """docstring_arg : format '=' STRING | signature '=' STRING""" pm = p.parser.pm if p[1] == 'format': value = pm.convert_docstring_format(p, 3) else: value = pm.convert_docstring_signature(p, 3) p[0] = {p[1]: value} # %ExportedHeaderCode ######################################################### def p_exported_header_code(p): "exported_header_code : ExportedHeaderCode CODE_BLOCK" pm = p.parser.pm if pm.skipping: return pm.spec.exported_header_code.append(p[2]) # %ExportedTypeHintCode ####################################################### def p_exported_type_hint_code(p): "exported_type_hint_code : ExportedTypeHintCode CODE_BLOCK" pm = p.parser.pm if pm.skipping: return if not pm.in_main_module: pm.spec.exported_type_hint_code.append(p[2]) # %Extract #################################################################### def p_extract(p): """extract : Extract NAME CODE_BLOCK | Extract begin_args '(' extract_args end_args ')' CODE_BLOCK""" pm = p.parser.pm if pm.skipping: return if len(p) == 4: id = p[2] order = -1 part = p[3] else: args = p[4] try: id = args['id'] except KeyError: pm.parser_error(p, 1, "%Extract must specify 'id'") id = '' order = args.get('order', -1) part = p[7] pm.spec.extracts.append(Extract(id, order, part.text)) def p_extract_args(p): """extract_args : extract_arg | extract_args ',' extract_arg""" if len(p) == 4: p[1].update(p[3]) p[0] = p[1] def p_extract_arg(p): """extract_arg : id '=' NAME | order '=' NUMBER""" p[0] = {p[1]: p[3]} # %Feature #################################################################### def p_feature(p): """feature : Feature NAME | Feature begin_args '(' name '=' NAME end_args ')'""" symbol = 2 if len(p) == 3 else 6 # Note that qualifiers are never skipped. p.parser.pm.add_qualifier(p, symbol, p[symbol], QualifierType.FEATURE) # %FinalisationCode ########################################################### def p_finalisation_code(p): "finalisation_code : FinalisationCode CODE_BLOCK" pm = p.parser.pm if pm.skipping: return if pm.scope.finalisation_code is not None: pm.parser_error(p, 1, "%FinalisationCode can only be specified once") pm.scope.finalisation_code = p[2] # %GCClearCode ################################################################ def p_gc_clear_code(p): "gc_clear_code : GCClearCode CODE_BLOCK" pm = p.parser.pm if pm.skipping: return if pm.scope.gc_clear_code is not None: pm.parser_error(p, 1, "%GCClearCode can only be specified once") pm.scope.gc_clear_code = p[2] # %GCTraverseCode ############################################################# def p_gc_traverse_code(p): "gc_traverse_code : GCTraverseCode CODE_BLOCK" pm = p.parser.pm if pm.skipping: return if pm.scope.gc_traverse_code is not None: pm.parser_error(p, 1, "%GCTraverseCode can only be specified once") pm.scope.gc_traverse_code = p[2] # %HiddenNamespace ############################################################ def p_hidden_ns(p): """hidden_ns : HideNamespace scoped_name | HideNamespace begin_args '(' hidden_ns_args end_args ')'""" pm = p.parser.pm if pm.skipping: return if len(p) == 3: name = p[2] else: try: name = p[4]['name'] except KeyError: pm.parser_error(p, 1, "the name of the namespace has not been specified") name = '' namespace = pm.new_class(p, 1, IfaceFileType.NAMESPACE, normalised_scoped_name(name, pm.scope)) namespace.is_hidden_namespace = True def p_hidden_ns_args(p): """hidden_ns_args : hidden_ns_arg | hidden_ns_args ',' hidden_ns_arg""" if len(p) == 4: p[1].update(p[3]) p[0] = p[1] def p_hidden_ns_arg(p): "hidden_ns_arg : name '=' scoped_name" p[0] = {p[1]: p[3]} # %If/%End #################################################################### def p_if_start(p): "if_start : If '(' qualifiers ')'" pm = p.parser.pm if pm.skipping: skipping = True else: skipping = not p[3] pm.skip_stack.append(skipping) def p_if_end(p): "if_end : End" pm = p.parser.pm if len(pm.skip_stack) >= 2: pm.skip_stack.pop() else: pm.parser_error(p, 1, "too many '%End' directives") # %Import ##################################################################### def p_import(p): """import : Import need_eol import_simple EOL | Import begin_args '(' import_compound end_args ')'""" def p_import_simple(p): "import_simple : file_path" pm = p.parser.pm if pm.skipping: return pm.push_file(p, 1, new_module=True) def p_import_compound(p): "import_compound : import_args" pm = p.parser.pm if pm.skipping: return try: sip_file = p[1]['name'] except KeyError: pm.parser_error(p, 1, "the name of the file has not been specified") pm.push_file(p, 1, sip_file, new_module=True) def p_import_args(p): """import_args : import_arg | import_args ',' import_arg""" if len(p) == 4: p[1].update(p[3]) p[0] = p[1] def p_import_arg(p): "import_arg : name '=' file_path" p[0] = {p[1]: p[3]} # %Include #################################################################### def p_include(p): """include : Include need_eol include_simple EOL | Include begin_args '(' include_compound end_args ')'""" def p_include_simple(p): "include_simple : file_path" pm = p.parser.pm if pm.skipping: return pm.push_file(p, 1) def p_include_compund(p): "include_compound : include_args" pm = p.parser.pm if pm.skipping: return try: sip_file = p[1]['name'] except KeyError: pm.parser_error(p, 1, "the name of the file has not been specified") pm.push_file(p, 1, sip_file, optional=p[1].get('optional', False)) def p_include_args(p): """include_args : include_arg | include_args ',' include_arg""" if len(p) == 4: p[1].update(p[3]) p[0] = p[1] def p_include_arg(p): """include_arg : name '=' file_path | optional '=' bool_value""" p[0] = {p[1]: p[3]} # %InitialisationCode ######################################################### def p_init_code(p): "init_code : InitialisationCode CODE_BLOCK" pm = p.parser.pm if pm.skipping: return pm.module_state.module.initialisation_code.append(p[2]) # %InstanceCode ############################################################### def p_instance_code(p): "instance_code : InstanceCode CODE_BLOCK" pm = p.parser.pm if pm.skipping: return if pm.scope.instance_code is not None: pm.parser_error(p, 1, "%InstanceCode can only be specified once") pm.scope.instance_code = p[2] # %License #################################################################### def p_license(p): """license : License STRING | License begin_args '(' license_args end_args ')'""" pm = p.parser.pm if pm.skipping: return if pm.module_state.module.license is not None: pm.parser_error(p, 1, "%License can only be specified once") if len(p) == 3: license = License(p[2]) else: args = p[4] try: license_type = args['type'] except KeyError: pm.parser_error(p, 1, "%License must specify 'type'") license_type = '' license = License(license_type) license.licensee = args.get('licensee') license.signature = args.get('signature') license.timestamp = args.get('timestamp') pm.module_state.module.license = license def p_license_args(p): """license_args : license_arg | license_args ',' license_arg""" if len(p) == 4: p[1].update(p[3]) p[0] = p[1] def p_license_arg(p): """license_arg : licensee '=' STRING | signature '=' STRING | timestamp '=' STRING | type '=' STRING""" p[0] = {p[1]: p[3]} # %MappedType ################################################################# # The mapped type annotations. _MAPPED_TYPE_ANNOTATIONS = ( 'AllowNone', 'NoAssignmentOperator', 'NoCopyCtor', 'NoDefaultCtor', 'NoRelease', 'PyName', 'PyQtFlags', 'TypeHint', 'TypeHintIn', 'TypeHintOut', 'TypeHintValue', ) def p_mapped_type(p): "mapped_type : mapped_type_head '{' mapped_type_body '}' ';'" pm = p.parser.pm if pm.skipping: return pm.validate_mapped_type(p, 1, pm.scope) pm.pop_scope() def p_mapped_type_template(p): "mapped_type_template : mapped_type_template_head '{' mapped_type_body '}' ';'" pm = p.parser.pm if pm.skipping: return pm.validate_mapped_type(p, 1, pm.scope) pm.parsing_template = False pm.pop_scope() def p_mapped_type_head(p): "mapped_type_head : MappedType base_type opt_annos" pm = p.parser.pm if pm.skipping: return pm.check_annotations(p, 3, "mapped type", _MAPPED_TYPE_ANNOTATIONS) pm.add_mapped_type(p, 1, p[2], p[3]) def p_mapped_type_template_head(p): "mapped_type_template_head : template_decl MappedType base_type opt_annos" # Note that we only use the template arguments to confirm that any simple # (ie. unscoped) names in the base type are to be substituted when the # template is instantiated. Anything else is quietly ignored. pm = p.parser.pm if pm.skipping: return pm.cpp_only(p, 1, "%MappedType templates") pm.check_annotations(p, 4, "mapped type", _MAPPED_TYPE_ANNOTATIONS) if p[3].type is not ArgumentType.TEMPLATE: pm.parser_error(p, 3, "%MappedType template must map a template type") # Check a template hasn't already been provided. for mtt in pm.spec.mapped_type_templates: if mtt.mapped_type.type.definition.cpp_name == p[3].definition.cpp_name and same_template_signature(mtt.mapped_type.type.definition.types, p[3].definition.types, deep=True): pm.parser_error(p, 1, "a %MappedType template for this type has already been defined") # Use a dummy interface file. mapped_type = MappedType(IfaceFile(IfaceFileType.MAPPED_TYPE), p[3]) pm.annotate_mapped_type(p, 4, mapped_type, p[4]) mtt = MappedTypeTemplate(mapped_type, p[1]) pm.spec.mapped_type_templates.insert(0, mtt) pm.push_scope(mapped_type) def p_mapped_type_body(p): """mapped_type_body : mapped_type_line | mapped_type_body mapped_type_line""" def p_mapped_type_line(p): """mapped_type_line : if_start | if_end | convert_from_type_code | convert_to_type_code | enum_decl | instance_code | mapped_type_function | release_code | type_code | type_header_code""" def p_mapped_type_function(p): "mapped_type_function : static cpp_type NAME '(' opt_arg_list ')' opt_const opt_exceptions opt_annos opt_signature ';' opt_docstring premethod_code method_code" pm = p.parser.pm if pm.skipping: return pm.check_annotations(p, 9, "function", _FUNCTION_ANNOTATIONS) pm.apply_type_annotations(p, 9, p[2], p[9]) overload = pm.add_function(p, 1, p[3], p[2], p[5], p[9], const=p[7], exceptions=p[8], cpp_signature=p[10], docstring=p[12], premethod_code=p[13], method_code=p[14]) overload.is_static = True pm.validate_function(p, 1, overload) # %ModuleHeaderCode ########################################################### def p_module_header_code(p): "module_header_code : ModuleHeaderCode CODE_BLOCK" pm = p.parser.pm if pm.skipping: return pm.module_state.module.module_header_code.append(p[2]) # %Module ##################################################################### def p_module(p): """module : Module dotted_name module_body | Module begin_args '(' module_args end_args ')' module_body""" pm = p.parser.pm if pm.skipping: return # See if this %Module is part of a %CompositeModule. if pm.spec.is_composite: # Historically we %Include modules although conceptually we actually # %Import them. Ensure that the scopes etc. are correct in either # case. pm.ensure_import() module_state = pm.module_state module = module_state.module if module.fq_py_name is not None: pm.parser_error(p, 1, "%Module has already been specified") return if len(p) == 4: args = {'name': p[2]} body = p[3] else: args = p[4] body = p[7] try: module.fq_py_name = cached_name(pm.spec, str(args['name'])) if pm.in_main_module: module.fq_py_name.used = True except KeyError: pm.parser_error(p, 1, "the name of the module has not been specified") # Configure the current module. module_state.all_raise_py_exception = args.get('all_raise_py_exception', False) module.default_virtual_error_handler = args.get( 'default_VirtualErrorHandler') module_state.call_super_init = args.get('call_super_init') module_state.kw_args = args.get('keyword_arguments', KwArgs.NONE) c_bindings = args.get('language') if c_bindings is not None: if pm.c_bindings is None: pm.c_bindings = c_bindings elif pm.c_bindings != c_bindings: pm.parser_error(p, 1, "cannot mix 'C' and 'C++' modules") # Deprecate and remove when Python v3.12 is no longer supported. if 'py_ssize_t_clean' in args: module.py_ssize_t_clean = args['py_ssize_t_clean'] if 'use_argument_names' in args: module.use_arg_names = args['use_argument_names'] if 'use_limited_api' in args: module.use_limited_api = args['use_limited_api'] for directive in body: if isinstance(directive, tuple): module_state.auto_py_name_rules.append(directive) elif isinstance(directive, Docstring): module.docstring = directive def p_module_args(p): """module_args : module_arg | module_args ',' module_arg""" if len(p) == 4: p[1].update(p[3]) p[0] = p[1] def p_module_arg(p): """module_arg : all_raise_py_exception '=' bool_value | call_super_init '=' bool_value | default_VirtualErrorHandler '=' NAME | keyword_arguments '=' STRING | language '=' STRING | name '=' dotted_name | py_ssize_t_clean '=' bool_value | use_argument_names '=' bool_value | use_limited_api '=' bool_value""" pm = p.parser.pm if p[1] == 'keyword_arguments': value = pm.convert_kw_args(p, 3) elif p[1] == 'language': if p[3] == 'C': value = True elif p[3] == 'C++': value = False else: pm.parser_error(p, 3, "unsupported language '{0}'".format(p[3])) value = None else: value = p[3] p[0] = {p[1]: value} def p_module_body(p): """module_body : '{' module_body_directives '}' ';' | empty""" p[0] = p[2] if len(p) == 5 else [] def p_module_body_directives(p): """module_body_directives : module_body_directive | module_body_directives module_body_directive""" if len(p) == 2: body = [] value = p[1] else: body = p[1] value = p[2] if value is not None: body.append(value) p[0] = body def p_module_body_directive(p): """module_body_directive : if_start | if_end | autopyname | docstring""" p[0] = p[1] # %ModuleCode ################################################################# def p_module_code(p): "module_code : ModuleCode CODE_BLOCK" pm = p.parser.pm if pm.skipping: return pm.module_state.module.module_code.append(p[2]) # %PickleCode ################################################################# def p_pickle_code(p): "pickle_code : PickleCode CODE_BLOCK" pm = p.parser.pm if pm.skipping: return if pm.scope.pickle_code is not None: pm.parser_error(p, 1, "%PickleCode can only be specified once") pm.scope.pickle_code = p[2] # %Platforms ################################################################## def p_platforms(p): "platforms : Platforms '{' qualifier_list '}'" pm = p.parser.pm # Remember which platforms were selected. Note that qualifiers are never # skipped. selected = [] for qual_name in p[3]: if qual_name in pm.tags: selected.append(qual_name) pm.add_qualifier(p, 1, qual_name, QualifierType.PLATFORM) # Check that no more than one platform was selected. if len(selected) > 1: pm.parser_error(p, 1, "only one of {0} can be selected from %Platforms".format( ','.join(["'" + s + "'" for s in selected]))) # %Plugin ##################################################################### def p_plugin(p): "plugin : Plugin NAME" pm = p.parser.pm if pm.skipping: return pm.deprecated(p, 1) pm.spec.plugins.append(p[2]) # %PostInitialisationCode ##################################################### def p_postinit_code(p): "postinit_code : PostInitialisationCode CODE_BLOCK" pm = p.parser.pm if pm.skipping: return pm.module_state.module.postinitialisation_code.append(p[2]) # %PreInitialisationCode ###################################################### def p_preinit_code(p): "preinit_code : PreInitialisationCode CODE_BLOCK" pm = p.parser.pm if pm.skipping: return pm.module_state.module.preinitialisation_code.append(p[2]) # %Property ################################################################### def p_property(p): "property : Property begin_args '(' property_args end_args ')' opt_property_body" pm = p.parser.pm if pm.skipping: return name = p[4].get('name') if name is None: p.parser.pm.parser_error(p, 1, "a name must be specified for %Property") return pm.check_attributes(p, 4, name) name = cached_name(pm.spec, name) if pm.in_main_module: name.used = True getter = p[4].get('get') if getter is None: p.parser.pm.parser_error(p, 1, "a getter must be specified for %Property") return prop = Property(name=name, getter=getter, setter=p[4].get('set')) for directive in p[7]: if isinstance(directive, Docstring): prop.docstring = directive pm.scope.properties.insert(0, prop) def p_property_args(p): """property_args : property_arg | property_args ',' property_arg""" if len(p) == 4: p[1].update(p[3]) p[0] = p[1] def p_property_arg(p): """property_arg : get '=' NAME | name '=' NAME | set '=' NAME""" p[0] = {p[1]: p[3]} def p_opt_property_body(p): """opt_property_body : empty | '{' property_body '}' ';'""" p[0] = [] if len(p) == 2 else p[2] def p_property_body(p): """property_body : property_line | property_body property_line""" if len(p) == 2: value = [] directive = p[1] else: value = p[1] directive = p[2] if directive is not None: value.append(directive) p[0] = value def p_property_line(p): """property_line : if_start | if_end | docstring""" p[0] = p[1] # %ReleaseCode ################################################################ def p_release_code(p): "release_code : ReleaseCode CODE_BLOCK" pm = p.parser.pm if pm.skipping: return if pm.scope.release_code is not None: pm.parser_error(p, 1, "%ReleaseCode can only be specified once") pm.scope.release_code = p[2] # %Timeline ################################################################### def p_timeline(p): "timeline : Timeline '{' qualifier_list '}'" pm = p.parser.pm # Allocate the timeline number. module_state = pm.module_state timeline = module_state.nr_timelines module_state.nr_timelines += 1 # Remember which tags were selected. Note that qualifiers are never # skipped. selected = [] for order, qual_name in enumerate(p[3]): if qual_name in pm.tags: selected.append(qual_name) pm.add_qualifier(p, 1, qual_name, QualifierType.TIME, order=order, timeline=timeline) # Check that no more than one qualifier was selected. if len(selected) > 1: pm.parser_error(p, 1, "only one of {0} can be selected from this %Timeline".format( ','.join(["'" + s + "'" for s in selected]))) # %TypeCode ################################################################### def p_type_code(p): "type_code : TypeCode CODE_BLOCK" pm = p.parser.pm if pm.skipping: return pm.scope.type_code.append(p[2]) # %TypeHeaderCode ############################################################# def p_type_header_code(p): "type_header_code : TypeHeaderCode CODE_BLOCK" pm = p.parser.pm if pm.skipping: return pm.scope.iface_file.type_header_code.append(p[2]) # %TypeHintCode ############################################################### def p_type_hint_code(p): "type_hint_code : TypeHintCode CODE_BLOCK" pm = p.parser.pm if pm.skipping: return # This can be specified multiple times for a module but only once for a # class. scope = pm.scope if scope is None: pm.module_state.module.type_hint_code.append(p[2]) else: if scope.type_hint_code is not None: pm.parser_error(p, 1, "%TypeHintCode can only be specified once") scope.type_hint_code = p[2] # %UnitCode ################################################################### def p_unit_code(p): "unit_code : UnitCode CODE_BLOCK" pm = p.parser.pm if pm.skipping: return pm.module_state.module.unit_code.append(p[2]) # %UnitPostIncludeCode ######################################################## def p_unit_postinclude_code(p): "unit_postinclude_code : UnitPostIncludeCode CODE_BLOCK" pm = p.parser.pm if pm.skipping: return pm.module_state.module.unit_postinclude_code.append(p[2]) # %VirtualErrorHandler ######################################################## def p_virtual_error_handler(p): """virtual_error_handler : VirtualErrorHandler NAME CODE_BLOCK | VirtualErrorHandler begin_args '(' veh_args end_args ')' CODE_BLOCK""" pm = p.parser.pm if pm.skipping: return if len(p) == 4: name = p[2] code = p[3] else: name = p[4].get('name') if name is None: pm.parser_error(p, 1, "a name must be specified for %VirtualErrorHandler") return code = p[7] for handler in pm.spec.virtual_error_handlers: if handler.name == name: pm.parser_error(p, 1, "a virtual error handler called '{0}' has already been defined".format(name)) break else: pm.spec.virtual_error_handlers.append( VirtualErrorHandler(code, pm.module_state.module, name)) def p_veh_args(p): """veh_args : veh_arg | veh_args ',' veh_arg""" if len(p) == 4: p[1].update(p[3]) p[0] = p[1] def p_veh_arg(p): "veh_arg : name '=' NAME" p[0] = {p[1]: p[3]} # A C/C++ type. ############################################################### def p_cpp_type(p): """cpp_type : const base_type derefs opt_ref | base_type derefs opt_ref""" if len(p) == 5: value = p[2] value.is_const = True value.derefs.extend(p[3]) value.is_reference = p[4] else: value = p[1] value.derefs.extend(p[2]) value.is_reference = p[3] # PyObject * is a synonym for SIP_PYOBJECT. if value.type is ArgumentType.DEFINED and len(value.definition) == 1 and value.definition.base_name == 'PyObject' and len(value.derefs) == 1 and not value.is_reference: value.type = ArgumentType.PYOBJECT value.definition = None value.derefs = [] p[0] = value def p_base_type(p): """base_type : pod_type | scoped_name | scoped_name '<' cpp_types '>' | struct scoped_name | union scoped_name""" pm = p.parser.pm if isinstance(p[1], ArgumentType): p[0] = Argument(p[1]) elif len(p) == 2: # Resolve it if it is the name of a typedef. This is done early as a # workaround for allowing /PyInt/ to be applied to typedef'ed types. ad = Argument(ArgumentType.DEFINED, definition=p[1]) while ad.type is ArgumentType.DEFINED: ad.type = ArgumentType.NONE search_typedefs(pm.spec, ad.definition, ad) # Don't resolve to a template type as it may be superceded later on # by a more specific mapped type. if ad.type in (ArgumentType.NONE, ArgumentType.TEMPLATE): ad = Argument(ArgumentType.DEFINED, definition=p[1]) break p[0] = ad elif len(p) == 3: # In a C module all structs and unions must be defined. if bool(p.parser.pm.c_bindings): type = ArgumentType.DEFINED elif p[1] == 'struct': type = ArgumentType.STRUCT else: type = ArgumentType.UNION p[0] = Argument(type, definition=p[2]) else: p[0] = Argument(ArgumentType.TEMPLATE, definition=Template(p[1], Signature(args=p[3]))) p[0].source_location = pm.get_source_location(p, 1) # Map unsigned two-word POD types. _UNSIGNED_MAP = { 'char': ArgumentType.USTRING, 'int': ArgumentType.UINT, 'long': ArgumentType.ULONG, 'short': ArgumentType.USHORT, } # Map one-word POD types. _ONE_WORD_MAP = { '...': ArgumentType.ELLIPSIS, 'void': ArgumentType.VOID, 'bool': ArgumentType.BOOL, 'char': ArgumentType.STRING, 'double': ArgumentType.DOUBLE, 'float': ArgumentType.FLOAT, 'short': ArgumentType.SHORT, 'int': ArgumentType.INT, 'unsigned': ArgumentType.UINT, 'long': ArgumentType.LONG, 'size_t': ArgumentType.SIZE, 'wchar_t': ArgumentType.WSTRING, 'Py_hash_t': ArgumentType.HASH, 'Py_ssize_t': ArgumentType.SSIZE, 'SIP_PYBUFFER': ArgumentType.PYBUFFER, 'SIP_PYCALLABLE': ArgumentType.PYCALLABLE, 'SIP_PYDICT': ArgumentType.PYDICT, 'SIP_PYENUM': ArgumentType.PYENUM, 'SIP_PYLIST': ArgumentType.PYLIST, 'SIP_PYOBJECT': ArgumentType.PYOBJECT, 'SIP_PYSLICE': ArgumentType.PYSLICE, 'SIP_PYTUPLE': ArgumentType.PYTUPLE, 'SIP_PYTYPE': ArgumentType.PYTYPE, 'SIP_SSIZE_T': ArgumentType.SSIZE, } def p_pod_type(p): """pod_type : unsigned long long | signed char | long long | unsigned char | unsigned short | unsigned int | unsigned long | unsigned | short | int | long | float | double | bool | char | wchar_t | void | SIP_PYOBJECT | SIP_PYTUPLE | SIP_PYLIST | SIP_PYDICT | SIP_PYCALLABLE | SIP_PYSLICE | SIP_PYTYPE | SIP_PYBUFFER | SIP_PYENUM | SIP_SSIZE_T | Py_hash_t | Py_ssize_t | size_t | ELLIPSIS""" if len(p) == 4: p[0] = ArgumentType.ULONGLONG elif len(p) == 3: if p[1] == 'signed': p[0] = ArgumentType.SSTRING elif p[1] == 'long': p[0] = ArgumentType.LONGLONG else: p[0] = _UNSIGNED_MAP[p[2]] else: if p[1] == 'SIP_SSIZE_T': p.parser.pm.deprecated(p, 1, instead="'Py_ssize_t'") p[0] = _ONE_WORD_MAP[p[1]] def p_cpp_types(p): """cpp_types : cpp_type | cpp_types ',' cpp_type""" if len(p) == 2: p[0] = [p[1]] else: if p[1][-1].type == ArgumentType.ELLIPSIS: p.parser.pm.parser_error(p, 3, "an ellipsis must be at the end of the argument list") p[0] = p[1] p[0].append(p[3]) def p_derefs(p): """derefs : empty | derefs '*' | derefs '*' const""" if len(p) == 2: p[0] = [] else: p[0] = p[1] p[0].append(len(p) == 4) def p_opt_ref(p): """opt_ref : '&' | empty""" if p[1] is None: p[0] = False else: p.parser.pm.cpp_only(p, 1, "references") p[0] = True # C++ classes and structs. #################################################### # The class annotations. _CLASS_ANNOTATIONS = ( 'Abstract', 'AllowNone', 'DelayDtor', 'Deprecated', 'ExportDerived', 'External', 'FileExtension', 'Metatype', 'Mixin', 'NoDefaultCtors', 'NoTypeHint', 'PyName', 'PyQtFlags', 'PyQtFlagsEnums', 'PyQtInterface', 'PyQtNoQMetaObject', 'Supertype', 'TypeHint', 'TypeHintIn', 'TypeHintOut', 'TypeHintValue', 'VirtualErrorHandler', ) def p_class_template(p): "class_template : template_decl class_decl" pm = p.parser.pm if pm.skipping: return pm.cpp_only(p, 1, "class templates") pm.class_templates.append((p[1], p[2])) pm.parsing_template = False def p_class_docstring(p): "class_docstring : docstring" pm = p.parser.pm if pm.skipping: return if pm.scope.docstring is None: pm.scope.docstring = p[1] else: pm.parser_error(p, 1, "%Docstring has already been defined for this class") def p_class_decl(p): "class_decl : class class_head opt_class_definition ';'" pm = p.parser.pm if pm.skipping: return # Return the class for any class template. p[0] = pm.complete_class(p, 2, p[2], p[3]) def p_class_head(p): "class_head : scoped_name superclasses opt_annos" pm = p.parser.pm if pm.skipping: return pm.check_annotations(p, 3, "class", _CLASS_ANNOTATIONS) if p[2] is not None: pm.cpp_only(p, 2, "super-classes") pm.define_class(p, 1, ClassKey.CLASS, p[1], p[3], superclasses=p[2]) # Return the annotations. p[0] = p[3] def p_struct_decl(p): "struct_decl : struct struct_head opt_class_definition ';'" pm = p.parser.pm if pm.skipping: return pm.complete_class(p, 2, p[2], p[3]) def p_struct_head(p): "struct_head : scoped_name superclasses opt_annos" pm = p.parser.pm if pm.skipping: return pm.check_annotations(p, 3, "class", _CLASS_ANNOTATIONS) pm.define_class(p, 1, ClassKey.STRUCT, p[1], p[3], superclasses=p[2]) # Return the annotations. p[0] = p[3] def p_superclasses(p): """superclasses : ':' superclass_list | empty""" p[0] = [] if len(p) == 2 else p[2] def p_superclass_list(p): """superclass_list : superclass | superclass_list ',' superclass""" if len(p) == 2: l = [] value = p[1] else: l = p[1] value = p[3] if value is not None: l.append(value) p[0] = l def p_superclass(p): "superclass : class_access scoped_name" pm = p.parser.pm if pm.skipping: return if p[1] != 'public': p[0] = None return # This is a hack to allow typedef'ed classes to be used before we have # resolved the typedef definitions. Unlike elsewhere, we require that the # typedef is defined before being used. ad = Argument(ArgumentType.DEFINED, definition=p[2]) while ad.type is ArgumentType.DEFINED: ad.type = ArgumentType.NONE search_typedefs(pm.spec, ad.definition, ad) if ad.type is not ArgumentType.NONE or len(ad.derefs) != 0 or ad.is_const or ad.is_reference: pm.parser_error(p, 2, "super-class list contains an invalid type") # Find the actual class. p[0] = pm.find_class(p, 2, IfaceFileType.CLASS, ad.definition, tmpl_arg=pm.parsing_template) def p_class_access(p): """class_access : empty | public | protected | private""" if p[1] is None: p[1] = 'public' p[0] = p[1] def p_opt_class_definition(p): """opt_class_definition : '{' opt_class_body '}' | empty""" pm = p.parser.pm if pm.skipping: return p[0] = (len(p) == 4) def p_opt_class_body(p): """opt_class_body : class_body | empty""" def p_class_body(p): """class_body : class_line | class_body class_line""" def p_class_line(p): """class_line : if_start | if_end | class_decl | class_docstring | class_template | ctor | dtor | enum_decl | exception | typedef_decl | method_variable | namespace_decl | struct_decl | union_decl | public_specifier | protected_specifier | private_specifier | signals_specifier | convert_from_type_code | convert_to_subclass_code | convert_to_type_code | finalisation_code | gc_clear_code | gc_traverse_code | get_buffer_code | instance_code | pickle_code | property | release_buffer_code | type_code | type_header_code | type_hint_code | deprecated_code_directives CODE_BLOCK""" def p_deprecated_code_directives(p): """deprecated_code_directives : BIGetReadBufferCode | BIGetWriteBufferCode | BIGetSegCountCode | BIGetCharBufferCode""" pm = p.parser.pm if pm.skipping: return pm.deprecated(p, 1) # The ctor annotations. _CTOR_ANNOTATIONS = ( 'Default', 'Deprecated', 'HoldGIL', 'KeywordArgs', 'NoDerived', 'NoRaisesPyException', 'NoTypeHint', 'PostHook', 'PreHook', 'RaisesPyException', 'ReleaseGIL', 'Transfer', ) def p_ctor(p): """ctor : explicit ctor_decl | ctor_decl""" pm = p.parser.pm if pm.skipping: return if len(p) == 3: pm.cpp_only(p, 1, "explicit constructors") def p_ctor_decl(p): "ctor_decl : NAME '(' opt_arg_list ')' opt_exceptions opt_annos opt_ctor_signature ';' opt_docstring premethod_code method_code" pm = p.parser.pm if pm.skipping: return pm.check_annotations(p, 6, "constructor", _CTOR_ANNOTATIONS) pm.add_ctor(p, 1, p[3], p[6], exceptions=p[5], cpp_signature=p[7], docstring=p[9], premethod_code=p[10], method_code=p[11]) def p_opt_ctor_signature(p): """opt_ctor_signature : '[' '(' opt_arg_list ')' ']' | empty""" if len(p) == 6: p[0] = Signature(args=p[3]) else: p[0] = None # The dtor annotations. _DTOR_ANNOTATIONS = ( 'HoldGIL', 'ReleaseGIL', ) def p_dtor(p): "dtor : opt_virtual '~' NAME '(' ')' opt_exceptions opt_abstract opt_annos ';' premethod_code method_code virtual_catcher_code" pm = p.parser.pm if pm.skipping: return pm.check_annotations(p, 8, "destructor", _DTOR_ANNOTATIONS) pm.add_dtor(p, 1, p[3], p[8], exceptions=p[6], abstract=p[7], premethod_code=p[10], method_code=p[11], virtual_catcher_code=p[12]) pm.parsing_virtual = False def p_method_variable(p): """method_variable : Q_SIGNAL simple_method_variable | Q_SLOT simple_method_variable | simple_method_variable""" pm = p.parser.pm if len(p) == 3: item = p[2] if isinstance(item, Overload): item.pyqt_method_specifier = PyQtMethodSpecifier.SIGNAL if p[1] == 'Q_SIGNAL' else PyQtMethodSpecifier.SLOT else: pm.parser_error(p, 1, "a PyQt method specifier can only be applied to member functions") else: item = p[1] if isinstance(item, Overload): pm.validate_function(p, 1, item) def p_simple_method_variable(p): """simple_method_variable : virtual function | static plain_method_variable | plain_method_variable""" if len(p) == 3: item = p[2] if item is not None: if p[1] == 'static': item.is_static = True elif not item.is_final: item.is_virtual = True p.parser.pm.scope.needs_shadow = True else: item = p[1] p[0] = item def p_plain_method_variable(p): """plain_method_variable : function | variable""" p[0] = p[1] def p_public_specifier(p): "public_specifier : public opt_slots ':'" pm = p.parser.pm if pm.skipping: return pm.scope_access_specifier = AccessSpecifier.PUBLIC pm.scope_pyqt_method_specifier = p[2] def p_protected_specifier(p): "protected_specifier : protected opt_slots ':'" pm = p.parser.pm if pm.skipping: return pm.scope_access_specifier = AccessSpecifier.PROTECTED pm.scope_pyqt_method_specifier = p[2] def p_private_specifier(p): "private_specifier : private opt_slots ':'" pm = p.parser.pm if pm.skipping: return pm.scope_access_specifier = AccessSpecifier.PRIVATE pm.scope_pyqt_method_specifier = p[2] def p_signals_specifier(p): """signals_specifier : signals ':' | Q_SIGNALS ':'""" pm = p.parser.pm if pm.skipping: return pm.scope_access_specifier = AccessSpecifier.PUBLIC pm.scope_pyqt_method_specifier = PyQtMethodSpecifier.SIGNAL def p_opt_slots(p): """opt_slots : slots | Q_SLOTS | empty""" p[0] = None if p[1] is None else PyQtMethodSpecifier.SLOT # C/C++ enums. ################################################################ # The enum annotations. _ENUM_ANNOTATIONS = ( 'BaseType', 'NoScope', 'NoTypeHint', 'PyName', ) # The enum member annotations. _ENUM_MEMBER_ANNOTATIONS = ( 'NoTypeHint', 'PyName', ) def p_enum_decl(p): "enum_decl : enum opt_enum_key opt_name opt_annos '{' opt_enum_body '}' ';'" pm = p.parser.pm if pm.skipping: return pm.check_annotations(p, 4, "enum", _ENUM_ANNOTATIONS) pm.add_enum(p, 1, p[3], p[2], p[4], p[6]) def p_opt_enum_key(p): """opt_enum_key : class | struct | union | empty""" # Return True if the enum is scoped. p[0] = p[1] is not None def p_opt_enum_body(p): """opt_enum_body : enum_body | empty""" p[0] = [] if p[1] is None else p[1] def p_enum_body(p): """enum_body : enum_line | enum_body enum_line""" if len(p) == 2: p[0] = [] line = p[1] else: p[0] = p[1] line = p[2] if line is not None: p[0].append(line) def p_enum_line(p): """enum_line : if_start | if_end | NAME opt_enum_assign opt_annos opt_comma""" pm = p.parser.pm if pm.skipping: p[0] = None return if len(p) == 5: pm.check_annotations(p, 3, "enum member", _ENUM_MEMBER_ANNOTATIONS) p[0] = (p[1], cached_name(pm.spec, pm.get_py_name(p[1], p[3])), p[3].get('NoTypeHint', False)) else: p[0] = None def p_opt_enum_assign(p): """opt_enum_assign : '=' value | empty""" def p_opt_comma(p): """opt_comma : empty | ','""" # C++ exceptions. ############################################################# # The exception annotations. _EXCEPTION_ANNOTATIONS = ( 'Default', 'PyName', ) def p_exception(p): "exception : Exception scoped_name opt_base_exception opt_annos '{' exception_body '}' ';'" pm = p.parser.pm if pm.skipping: return pm.cpp_only(p, 1, "%Exception") pm.check_annotations(p, 4, "exception", _EXCEPTION_ANNOTATIONS) cpp_name = p[2] py_name = pm.get_py_name(cpp_name.base_name, p[4]) pm.check_attributes(p, 2, py_name) builtin_base, defined_base = p[3] raise_code = p[6].get('%RaiseCode') type_header_code = p[6].get('%TypeHeaderCode') if raise_code is None: pm.parser_error(p, 1, "%Exception must have a %RaiseCode sub-directive") xd = pm.find_exception(p, 1, cpp_name, raise_code=raise_code) module = pm.module_state.module if xd.iface_file.module is not None: pm.parser_error(p, 2, "an %Exception with this name has already been defined") xd.iface_file.module = module if type_header_code is not None: xd.iface_file.type_header_code.append(type_header_code) xd.builtin_base_exception = builtin_base xd.defined_base_exception = defined_base xd.py_name = py_name if p[4].get('Default'): if module.default_exception is None: module.default_exception = xd else: pm.parser_error(p, 1, "another %Exception has already been annotated with '/Default/'") def p_opt_base_exception(p): """opt_base_exception : '(' scoped_name ')' | empty""" builtin_base = None defined_base = None if len(p) == 4: base = p[2] base.make_absolute() # See if it is a project-defined exception. for xd in p.parser.pm.spec.exceptions: if xd.iface_file.fq_cpp_name == base: defined_base = xd break else: # See if it is a builtin Python exception. if len(base) == 1 and base.base_name.startswith('SIP_'): py_name = base.base_name[4:] from .python_exceptions import PYTHON_EXCEPTIONS if py_name in PYTHON_EXCEPTIONS: builtin_base = py_name p[0] = (builtin_base, defined_base) def p_exception_body(p): """exception_body : exception_line | exception_body exception_line""" if len(p) == 3: p[1].update(p[2]) p[0] = p[1] def p_exception_line(p): """exception_line : if_start | if_end | RaiseCode CODE_BLOCK | TypeHeaderCode CODE_BLOCK""" p[0] = {} if len(p) == 2 or p.parser.pm.skipping else {p[1]: p[2]} # C/C++ functions. ############################################################ # The function annotations. _FUNCTION_ANNOTATIONS = ( '__len__', '__imatmul__', '__matmul__', 'AbortOnException', 'AllowNone', 'AutoGen', 'Deprecated', 'DisallowNone', 'Encoding', 'Factory', 'HoldGIL', 'KeywordArgs', 'KeepReference', 'NewThread', 'NoArgParser', 'NoCopy', 'NoRaisesPyException', 'NoTypeHint', 'NoVirtualErrorHandler', 'Numeric', 'PostHook', 'PreHook', 'PyInt', 'PyName', 'PyQtSignalHack', 'RaisesPyException', 'ReleaseGIL', 'Sequence', 'VirtualErrorHandler', 'Transfer', 'TransferBack', 'TransferThis', 'TypeHint', ) def p_function(p): """function : function_decl | assignment_operator_decl | operator_decl | operator_cast_decl""" p[0] = p[1] def p_function_decl(p): "function_decl : cpp_type NAME '(' opt_arg_list ')' opt_const opt_final opt_exceptions opt_abstract opt_annos opt_signature ';' opt_docstring premethod_code method_code virtual_catcher_code virtual_call_code" pm = p.parser.pm if pm.skipping: return pm.check_annotations(p, 10, "function", _FUNCTION_ANNOTATIONS) pm.apply_type_annotations(p, 10, p[1], p[10]) p[0] = pm.add_function(p, 1, p[2], p[1], p[4], p[10], const=p[6], final=p[7], exceptions=p[8], abstract=p[9], cpp_signature=p[11], docstring=p[13], premethod_code=p[14], method_code=p[15], virtual_catcher_code=p[16], virtual_call_code=p[17]) def p_assignment_operator_decl(p): "assignment_operator_decl : cpp_type operator '=' '(' cpp_type ')' ';'" pm = p.parser.pm if pm.skipping: return if pm.scope_access_specifier is AccessSpecifier.PRIVATE: pm.scope.cannot_assign = True else: pm.parser_error(p, 2, "an assignment operator may only be specified in the private section of a class") def p_operator_decl(p): "operator_decl : cpp_type operator operator_name '(' opt_arg_list ')' opt_const opt_final opt_exceptions opt_abstract opt_annos opt_signature ';' premethod_code method_code virtual_catcher_code virtual_call_code" pm = p.parser.pm if pm.skipping: return pm.check_annotations(p, 11, "function", _FUNCTION_ANNOTATIONS) pm.apply_type_annotations(p, 11, p[1], p[11]) scope = pm.scope # Handle the unary '+' and '-' operators. if (scope is not None and len(p[5]) == 0) or (scope is None and len(p[5]) == 1): if p[3] == '__add__': p[3] = '__pos__' elif p[3] == '__sub__': p[3] = '__neg__' p[0] = pm.add_function(p, 1, p[3], p[1], p[5], p[11], const=p[7], final=p[8], exceptions=p[9], abstract=p[10], cpp_signature=p[12], premethod_code=p[14], method_code=p[15], virtual_catcher_code=p[16], virtual_call_code=p[17]) # Types that can be cast to a Python int. _INT_TYPES = ( ArgumentType.BOOL, ArgumentType.CBOOL, ArgumentType.BYTE, ArgumentType.SBYTE, ArgumentType.UBYTE, ArgumentType.SHORT, ArgumentType.USHORT, ArgumentType.INT, ArgumentType.CINT, ArgumentType.UINT, ArgumentType.LONG, ArgumentType.ULONG, ArgumentType.LONGLONG, ArgumentType.ULONGLONG, ) # Types that can be cast to a Python float. _FLOAT_TYPES = ( ArgumentType.FLOAT, ArgumentType.CFLOAT, ArgumentType.DOUBLE, ArgumentType.CDOUBLE, ) def p_operator_cast_decl(p): "operator_cast_decl : operator cpp_type '(' opt_arg_list ')' opt_const opt_final opt_exceptions opt_abstract opt_annos opt_signature ';' premethod_code method_code virtual_catcher_code virtual_call_code" pm = p.parser.pm if pm.skipping: return pm.check_annotations(p, 10, "function", _FUNCTION_ANNOTATIONS) pm.apply_type_annotations(p, 10, p[2], p[10]) if pm.scope is None: pm.parser_error(p, 1, "operator casts can only be specified as part of a class") if len(p[4]) != 0: pm.parser_error(p, 1, "operator casts cannot have any arguments") # Get the slot name if it is implemented as a Python cast. slot_name = None cpp_type = p[2] if cpp_type.type in _INT_TYPES: slot_name = '__int__' elif cpp_type.type in _FLOAT_TYPES: slot_name = '__float__' elif cpp_type.type is not ArgumentType.DEFINED: pm.parser_error(p, 2, "unsupported operator cast") if slot_name is None: # Check the cast hasn't already been specified. for cast in pm.scope.casts: if cast.definition == cpp_type.definition: pm.parser_error(p, 2, "operator cast has already been specified for this class") break else: pm.scope.casts.insert(0, cpp_type) else: p[0] = pm.add_function(p, 1, slot_name, p[2], p[4], p[10], const=p[6], final=p[7], exceptions=p[8], abstract=p[9], cpp_signature=p[11], premethod_code=p[13], method_code=p[14], virtual_catcher_code=p[15], virtual_call_code=p[16]) def p_opt_arg_list(p): """opt_arg_list : arg_list | empty""" if p[1] is None: arg_list = [] else: arg_list = p[1] # Handle C void prototypes. if bool(p.parser.pm.c_bindings) and len(arg_list) == 1: arg = arg_list[0] if arg.type is ArgumentType.VOID and len(arg.derefs) == 0: arg_list = [] p[0] = arg_list def p_arg_list(p): """arg_list : arg_value | arg_list ',' arg_value""" if len(p) == 2: arg_list = [p[1]] else: arg_list = p[1] arg_list.append(p[3]) p[0] = arg_list def p_arg_value(p): "arg_value : arg_type opt_assign" p[0] = p[1] p[0].default_value = p[2] # The argument annotations. _ARGUMENT_ANNOTATIONS = ( 'AllowNone', 'Array', 'ArraySize', 'Constrained', 'DisallowNone', 'Encoding', 'GetWrapper', 'In', 'KeepReference', 'NoCopy', 'Out', 'PyInt', 'ResultSize', 'ScopesStripped', 'Transfer', 'TransferBack', 'TransferThis', 'TypeHint', 'TypeHintIn', 'TypeHintOut', 'TypeHintValue', ) def p_arg_type(p): "arg_type : cpp_type opt_name opt_annos" pm = p.parser.pm pm.check_annotations(p, 3, "argument", _ARGUMENT_ANNOTATIONS) arg = p[1] annotations = p[3] if p[2] is not None: arg.name = cached_name(pm.spec, p[2]) pm.apply_common_argument_annotations(p, 3, arg, annotations) has_array = 'Array' in annotations has_array_size = 'ArraySize' in annotations if has_array: if has_array_size: pm.parser_error(p, 3, "/Array/ and /ArraySize/ cannot both be specified") arg.array = ArrayArgument.ARRAY elif has_array_size: arg.array = ArrayArgument.ARRAY_SIZE pm.apply_type_annotations(p, 3, arg, annotations) if 'Constrained' in annotations: arg.is_constrained = True if arg.type is ArgumentType.BOOL: arg.type = ArgumentType.CBOOL elif arg.type is ArgumentType.INT: arg.type = ArgumentType.CINT elif arg.type is ArgumentType.FLOAT: arg.type = ArgumentType.CFLOAT elif arg.type is ArgumentType.DOUBLE: arg.type = ArgumentType.CDOUBLE arg.get_wrapper = annotations.get('GetWrapper', False) arg.is_in = annotations.get('In', False) arg.is_out = annotations.get('Out', False) arg.result_size = annotations.get('ResultSize', False) try: arg.scopes_stripped = annotations['ScopesStripped'] if arg.scopes_stripped <= 0: pm.parser_error(p, 3, "/ScopesStripped/ must be greater than 0") except KeyError: arg.scopes_stripped = 0 arg.transfer = pm.get_transfer(p, 3, annotations) p[0] = arg def p_opt_assign(p): """opt_assign : '=' expr | empty""" p[0] = p[2] if len(p) == 3 else None def p_expr(p): """expr : value | expr binop value""" if len(p) == 2: expr = [p[1]] else: expr = p[1] expr[-1].binary_operator = p[2] expr.append(p[3]) p[0] = expr def p_value(p): "value : opt_cast opt_unop simple_value" value = p[3] value.cast = p[1] value.unary_operator = p[2] p[0] = value def p_simple_value(p): """simple_value : empty_value | function_call_value | null_value | number_value | quoted_char_value | real_value | scoped_name_value | string_value""" p[0] = p[1] def p_empty_value(p): "empty_value : '{' '}'" p[0] = Value(ValueType.EMPTY, None) def p_function_call_value(p): "function_call_value : base_type '(' opt_expr_list ')'" p[0] = Value(ValueType.FCALL, FunctionCall(p[1], p[3])) def p_null_value(p): "null_value : NULL" p[0] = Value(ValueType.NUMERIC, 0) def p_number_value(p): """number_value : NUMBER | bool_value""" p[0] = Value(ValueType.NUMERIC, p[1]) def p_quoted_char_value(p): "quoted_char_value : QUOTED_CHAR" p[0] = Value(ValueType.QCHAR, p[1]) def p_real_value(p): "real_value : REAL" p[0] = Value(ValueType.REAL, p[1]) def p_scoped_name_value(p): "scoped_name_value : scoped_name" p[0] = Value(ValueType.SCOPED, p[1]) def p_string_value(p): "string_value : STRING" p[0] = Value(ValueType.STRING, p[1]) def p_opt_expr_list(p): """opt_expr_list : expr_list | empty""" p[0] = [] if p[1] is None else p[1] def p_expr_list(p): """expr_list : expr | expr_list ',' expr""" if len(p) == 2: value = [p[1]] else: value = p[1] value.append(p[3]) p[0] = value def p_opt_cast(p): """opt_cast : '(' scoped_name ')' | empty""" p[0] = p[2] if len(p) == 4 else None def p_binop(p): """binop : '-' | '+' | '*' | '/' | '&' | '|'""" p[0] = p[1] def p_opt_unop(p): """opt_unop : empty | '!' | '~' | '-' | '+' | '*' | '&'""" p[0] = p[1] def p_opt_exceptions(p): """opt_exceptions : empty | noexcept | throw '(' opt_exception_list ')'""" if p[1] == 'throw': p[0] = p[3] p.parser.pm.deprecated(p, 1) elif p[1] == 'noexcept': p[0] = ThrowArguments() else: p[0] = None def p_opt_exception_list(p): """opt_exception_list : exception_list | empty""" p[0] = ThrowArguments() if p[1] is None else p[1] def p_exception_list(p): """exception_list : scoped_name | exception_list ',' scoped_name""" pm = p.parser.pm if len(p) == 2: throw_args = ThrowArguments(arguments=[pm.find_exception(p, 1, p[1])]) else: throw_args = p[1] throw_args.arguments.append(pm.find_exception(p, 3, p[3])) p[0] = throw_args def p_opt_abstract(p): """opt_abstract : '=' NUMBER | empty""" if len(p) == 2: p[0] = False else: if p[2] != 0: p.parser.pm.parser_error(p, 2, "'0' expected") p[0] = True def p_opt_signature(p): """opt_signature : '[' cpp_type '(' opt_arg_list ')' ']' | empty""" if len(p) == 7: p[0] = Signature(args=p[4], result=p[2]) else: p[0] = None # The map of operators to dunder method names. _OPERATOR_NAME_MAP = { '+': '__add__', '-': '__sub__', '*': '__mul__', '/': '__truediv__', '%': '__mod__', '&': '__and__', '|': '__or__', '^': '__xor__', '<<': '__lshift__', '>>': '__rshift__', '+=': '__iadd__', '-=': '__isub__', '*=': '__imul__', '/=': '__itruediv__', '%=': '__imod__', '&=': '__iand__', '|=': '__ior__', '^=': '__ixor__', '<<=': '__ilshift__', '>>=': '__irshift__', '~': '__invert__', '()': '__call__', '[]': '__getitem__', '<': '__lt__', '<=': '__le__', '==': '__eq__', '!=': '__ne__', '>': '__gt__', '>=': '__ge__', } def p_operator_name(p): """operator_name : '+' | '-' | '*' | '/' | '%' | '&' | '|' | '^' | '<' '<' | '>' '>' | '+' '=' | '-' '=' | '*' '=' | '/' '=' | '%' '=' | '&' '=' | '|' '=' | '^' '=' | '<' '<' '=' | '>' '>' '=' | '~' | '(' ')' | '[' ']' | '<' | '<' '=' | '=' '=' | '!' '=' | '>' | '>' '='""" p[0] = _OPERATOR_NAME_MAP[''.join(p[1:])] def p_method_code(p): """method_code : MethodCode CODE_BLOCK | empty""" p[0] = p[2] if len(p) == 3 else None def p_premethod_code(p): """premethod_code : PreMethodCode CODE_BLOCK | empty""" p[0] = p[2] if len(p) == 3 else None def p_virtual_call_code(p): """virtual_call_code : VirtualCallCode CODE_BLOCK | empty""" p[0] = p[2] if len(p) == 3 else None def p_virtual_catcher_code(p): """virtual_catcher_code : VirtualCatcherCode CODE_BLOCK | empty""" p[0] = p[2] if len(p) == 3 else None # C++ namespaces. ############################################################# # The namespace annotations. _NAMESPACE_ANNOTATIONS = ( 'PyQtNoQMetaObject', ) def p_namespace_decl(p): "namespace_decl : namespace namespace_head opt_namespace_body ';'" pm = p.parser.pm if pm.skipping: return if pm.in_main_module: pm.scope.iface_file.cpp_name.used = True pm.scope.py_name.used = True pm.pop_scope() def p_namespace_head(p): "namespace_head : scoped_name opt_annos" pm = p.parser.pm if pm.skipping: return pm.cpp_only(p, 1, "namespaces") pm.check_annotations(p, 2, "namespace", _NAMESPACE_ANNOTATIONS) namespace = pm.new_class(p, 1, IfaceFileType.NAMESPACE, normalised_scoped_name(p[1], pm.scope)) namespace.pyqt_no_qmetaobject = p[2].get('PyQtNoQMetaObject', False) pm.push_scope(namespace) def p_opt_namespace_body(p): """opt_namespace_body : '{' namespace_body '}' | empty""" def p_namespace_body(p): """namespace_body : namespace_statement | namespace_body namespace_statement""" # C/C++ typedefs. ############################################################# # The typedef annotations. _TYPEDEF_ANNOTATIONS = ( 'Capsule', 'Encoding', 'NoTypeName', 'PyInt', 'PyName', 'TypeHint', 'TypeHintIn', 'TypeHintOut', ) def p_typedef_decl(p): """typedef_decl : typedef cpp_type NAME opt_annos ';' opt_docstring | typedef cpp_type '(' '*' NAME ')' '(' cpp_types ')' opt_annos ';' opt_docstring""" pm = p.parser.pm if pm.skipping: return if len(p) == 7: type = p[2] name_symbol = 3 annos_symbol = 4 docstring = p[6] else: type = p[2] name_symbol = 5 annos_symbol = 10 docstring = p[12] cpp_name = p[name_symbol] fq_cpp_name = normalised_scoped_name(ScopedName(cpp_name), pm.scope) annotations = p[annos_symbol] pm.check_annotations(p, annos_symbol, "typedef", _TYPEDEF_ANNOTATIONS) pm.apply_type_annotations(p, annos_symbol, type, annotations) no_type_name = annotations.get('NoTypeName', False) # See if we are instantiating a class template. if type.type is ArgumentType.TEMPLATE: instantiated = pm.instantiate_class_template(p, name_symbol, fq_cpp_name, type.definition, pm.get_py_name(cpp_name, annotations), no_type_name, docstring) if instantiated: return # Handle any 'Capsule' annotation. if 'Capsule' in annotations: # Make sure the type is 'void *'. if type.type is ArgumentType.VOID and len(type.derefs) == 1 and not type.is_const and not type.is_reference: type.type = ArgumentType.CAPSULE type.derefs = [] else: pm.parser_error(p, annos_symbol, "/Capsule/ can only be specified for a void* type") pm.add_typedef(p, name_symbol, WrappedTypedef(fq_cpp_name, pm.module_state.module, pm.scope, type, no_type_name=no_type_name)) # C/C++ unions. ############################################################### # The union annotations. _UNION_ANNOTATIONS = ( 'AllowNone', 'DelayDtor', 'Deprecated', 'ExportDerived', 'External', 'FileExtension', 'Metatype', 'Mixin', 'NoDefaultCtors', 'NoTypeHint', 'PyName', 'PyQtFlags', 'PyQtFlagsEnums', 'PyQtInterface', 'PyQtNoQMetaObject', 'Supertype', 'TypeHint', 'TypeHintIn', 'TypeHintOut', 'TypeHintValue', ) def p_union_decl(p): "union_decl : union union_head opt_class_definition ';'" pm = p.parser.pm if pm.skipping: return pm.complete_class(p, 2, p[2], p[3]) def p_union_head(p): "union_head : scoped_name opt_annos" pm = p.parser.pm if pm.skipping: return pm.check_annotations(p, 2, "union", _UNION_ANNOTATIONS) pm.define_class(p, 1, ClassKey.UNION, p[1], p[2]) # Return the annotations. p[0] = p[2] # C/C++ variables. ############################################################ # The variable annotations. _VARIABLE_ANNOTATIONS = ( 'Encoding', 'NoSetter', 'NoTypeHint', 'PyInt', 'PyName', 'TypeHint', ) def p_variable(p): "variable : cpp_type NAME opt_annos variable_body ';'" pm = p.parser.pm if pm.skipping: return type = p[1] cpp_name = p[2] annos_symbol = 3 body = p[4] # We don't currently support /AllowNone/ and /DisallowNone/ for variables. # Generated variable setters support allow None for some (pointer) types # but not others. For pointer types we explicity disallow None. This # doesn't affect the generated code but does mean that type hints match # those from previous versions. This should be changed to properly support # the annotations. if len(type.derefs) != 0: type.disallow_none = True annotations = p[annos_symbol] pm.check_annotations(p, annos_symbol, "variable", _VARIABLE_ANNOTATIONS) pm.apply_type_annotations(p, annos_symbol, type, annotations) py_name = cached_name(pm.spec, pm.get_py_name(cpp_name, p[3])) if pm.in_main_module: py_name.used = True fq_cpp_name = normalised_scoped_name(cpp_name, pm.scope) variable = WrappedVariable(fq_cpp_name, pm.module_state.module, py_name, pm.scope, type) variable.no_setter = annotations.get('NoSetter', False) variable.no_type_hint = annotations.get('NoTypeHint', False) variable.access_code = body.get('%AccessCode') variable.get_code = body.get('%GetCode') variable.set_code = body.get('%SetCode') pm.validate_variable(p, 1, variable) pm.spec.variables.append(variable) p[0] = variable def p_variable_body(p): """variable_body : '{' variable_body_directives '}' | empty""" p[0] = p[2] if len(p) == 4 else {} def p_variable_body_directives(p): """variable_body_directives : variable_body_directive | variable_body_directives variable_body_directive""" if len(p) == 3: p[1].update(p[2]) p[0] = p[1] def p_variable_body_directive(p): """variable_body_directive : if_start | if_end | AccessCode CODE_BLOCK | GetCode CODE_BLOCK | SetCode CODE_BLOCK""" p[0] = {} if len(p) == 2 or p.parser.pm.skipping else {p[1]: p[2]} # Annotations. ################################################################ def p_opt_annos(p): """opt_annos : '/' annotations '/' | empty""" p[0] = p[2] if len(p) == 4 else {} def p_annotations(p): """annotations : annotation | annotations ',' annotation""" if len(p) == 4: p[1].update(p[3]) p[0] = p[1] def p_annotation(p): """annotation : NAME | NAME '=' annotation_value""" value = None if len(p) == 2 else p[3] value = p.parser.pm.validate_annotation(p, 1, value) p[0] = {p[1]: value} def p_annotation_value(p): """annotation_value : dotted_name | STRING | NUMBER""" p[0] = p[1] # A (possibly) scoped name. ################################################### # Note that support for the global scope (ie. a leading '::') requires the # following to eliminate a shift/reduce conflict. However that still leaves # one other shift/reduce conflict which I can't get rid of. precedence = ( ('left', 'SCOPE'), ) def p_scoped_name(p): """scoped_name : SCOPE relative_scoped_name | relative_scoped_name""" if len(p) == 3: p.parser.pm.cpp_only(p, 1, "scoped names") p[2].make_absolute() p[0] = p[2] else: p[0] = p[1] def p_relative_scoped_name(p): """relative_scoped_name : NAME | relative_scoped_name SCOPE NAME""" if len(p) == 2: p[0] = ScopedName(p[1]) else: p.parser.pm.cpp_only(p, 2, "scoped names") p[1].append(p[3]) p[0] = p[1] # The remaining value productions. ############################################ def p_template_decl(p): "template_decl : template '<' cpp_types '>'" pm = p.parser.pm if pm.skipping: return p[0] = Signature(args=p[3]) pm.parsing_template = True def p_bool_value(p): """bool_value : true | True | false | False""" p[0] = (p[1].lower() == 'true') def p_dotted_name(p): """dotted_name : NAME | DOTTED_NAME""" p[0] = DottedName(p[1]) def p_file_path(p): """file_path : NAME | DOTTED_NAME | FILE_PATH""" p[0] = p[1] def p_empty(p): "empty :" p[0] = None def p_opt_const(p): """opt_const : const | empty""" p[0] = p[1] is not None def p_opt_docstring(p): """opt_docstring : docstring | empty""" p[0] = p[1] def p_opt_final(p): """opt_final : final | empty""" p[0] = p[1] is not None def p_opt_name(p): """opt_name : NAME | empty""" p[0] = p[1] def p_opt_virtual(p): """opt_virtual : virtual | empty""" pm = p.parser.pm if pm.skipping: return if p[1] is not None: pm.parsing_virtual = True def p_ored_qualifiers(p): """ored_qualifiers : NAME | '!' NAME | ored_qualifiers LOGICAL_OR NAME | ored_qualifiers LOGICAL_OR '!' NAME""" pm = p.parser.pm nr_symbols = len(p) if nr_symbols == 2: p[0] = pm.evaluate_feature_or_platform(p, 1) elif nr_symbols == 3: p[0] = pm.evaluate_feature_or_platform(p, 2, inverted=True) elif nr_symbols == 4: p[0] = p[1] or pm.evaluate_feature_or_platform(p, 3) else: p[0] = p[1] or pm.evaluate_feature_or_platform(p, 4, inverted=True) def p_qualifier_list(p): """qualifier_list : NAME | qualifier_list NAME""" if len(p) == 2: value = [p[1]] else: value = p[1] value.append(p[2]) p[0] = value def p_qualifiers(p): """qualifiers : ored_qualifiers | opt_name '-' opt_name""" if len(p) == 2: p[0] = p[1] else: p[0] = p.parser.pm.evaluate_timeline(p, 1, 3) sip-6.8.6/sipbuild/generator/parser/tokens.py000066400000000000000000000204601464421045000213020ustar00rootroot00000000000000# SPDX-License-Identifier: BSD-2-Clause # Copyright (c) 2024 Phil Thompson from ..specification import CodeBlock from .ply.lex import TOKEN # The lexer states. states = ( ('code', 'exclusive'), ('ccomment', 'exclusive'), ('directive', 'inclusive'), ('needeol', 'inclusive'), ) # The single character tokens. literals = '(){}.,;:=!-+*/&|~<>[]%^' # The non-code directives. directives = { 'AutoPyName', 'CompositeModule', 'DefaultDocstringFormat', 'DefaultDocstringSignature', 'DefaultEncoding', 'DefaultMetatype', 'DefaultSupertype', 'End', 'Exception', 'Feature', 'HideNamespace', 'If', 'Import', 'Include', 'License', 'MappedType', 'Module', 'Platforms', 'Property', 'Timeline', # Remove in SIP v7. 'Plugin', } # The code directives. code_directives = { 'AccessCode', 'BIGetBufferCode', 'BIReleaseBufferCode', 'ConvertFromTypeCode', 'ConvertToSubClassCode', 'ConvertToTypeCode', 'Copying', 'Docstring', 'ExportedHeaderCode', 'ExportedTypeHintCode', 'Extract', 'FinalisationCode', 'GCClearCode', 'GCTraverseCode', 'GetCode', 'InitialisationCode', 'InstanceCode', 'MethodCode', 'ModuleCode', 'ModuleHeaderCode', 'PickleCode', 'PostInitialisationCode', 'PreInitialisationCode', 'PreMethodCode', 'RaiseCode', 'ReleaseCode', 'SetCode', 'TypeCode', 'TypeHeaderCode', 'TypeHintCode', 'UnitCode', 'UnitPostIncludeCode', 'VirtualCallCode', 'VirtualCatcherCode', 'VirtualErrorHandler', # Remove in SIP v7. 'BIGetCharBufferCode', 'BIGetReadBufferCode', 'BIGetSegCountCode', 'BIGetWriteBufferCode', } # The plain keywords. keywords = { 'bool', 'char', 'class', 'const', 'double', 'enum', 'explicit', 'false', 'final', 'float', 'int', 'long', 'namespace', 'noexcept', 'NULL', 'operator', 'private', 'protected', 'public', 'Py_hash_t', 'Py_ssize_t', 'Q_SIGNAL', 'Q_SIGNALS', 'Q_SLOT', 'Q_SLOTS', 'short', 'signals', 'signed', 'SIP_PYBUFFER', 'SIP_PYCALLABLE', 'SIP_PYDICT', 'SIP_PYENUM', 'SIP_PYLIST', 'SIP_PYOBJECT', 'SIP_PYSLICE', 'SIP_PYTUPLE', 'SIP_PYTYPE', 'size_t', 'slots', 'static', 'struct', 'template', 'throw', 'true', 'typedef', 'union', 'unsigned', 'virtual', 'void', 'wchar_t', # Remove in SIP v7. 'SIP_SSIZE_T', } # The directive keywords. directive_keywords = { 'all_raise_py_exception', 'call_super_init', 'default_VirtualErrorHandler', 'False', 'format', 'get', 'id', 'keyword_arguments', 'language', 'licensee', 'name', 'optional', 'order', 'remove_leading', 'set', 'signature', 'timestamp', 'True', 'type', 'py_ssize_t_clean', 'use_argument_names', 'use_limited_api', } # The lexer tokens. tokens = [ 'CODE_BLOCK', 'DOTTED_NAME', 'ELLIPSIS', 'EOF', 'EOL', 'FILE_PATH', 'LOGICAL_OR', 'NAME', 'NUMBER', 'QUOTED_CHAR', 'REAL', 'SCOPE', 'STRING', ] tokens.extend(directives) tokens.extend(code_directives) tokens.extend(keywords) tokens.extend(directive_keywords) # Handle EOF. def t_eof(t): try: t.lexer.pm.pop_file() except IndexError: return None # Return an explicit EOF token. This stops the parser looking too far into # the popped file. t.type = 'EOF' return t # Handle errors. def t_ANY_error(t): t.lexer.pm.lexer_error(t, "'{0}' is unexpected".format(t.value[0])) t.lexer.skip(1) # Ignore whitespace except when reading code blocks. t_ANY_ignore = ' \t\r' t_code_ignore = '' # Handle newlines outside of code blocks and comments. def t_newline(t): r'\n' lexer = t.lexer pm = lexer.pm # Maintain the line number. lexer.lineno += 1 # Enter the 'code' state if we are at the end of a code directive name and # arguments. if pm.code_block is not None and pm.paren_depth == 0: pm.code_block.line_nr = lexer.lineno pm.set_lexer_state('code') # Maintain the parenthesis depth. def t_LPAREN(t): r'\(' t.lexer.pm.paren_depth += 1 t.type = '(' return t def t_RPAREN(t): r'\)' t.lexer.pm.paren_depth -= 1 t.type = ')' return t # Handle directives. def t_DIRECTIVE(t): r'%[a-zA-Z][a-zA-Z]*' # The name of the directive is used as its type. name = t.value[t.value.index('%') + 1:] if name in code_directives: t.lexer.pm.code_block = CodeBlock(t.lexer.pm.raw_sip_file) t.type = name elif name in directives: t.type = name return t # Handle the %End of a code directive. def t_code_END(t): r'%End' code_block = t.lexer.pm.code_block t.lexer.pm.code_block = None # Ignore any indentation preceding the # %End. code_block.text = code_block.text.rstrip(' \t') t.type = 'CODE_BLOCK' t.value = code_block t.lexer.begin('INITIAL') return t # Handle a newline when an end-of-line needs to be reported to the parser. def t_needeol_newline(t): r'\n' # Maintain the line number. t.lexer.lineno += 1 t.lexer.pm.set_lexer_state() t.type = 'EOL' return t # Handle a newline in a code directive. def t_code_newline(t): r'\n' # Maintain the line number. t.lexer.lineno += 1 t.lexer.pm.code_block.text += t.value # Discard the token. return None # Handle a character in a code directive. def t_code_CH(t): r'.' t.lexer.pm.code_block.text += t.value # Discard the token. return None # Handle keywords, ellipsis, names, dotted name and file paths. ambiguous = r'[._A-Za-z][._/A-Za-z\d\-]*[._A-Za-z\d]' @TOKEN(ambiguous) def t_AMBIGUOUS(t): t.type = t.lexer.pm.disambiguate_token(t.value, keywords) return t # Handle directive keywords (ie. keywords that are only recognised in the # context of a directive), ellipsis, names, dotted name and file paths. @TOKEN(ambiguous) def t_directive_AMBIGUOUS(t): t.type = t.lexer.pm.disambiguate_token(t.value, directive_keywords) return t # Handle a C++-style comment. def t_CPPCOMMENT(t): r'//.*' # Discard the token. return None # Handle the start of a C-style comment. def t_COMMENTSTART(t): r'/\*' t.lexer.push_state('ccomment') # Discard the token. return None # Handle the end of a C-style comment. def t_ccomment_COMMENTEND(t): r'\*/' t.lexer.pop_state() # Discard the token. return None # Handle a newline in a C-style comment. def t_ccomment_newline(t): r'\n' # Maintain the line number. t.lexer.lineno += 1 # Discard the token. return None # Handle the content of a C-style comment. def t_ccomment_CH(t): r'.' # Maintain the line number. if t.value == '\n': t.lexer.lineno += 1 # Discard the token. return None # Handle an unsigned hexadecimal number. def t_HEXNUMBER(t): r'0x[\da-fA-F]+' t.type = 'NUMBER' t.value = int(t.value, base=16) return t # Handle a number. def t_NUMBER(t): r'-?(\d+(\.\d*)?|\.\d+)([eE][-+]?\d+)?[fFlLuU]?' # Remove any suffix character. value = t.value if not value[-1].isdigit(): value = value[:-1] try: t.type = 'NUMBER' t.value = int(value) except ValueError: t.type = 'REAL' t.value = float(value) return t # Handle a double-quoted string. def t_STRING(t): r'"(\\.|[^\\"])*"' # Strip the quotes and handle any standard escape characters. value = t.value.strip('"') value = value.replace(r'\n', '\n') value = value.replace(r'\r', '\r') value = value.replace(r'\t', '\t') value = value.replace(r'\\', '\\') t.type = 'STRING' t.value = value return t # Handle a single-quoted hex encoded character. def t_QHEXCH(t): r"'\\x[\da-fA-F]+'" t.type = 'QUOTED_CHAR' t.value = chr(int(t.value.strip("'")[2:], base=16)) return t # Handle a single-quoted character. def t_QCH(t): r"'[^'\n]*['\n]" # Make sure these is only one quoted character. If not then report the # error and carry on with a fudged value. n_ch = len(t.value) if n_ch != 3: t.lexer.pm.lexer_error(t, "exactly one character expected between single quotes") if n_ch == 0: t.value = '?' t.type = 'QUOTED_CHAR' t.value = t.value[1] return t # The remaining trivial token definitions. t_LOGICAL_OR = r'\|\|' t_SCOPE = r'::' # We only deal with a single character as everything else is handled by # AMBIGUOUS. t_NAME = r'[_A-Za-z]' sip-6.8.6/sipbuild/generator/python_slots.py000066400000000000000000000142321464421045000212500ustar00rootroot00000000000000# SPDX-License-Identifier: BSD-2-Clause # Copyright (c) 2024 Phil Thompson from .specification import PySlot # A 4-tuple of the name, the type, True if the slot requires %MethodCode and # the number of arguments required (-1 if any number is valid). _SLOT_DEFINITIONS = ( ('__str__', PySlot.STR, True, 0), ('__int__', PySlot.INT, False, 0), ('__float__', PySlot.FLOAT, False, 0), ('__len__', PySlot.LEN, True, 0), ('__contains__', PySlot.CONTAINS, True, 1), ('__add__', PySlot.ADD, False, 1), ('__sub__', PySlot.SUB, False, 1), ('__mul__', PySlot.MUL, False, 1), ('__mod__', PySlot.MOD, False, 1), ('__floordiv__', PySlot.FLOORDIV, True, 1), ('__truediv__', PySlot.TRUEDIV, False, 1), ('__and__', PySlot.AND, False, 1), ('__or__', PySlot.OR, False, 1), ('__xor__', PySlot.XOR, False, 1), ('__lshift__', PySlot.LSHIFT, False, 1), ('__rshift__', PySlot.RSHIFT, False, 1), ('__iadd__', PySlot.IADD, False, 1), ('__isub__', PySlot.ISUB, False, 1), ('__imul__', PySlot.IMUL, False, 1), ('__imod__', PySlot.IMOD, False, 1), ('__ifloordiv__', PySlot.IFLOORDIV, True, 1), ('__itruediv__', PySlot.ITRUEDIV, False, 1), ('__iand__', PySlot.IAND, False, 1), ('__ior__', PySlot.IOR, False, 1), ('__ixor__', PySlot.IXOR, False, 1), ('__ilshift__', PySlot.ILSHIFT, False, 1), ('__irshift__', PySlot.IRSHIFT, False, 1), ('__invert__', PySlot.INVERT, False, 0), ('__call__', PySlot.CALL, False, -1), ('__getitem__', PySlot.GETITEM, False, 1), ('__setitem__', PySlot.SETITEM, True, 2), ('__delitem__', PySlot.DELITEM, True, 1), ('__lt__', PySlot.LT, False, 1), ('__le__', PySlot.LE, False, 1), ('__eq__', PySlot.EQ, False, 1), ('__ne__', PySlot.NE, False, 1), ('__gt__', PySlot.GT, False, 1), ('__ge__', PySlot.GE, False, 1), ('__bool__', PySlot.BOOL, True, 0), ('__neg__', PySlot.NEG, False, 0), ('__pos__', PySlot.POS, False, 0), ('__abs__', PySlot.ABS, True, 0), ('__repr__', PySlot.REPR, True, 0), ('__hash__', PySlot.HASH, True, 0), ('__index__', PySlot.INDEX, True, 0), ('__iter__', PySlot.ITER, True, 0), ('__next__', PySlot.NEXT, True, 0), ('__setattr__', PySlot.SETATTR, True, 2), ('__delattr__', PySlot.DELATTR, True, 1), ('__matmul__', PySlot.MATMUL, False, 1), ('__imatmul__', PySlot.IMATMUL, False, 1), ('__await__', PySlot.AWAIT, True, 0), ('__aiter__', PySlot.AITER, True, 0), ('__anext__', PySlot.ANEXT, True, 0), ) # The mapping of slot type to slot name. slot_type_name_map = {t: n for n, t, f, a in _SLOT_DEFINITIONS} # The mapping of slot name to a 3-tuple of type, %MethodCode flag and argument # count. slot_name_detail_map = {n: (t, f, a) for n, t, f, a in _SLOT_DEFINITIONS} def invalid_global_slot(slot): """ Return True if a slot cannot be specified as a global (ie. module level) slot. """ if slot in (PySlot.NEG, PySlot.POS): return False if is_number_slot(slot): return False if is_inplace_number_slot(slot): return False if is_rich_compare_slot(slot): return False return True def is_hash_return_slot(slot): """ Return True if a slot returns a Py_hash_t. """ return slot is PySlot.HASH _INPLACE_NUMBER_SLOTS = (PySlot.IADD, PySlot.ISUB, PySlot.IMUL, PySlot.IMOD, PySlot.IFLOORDIV, PySlot.ITRUEDIV, PySlot.IAND, PySlot.IOR, PySlot.IXOR, PySlot.ILSHIFT, PySlot.IRSHIFT, PySlot.IMATMUL) def is_inplace_number_slot(slot): """ Return True if a slot is an inplace binary numeric slot. """ return slot in _INPLACE_NUMBER_SLOTS _INPLACE_SEQUENCE_SLOTS = (PySlot.ICONCAT, PySlot.IREPEAT) def is_inplace_sequence_slot(slot): """ Return True if a slot is an inplace sequence slot. """ return slot in _INPLACE_SEQUENCE_SLOTS _INT_ARG_SLOTS = (PySlot.REPEAT, PySlot.IREPEAT) def is_int_arg_slot(slot): """ Return True if a slot taks an int argument. """ return slot in _INT_ARG_SLOTS _INT_RETURN_SLOTS = (PySlot.BOOL, PySlot.CONTAINS) def is_int_return_slot(slot): """ Return True if a slot returns an int. """ return slot in _INT_RETURN_SLOTS _MULTI_ARG_SLOTS = (PySlot.SETITEM, PySlot.CALL) def is_multi_arg_slot(slot): """ Return True if a slot takes more than one argument. """ return slot in _MULTI_ARG_SLOTS _NUMBER_SLOTS = (PySlot.ADD, PySlot.SUB, PySlot.MUL, PySlot.MOD, PySlot.FLOORDIV, PySlot.TRUEDIV, PySlot.AND, PySlot.OR, PySlot.XOR, PySlot.LSHIFT, PySlot.RSHIFT, PySlot.MATMUL) def is_number_slot(slot): """ Return True if a slot is a binary numeric slot. """ return slot in _NUMBER_SLOTS _RICH_COMPARE_SLOTS = (PySlot.LT, PySlot.LE, PySlot.EQ, PySlot.NE, PySlot.GT, PySlot.GE) def is_rich_compare_slot(slot): """ Return True if a slot is a rich comparision slot. """ return slot in _RICH_COMPARE_SLOTS def is_ssize_return_slot(slot): """ Return True if a slot returns a Py_ssize_t. """ return slot is PySlot.LEN _VOID_RETURN_SLOTS = (PySlot.SETITEM, PySlot.DELITEM, PySlot.SETATTR) def is_void_return_slot(slot): """ Return True if a slot returns a void. """ return slot in _VOID_RETURN_SLOTS _ZERO_ARG_SLOTS = (PySlot.STR, PySlot.INT, PySlot.FLOAT, PySlot.INVERT, PySlot.NEG, PySlot.LEN, PySlot.BOOL, PySlot.POS, PySlot.ABS, PySlot.REPR, PySlot.HASH, PySlot.INDEX, PySlot.ITER, PySlot.NEXT, PySlot.AWAIT, PySlot.AITER, PySlot.ANEXT) def is_zero_arg_slot(slot): """ Return True if a slot takes zero arguments. """ return slot in _ZERO_ARG_SLOTS # A map of slots and the names of their reflections. _SLOT_REFLECTIONS = { PySlot.ADD: '__radd__', PySlot.SUB: '__rsub__', PySlot.MUL: '__rmul__', PySlot.MATMUL: '__rmatmul__', PySlot.TRUEDIV: '__rtruediv__', PySlot.FLOORDIV: '__rfloordiv__', PySlot.MOD: '__rmod__', PySlot.LSHIFT: '__rlshift__', PySlot.RSHIFT: '__rrshift__', PySlot.AND: '__rand__', PySlot.OR: '__ror__', PySlot.XOR: '__rxor__', } def reflected_slot(slot): """ Return the name of the reflected version of a slot or None if it doesn't have one. """ return _SLOT_REFLECTIONS.get(slot) sip-6.8.6/sipbuild/generator/resolver/000077500000000000000000000000001464421045000177705ustar00rootroot00000000000000sip-6.8.6/sipbuild/generator/resolver/__init__.py000066400000000000000000000003021464421045000220740ustar00rootroot00000000000000# SPDX-License-Identifier: BSD-2-Clause # Copyright (c) 2024 Phil Thompson # Publish the API. This is private to the rest of sip. from .resolver import resolve sip-6.8.6/sipbuild/generator/resolver/resolver.py000066400000000000000000002337071464421045000222170ustar00rootroot00000000000000# SPDX-License-Identifier: BSD-2-Clause # Copyright (c) 2024 Phil Thompson from copy import copy from ..error_log import ErrorLog from ..instantiations import instantiate_type_hints from ..python_slots import (is_hash_return_slot, is_int_return_slot, is_inplace_number_slot, is_rich_compare_slot, is_ssize_return_slot, is_void_return_slot, is_zero_arg_slot) from ..scoped_name import ScopedName from ..specification import (AccessSpecifier, Argument, ArgumentType, ArrayArgument, ClassKey, Constructor, IfaceFileType, MappedType, Member, PyQtMethodSpecifier, PySlot, Signature, Transfer, ValueType, VirtualHandler, VirtualOverload, VisibleMember, WrappedClass) from ..templates import (encoded_template_name, same_template_signature, template_code, template_code_blocks, template_expansions) from ..utils import (append_iface_file, argument_as_str, cached_name, find_iface_file, find_method, same_argument_type, same_base_type, same_signature, search_typedefs) def resolve(spec, modules): """ Resolve all types of a parsed specification and create additional views so that code can be generated. """ error_log = ErrorLog() final_checks = [] # Build the list of all imports for each module. for mod in modules: _set_all_imports(mod, error_log) # Set the base name of the module. This is done for efficiency. mod.py_name = mod.fq_py_name.name.split('.')[-1] # Set the default meta-type for the main module if it doesn't have one # explicitly set. if spec.module.default_metatype is None: for mod in spec.module.all_imports: if mod.default_metatype is None: continue if spec.module.default_metatype is None: spec.module.default_metatype = mod.default_metatype elif spec.module.default_metatype is not mod.default_metatype: error_log.log( "'{0}' module has imported different default meta-types '{1}' and '{2}'".format( spec.module.fq_py_name, spec.module.default_metatype, mod.default_metatype)) # Check each class has been defined. for klass in spec.classes: if klass.iface_file.module is None: error_log.log( "class '{0}' has not been defined".format( klass.iface_file.fq_cpp_name)) # Mark any QObject class. This flag will ripple through all derived # classes when we set the hierarchy. if klass.iface_file.fq_cpp_name.base_name == 'QObject': klass.is_qobject = True # The class list has the main module's classes at the front and the ones # from the module at the most nested %Import at the end. Set the MRO for # each class and re-order the list of classes so that no class appears # before a super class or an enclosing scope class. reversed_classes = reversed(spec.classes) spec.classes = [] for klass in reversed_classes: # Ignore undefined classes. if klass.iface_file.module is None: continue _set_mro(spec, klass, error_log) # Resolve the various types in the modules. _resolve_module(spec, spec.module, error_log, final_checks) # Handle default ctors now that the argument types are resolved. for klass in spec.classes: if klass.no_default_ctors or klass.is_opaque or klass.iface_file.type is IfaceFileType.NAMESPACE: continue _add_default_copy_ctor(klass) # Add any automatically generated methods. for klass in spec.classes: for overload in klass.overloads: if overload.is_auto_generated: _add_auto_overload(spec, klass, overload) # Move casts and slots around to their correct classes (if in the same # module) or create proxies for them (if cross-module). _move_main_module_casts_slots(spec, error_log) # Automatically generate missing complementary slots. for klass in spec.classes: _add_complementary_slots(spec, klass) for klass in spec.module.proxies: _add_complementary_slots(spec, klass) # Generate the different class views. for klass in spec.classes: if klass.iface_file.type is IfaceFileType.CLASS: if klass.needs_shadow and not klass.is_incomplete and klass.dtor is not AccessSpecifier.PRIVATE and klass.can_create: klass.has_shadow = True # Get the list of visible Python member functions. _get_visible_py_members(spec, klass) # Get the virtual members. if klass.needs_shadow: _get_virtuals(spec, klass, error_log) elif klass.iface_file.type is IfaceFileType.NAMESPACE: for overload in klass.overloads: _iface_files_are_used_by_overload(spec, klass.iface_file.used, overload) for mod in modules: # Create the list of numbered types sorted by type name. _create_sorted_numbered_types(spec, mod, error_log) for overload in mod.overloads: _iface_files_are_used_by_overload(spec, mod.used, overload) # Update proxies with some information from the real classes. for klass in mod.proxies: klass.iface_file.type_nr = klass.real_class.iface_file.type_nr # Additional class specific checks. for klass in spec.classes: # Ignore undefined classes. if klass.iface_file.module is None: continue _check_helpers(spec, klass) _check_properties(klass, error_log) # Number the exceptions as they will be seen by the main module. for exception in spec.exceptions: exception_mod = exception.iface_file.module # Include the %TypeHeaderCode for exceptions defined in the main # module. if spec.abi_version >= (13, 1) or (spec.abi_version >= (12, 9) and spec.abi_version < (13, 0)): if exception_mod is spec.module: append_iface_file(spec.module.used, exception.iface_file) # Skip those that don't require a Python exception object to be # created. if exception.iface_file.type is not IfaceFileType.EXCEPTION: continue if exception.builtin_base_exception is None and exception.defined_base_exception is None: continue if exception_mod is spec.module or exception.needed: exception.exception_nr = exception_mod.nr_exceptions exception_mod.nr_exceptions += 1 # For PyQt6 mark all enum interface files as being used. if 'PyQt6' in spec.plugins: for enum in spec.enums: if enum.module is spec.module: _enum_iface_file_is_used(enum, spec.module) # Perform any final checks. for check in final_checks: check() # Raise an exception for any errors. error_log.as_exception() def _resolve_module(spec, mod, error_log, final_checks, seen=None): """ Resolve a module and the modules it imports. """ if seen is None: seen = [] elif mod in seen: # Nothing left to do. return # The modules on which this one depends must be done first because they # might generate new template-based types and they must be defined in the # right module. for imported_mod in mod.imports: _resolve_module(spec, imported_mod, error_log, final_checks, seen=seen) # Resolve typedefs, variables and global functions. _resolve_typedefs(spec, mod, error_log) _resolve_variables(spec, mod, error_log) _resolve_scope_overloads(spec, mod.overloads, error_log, final_checks) # Resolve class ctors, functions and casts. for klass in spec.classes: if klass.iface_file.module is mod: _resolve_ctors(spec, klass, error_log) # Handle any dtor exceptions. _set_needed_exceptions(spec, mod, klass.dtor_throw_args) _resolve_scope_overloads(spec, klass.overloads, error_log, final_checks, scope=klass) _transform_casts(spec, klass, error_log) # Resolve mapped types based on templates. _resolve_mapped_types(spec, mod, error_log, final_checks) seen.append(mod) # The map of slots and their compliments. _SLOT_MAP = { PySlot.LT: (PySlot.GE, '__ge__'), PySlot.LE: (PySlot.GT, '__gt__'), PySlot.GT: (PySlot.LE, '__le__'), PySlot.GE: (PySlot.LT, '__lt__'), PySlot.EQ: (PySlot.NE, '__ne__'), PySlot.NE: (PySlot.EQ, '__eq__'), } def _add_complementary_slots(spec, klass): """ Add any missing complementary slots to a class. This emulates the C++ behaviour of automatically interpreting (for example) >= as !<. """ for member in list(klass.members): try: compl, compl_name = _SLOT_MAP[member.py_slot] except KeyError: continue _add_complementary_slot(spec, klass, member, compl, compl_name) def _add_complementary_slot(spec, klass, member, compl, compl_name): """ Add a complementary slot if it is missing. """ member2 = None for overload in klass.overloads: if overload.common is not member or overload.is_complementary or overload.method_code is not None: continue # Try and find an existing complementary slot. for overload2 in klass.overloads: if overload2.common.py_slot is compl and same_signature(spec, overload.py_signature, overload2.py_signature): break else: # There is no explicit complementary slot so create a new member if # needed. if member2 is None: for member2 in klass.members: if member2.py_slot is compl: break else: member2 = copy(member) member2.py_name = cached_name(spec, compl_name) member2.py_slot = compl if member.py_name.used: member2.py_name.used = True klass.members.insert(0, member2) # Create the complementary slot. overload2 = copy(overload) overload2.is_virtual = False overload2.is_complementary = True overload2.common = member2 overload2.cpp_name = compl_name klass.overloads.insert(0, overload2) break def _check_helpers(spec, klass): """ See if a class supports array and copy helpers. """ # We disregard classes that are abstract, or have a private assignment # operator or don't have a public dtor. if klass.is_abstract: return if klass.cannot_assign: return if klass.dtor is not AccessSpecifier.PUBLIC: return # See if the class has a default ctor and a public copy ctor. public_default_ctor = public_copy_ctor = False for ctor in klass.ctors: if ctor.cpp_signature is None or ctor.access_specifier is not AccessSpecifier.PUBLIC: continue if len(ctor.cpp_signature.args) == 0 or ctor.cpp_signature.args[0].default_value is not None: # The ctor either has no arguments or all arguments have defaults. public_default_ctor = True elif len(ctor.cpp_signature.args) == 1: arg = ctor.cpp_signature.args[0] if arg.type is not ArgumentType.CLASS: continue if arg.definition is klass and arg.is_reference and arg.is_const and len(arg.derefs) == 0 and arg.default_value is None: public_copy_ctor = True if public_default_ctor: klass.needs_array_helper = True append_iface_file(klass.iface_file.module.used, klass.iface_file) if public_copy_ctor: klass.needs_copy_helper = True append_iface_file(klass.iface_file.module.used, klass.iface_file) def _set_all_imports(mod, error_log, seen=None): """ Set the list of all imports for a module. The list is ordered so that a module appears before any module that imports it. """ # Check for recursive imports. if seen is None: seen = [] elif mod in seen: error_log.log( "module '{0}' is imported recursively".format(mod.fq_py_name)) seen.append(mod) # Make sure all the direct imports are done first. for direct_import in mod.imports: _set_all_imports(direct_import, error_log, seen=seen) # Now build the list from our direct imports' lists but ignoring # duplicates. def _append_unique_import(imported): if imported not in mod.all_imports: mod.all_imports.append(imported) for direct_import in mod.imports: for imported in direct_import.all_imports: _append_unique_import(imported) _append_unique_import(direct_import) seen.remove(mod) def _move_main_module_casts_slots(spec, error_log): """ Move the casts and slots to the correct place for a main module (ie. the one we are generating code for). """ for klass in spec.classes: if klass.iface_file.module is spec.module: _move_class_casts(spec, klass, error_log) for member in spec.module.global_functions: if member.py_slot is not None and member.module is spec.module: _move_global_slot(spec, member, error_log) def _move_class_casts(spec, klass, error_log): """ Move any class casts to its correct class, or publish as a ctor extender. """ for cast in klass.casts: dst_klass = cast.definition # Create the new ctor. arg = Argument(ArgumentType.CLASS, definition=klass, derefs=list(cast.derefs), is_reference=cast.is_reference, is_const=cast.is_const, is_in=True, source_location=cast.source_location) signature = Signature(args=[arg]) ctor = Constructor(AccessSpecifier.PUBLIC, py_signature=signature, cpp_signature=signature, is_cast=True) # If the destination class is in a different module then use a proxy. if dst_klass.iface_file.module is not spec.module: _iface_file_is_used(spec.module.used, arg) dst_klass = _get_proxy(spec.module, dst_klass) ctor.no_typehint = True _iface_file_is_used(dst_klass.iface_file.used, arg) # Check it hasn't already been defined. for dst_ctor in dst_klass.ctors: if same_signature(spec, dst_ctor.py_signature, ctor.py_signature, strict=False): error_log.log( " operator '{0}::{0}({1})' already defined".format( dst_klass.iface_file.fq_cpp_name, klass.iface_file.fq_cpp_name)) dst_klass.ctors.append(ctor) def _move_global_slot(spec, global_slot, error_log): """ If possible, move a global slot to its correct class. """ for overload in list(spec.module.overloads): if overload.common is not global_slot: continue # We know that the slot has the right number of arguments, but the # first or second one needs to be a class or enum defined in the same # module. Otherwise we leave it as it is and publish it as a slot # extender. arg0 = overload.py_signature.args[0] try: arg1 = overload.py_signature.args[1] except IndexError: arg1 = None arg_members = None arg_overloads = None arg_module = None arg_enum = None is_second = False if arg0.type is ArgumentType.CLASS: arg_members = arg0.definition.members arg_overloads = arg0.definition.overloads arg_module = arg0.definition.iface_file.module elif arg0.type is ArgumentType.ENUM: arg_members = arg0.definition.slots arg_overloads = arg0.definition.overloads; arg_module = arg0.definition.module arg_enum = arg0.definition elif arg1 is None: if arg0.type is ArgumentType.NONE: # The error has already been logged. pass else: _log_overload_error(error_log, "the argument must be a class or enum", overload) continue elif arg1.type is ArgumentType.CLASS: arg_members = arg1.definition.members arg_overloads = arg1.definition.overloads arg_module = arg1.definition.iface_file.module is_second = True elif arg1.type is ArgumentType.ENUM: arg_members = arg1.definition.slots arg_overloads = arg1.definition.overloads arg_module = arg1.definition.module arg_enum = arg1.definition is_second = True else: if arg0.type is ArgumentType.NONE or arg1.type is ArgumentType.NONE: # The error has already been logged. pass else: _log_overload_error(error_log, "one of the arguments must be a class or enum", overload) continue # For rich comparisons the first argument must be a class or an enum. # For cross-module slots then it may only be a class. (This latter # limitation is artificial, but is unlikely to be a problem in # practice.) if is_rich_compare_slot(global_slot.py_slot): if is_second: _log_overload_error(error_log, "argument 1 must be a class or enum", overload) continue if arg_module is not global_slot.module and arg0.type is ArgumentType.ENUM: _log_overload_error(error_log, "argument 1 must be a class", overload) continue if arg_module is not global_slot.module: if is_rich_compare_slot(global_slot.py_slot): proxy = _get_proxy(arg_module, arg0.definition) # Create a new proxy member if needed. for proxy_member in proxy.members: if proxy_member.py_slot is global_slot.py_slot: break else: proxy_member = Member(arg_module, global_slot.py_name, py_slot=global_slot.py_slot, namespace_iface_file=global_slot.namespace_iface_file) proxy.members.insert(0, proxy_member) # Remove the overload from the list. spec.module.overloads.remove(overload) # Add the overload to the proxy. overload.common = proxy_member overload.no_typehint = True proxy.overloads.insert(0, overload) # Remove the overload's first argument. del overload.py_signature.args[0] continue # Remove from the list. spec.module.overloads.remove(overload) if arg_enum is not None: _enum_iface_file_is_used(arg_enum, arg_module) arg_enum.py_name.used = True # See if there is already a member or create a new one. inject_equality_slot = False for arg_member in arg_members: if arg_member.py_slot is global_slot.py_slot: break else: arg_member = copy(global_slot) arg_member.module = arg_module arg_members.insert(0, arg_member) # Legacy enum members, when accessed as scoped values, are created # on the fly. By default these members compare for equality # correctly (ie. 'E.M == E.M' works as expected). However if there # is another equality operator defined then it will fail so we have # to explicitly inject the comparison. if spec.abi_version < (13, 0) and arg0.type is ArgumentType.ENUM and arg_member.py_slot is PySlot.EQ and not is_second: inject_equality_slot = True # Move the overload to the end of the destination list. if is_second: overload.is_reflected = True overload.access_specifier = AccessSpecifier.PUBLIC overload.common = arg_member overload.is_global = True arg_overloads.append(overload) # Inject an additional equality slot if necessary. if inject_equality_slot: eq_overload = copy(overload) eq_overload.py_signature = copy(eq_overload.py_signature) eq_overload.py_signature.args = copy(eq_overload.py_signature.args) eq_overload.cpp_signature = eq_overload.py_signature eq_overload.py_signature.args[0].derefs = [False] del eq_overload.py_signature.args[1] arg_overloads.append(eq_overload) # Remove the first argument of inplace numeric operators and comparison # operators. if is_inplace_number_slot(arg_member.py_slot) or is_rich_compare_slot(arg_member.py_slot): # Remember if the argument was a pointer. if len(arg0.derefs) > 0: overload.dont_deref_self = True del overload.py_signature.args[0] # Remove the only argument of unary operators. if is_zero_arg_slot(arg_member.py_slot): del overload.py_signature.args[0] def _get_proxy(mod, klass): """ Create a proxy for a class if it doesn't already exist. Proxies are used as containers for cross-module extenders. """ for proxy in mod.proxies: if proxy.iface_file is klass.iface_file: return proxy proxy = WrappedClass(klass.iface_file, klass.py_name, scope=klass.scope, mro=klass.mro, real_class=klass, superclasses=klass.superclasses) mod.proxies.insert(0, proxy) return proxy def _add_auto_overload(spec, auto_klass, auto_overload): """ Add an overload that is automatically generated. """ # Find every class that has this one in its hierarchy. for klass in spec.classes: if klass is auto_klass: continue for mro_klass in klass.mro: if mro_klass is auto_klass: # Another overload may already exist. for member in klass.members: if member.py_name is auto_overload.common.py_name: break else: member = copy(auto_overload.common) member.module = klass.iface_file.module klass.members.insert(0, member) overload = copy(auto_overload) overload.common = member overload.is_autogenerated = False klass.overloads.insert(0, overload) if klass.iface_file.module is spec.module: member.py_name.used = True break def _set_mro(spec, klass, error_log, seen=None): """ Set the MRO for a class and add it to the list of classes so that it is after any classes it depends on. """ # See if it has already been done. if klass in spec.classes: return # Initialise the detection of recursive hierarchies. if seen is None: seen = [] # Handle any enclosing scope. if klass.scope is not None: _set_mro(spec, klass.scope, error_log, seen=seen) if klass.scope.deprecated: klass.deprecated = True if klass.iface_file.type is IfaceFileType.CLASS: # The first thing is itself. klass.mro.append(klass) if klass.convert_to_subclass_code is not None: klass.subclass_base = klass # Now do it's super-classes. seen.append(klass) for superklass in klass.superclasses: if superklass in seen: error_log.log( "recursive class hierarchy detected: '{0}' and '{1}'".format( klass.iface_file.fq_cpp_name, superklass.iface_file.fq_cpp_name)) continue # Unions cannot be super-classes. if superklass.class_key is ClassKey.UNION: error_log.log( "union '{0}' cannot be a super-class".format( superklass.iface_file.fq_cpp_name)) # Make sure the super-class's hierarchy has been done. */ _set_mro(spec, superklass, error_log, seen=seen) # Append the super-class's MRO. for superklass_mro in superklass.mro: if superklass_mro not in klass.mro: klass.mro.append(superklass_mro) if klass.iface_file.module is spec.module: superklass_mro.iface_file.needed = True if superklass_mro.deprecated: klass.deprecated = True # If the super-class is a QObject sub-class then this one is as # well. if superklass_mro.is_qobject: klass.is_qobject = True # If the super-class can't be assigned to then this one cannot # either. if superklass_mro.cannot_assign: klass.cannot_assign = True # If the super-class needs a shadow then this one should have # one as well. if superklass_mro.needs_shadow: klass.needs_shadow = True # Ensure that the sub-class base class is the furthest up the # hierarchy. if superklass_mro.subclass_base is not None: klass.subclass_base = superklass_mro.subclass_base; seen.remove(klass) # If the class doesn't have an explicit meta-type then inherit from the # module's default. if klass.metatype is None and len(klass.superclasses) == 0: # The class may not have been defined. if klass.iface_file.module is not None: klass.metatype = klass.iface_file.module.default_metatype if klass.metatype is not None and klass.iface_file.module is spec.module: klass.metatype.used = True # If the class doesn't have an explicit super-type then inherit from # the module's default. if klass.supertype is None and len(klass.superclasses) == 0: # The class may not have been defined. if klass.iface_file.module is not None: klass.supertype = klass.iface_file.module.default_supertype if klass.supertype is not None: # If the super-type ends with 'sip.wrapper' then assume it is the # default. if klass.supertype.name.endswith('sip.wrapper'): klass.supertype = None if klass.supertype is not None and klass.iface_file.module is spec.module: klass.supertype.used = True # Make sure that the module in which a sub-class convertor will be created # knows about the base class. if klass.subclass_base is not None: append_iface_file(klass.iface_file.module.used, klass.subclass_base.iface_file) # We can't have a shadow if the specification is incomplete, there is a # private dtor, there are no non-private ctors or there are private # abstract methods. if klass.is_incomplete or klass.dtor is AccessSpecifier.PRIVATE or not klass.can_create: klass.has_shadow = False else: # Note that we should be able to provide better support for abstract # private methods than we do at the moment. for overload in klass.overloads: if overload.is_abstract and overload.access_specifier is AccessSpecifier.PRIVATE: klass.has_shadow = False # It also means we cannot create an instance from Python. klass.can_create = False break # Add it to the new list of classes. spec.classes.append(klass) def _resolve_typedefs(spec, mod, error_log): """ Resolve the base types for all typedefs of a module. """ for typedef in spec.typedefs: if typedef.module is mod: _resolve_type(spec, typedef.module, typedef.scope, typedef.type, error_log) def _resolve_mapped_types(spec, mod, error_log, final_checks): """ Resolve the data types for mapped types based on a template. """ for mapped_type in spec.mapped_types: if mapped_type.iface_file.module is mod: if mapped_type.type.type is ArgumentType.TEMPLATE: _resolve_mapped_type_types(spec, mapped_type, error_log) else: _resolve_scope_overloads(spec, mapped_type.overloads, error_log, final_checks, scope=mapped_type) def _resolve_ctors(spec, klass, error_log): """ Resolve the data types for a class's ctors. """ for ctor in klass.ctors: _resolve_ctor_types(spec, klass, ctor, error_log) # Now check that the Python signature doesn't conflict with an earlier # one. If there is %MethodCode then assume that it will handle any # potential conflicts. if ctor.method_code is None: for previous_ctor in klass.ctors: if previous_ctor is ctor: break if previous_ctor.method_code is not None: continue sig_state = _same_python_signature(spec, previous_ctor.py_signature, ctor.py_signature) if sig_state is None: # The error has already been logged. break if sig_state: error_log.log( "class '{0}' has ctors with the same Python signature".format( klass.iface_file.fq_cpp_name)) break if klass.deprecated: ctor.deprecated = True def _transform_casts(spec, klass, error_log): """ Resolve the data type for a list of casts. """ for cast in klass.casts: _resolve_type(spec, klass.iface_file.module, klass, cast, error_log) if cast.type is ArgumentType.NONE: # The error has already been logged. pass elif cast.type is not ArgumentType.CLASS: error_log.log( "operator cast '{0}' must be to a class".format( klass.iface_file.fq_cpp_name)) def _add_default_copy_ctor(klass): """ Add a default copy ctor if required. """ # See if there is a private copy ctor in the hierarchy. for mro_klass in klass.mro: for ctor in mro_klass.ctors: # See if is a copy ctor. if len(ctor.py_signature.args) != 1: continue arg = ctor.py_signature.args[0] if len(arg.derefs) == 0 and arg.is_reference: if arg.type is ArgumentType.CLASS and arg.definition.iface_file is mro_klass.iface_file: break else: continue # If the copy ctor is private then the class can't be copied. if ctor.access_specifier is AccessSpecifier.PRIVATE: klass.cannot_copy = True return # If the ctor is in the class itself then there is nothing to do. if mro_klass is klass.mro[0]: return # Otherwise we need to create a default. break # Create a default public copy ctor. arg = Argument(ArgumentType.CLASS, definition=klass, is_reference=True, is_const=True, is_in=True) result = Argument(ArgumentType.VOID) signature = Signature(args=[arg], result=result) ctor = Constructor(AccessSpecifier.PUBLIC, py_signature=signature, cpp_signature=signature) if klass.deprecated: ctor.deprecated = True if not klass.is_abstract: klass.can_create = True # Append it to the list. klass.ctors.append(ctor) def _resolve_scope_overloads(spec, overloads, error_log, final_checks, scope=None): """ Resolve the data types for a scope's overloads. """ for overload in overloads: _resolve_func_types(spec, overload.common.module, scope, overload, error_log, final_checks) # Now check that the Python signature doesn't conflict with an earlier # one. If there is %MethodCode then assume that it will handle any # potential conflicts. if overload.method_code is None and spec.is_strict: for previous_overload in overloads: if previous_overload is overload: break if previous_overload.common is not overload.common: continue if previous_overload.method_code is not None: continue sig_state = _same_python_signature(spec, previous_overload.py_signature, overload.py_signature) if sig_state is None: # The error has already been logged. break if sig_state: _log_overload_error(error_log, "has overloaded functions with the same Python signature", overload, scope=scope) break if isinstance(scope, WrappedClass): if scope.deprecated: overload.deprecated = True if overload.is_abstract: scope.is_abstract = True def _resolve_variables(spec, mod, error_log): """ Resolve the data types for the variables of a module. """ for variable in spec.variables: if variable.module is mod: _resolve_variable_type(spec, variable, error_log) def _get_visible_py_members(spec, klass): """ Set the list of visible Python member functions for a class. """ for mro_klass in klass.mro: for member in mro_klass.members: # See if it is already in the list. This has the desired side # effect of eliminating any functions that have an implementation # closer to this class in the hierarchy. This is the only reason # to define private functions. for visible_member in klass.visible_members: if visible_member.member.py_name is member.py_name: break else: visible_member = VisibleMember(member, mro_klass) klass.visible_members.insert(0, visible_member) for overload in mro_klass.overloads: if overload.common is member: need_types = False # If the visible overload is abstract then it hasn't # had a concrete implementation so this class must also # be abstract. if overload.is_abstract: klass.is_abstract = True if klass.iface_file.module is spec.module and (klass is mro_klass or (overload.access_specifier is AccessSpecifier.PROTECTED and klass.has_shadow)): need_types = True member.py_name.used = True _iface_files_are_used_by_overload(spec, klass.iface_file.used, overload, need_types=need_types); def _get_virtuals(spec, klass, error_log): """ Get all the virtuals for a particular class. """ # Copy the collected virtuals of each super-class updating from what we # find in this class. for superklass in klass.superclasses: for virtual_overload in superklass.virtual_overloads: implicit = True for overload in klass.overloads: if virtual_overload.overload.cpp_name != overload.cpp_name: continue implicit = False if overload.is_final: break # See if it re-implements rather than hides. if _same_cpp_overload(spec, virtual_overload.overload, overload): # What if is is private? overload.is_virtual = True overload.is_virtual_reimplementation = True # Use the base implementation's virtual handler code if # there is any. We cannot just use its virtual handler # because this re-implementation may have different # annotations which means the complete handler would be # different. In practice there is no reason why it would # be different (and maybe this should be detected as an # error) but if they are the same then the same handler # will eventually be chosen. if overload.virtual_catcher_code is None: overload.virtual_catcher_code = virtual_overload.overload.virtual_catcher_code # Use the base implementation's virtual error handler if # one isn't explicitly specified. if overload.virtual_error_handler is None: overload.virtual_error_handler = virtual_overload.overload.virtual_error_handler _add_virtual_overload(spec, overload, klass, error_log) # Add it if it wasn't explicitly mentioned in the class. if implicit: _add_virtual_overload(spec, virtual_overload.overload, klass, error_log) # Handle any new virtuals. for overload in klass.overloads: if overload.is_virtual and not overload.is_virtual_reimplementation and not overload.is_final: _add_virtual_overload(spec, overload, klass, error_log) def _add_virtual_overload(spec, overload, klass, error_log): """ Add an overload to the list of virtuals for a class. """ # If this class is defined in the main module then make sure the virtuals # have a handler. if klass.iface_file.module is spec.module: virtual_handler = _get_virtual_handler(spec, overload, klass, error_log) # Make sure we get the name. overload.common.py_name.used = True # Make sure we have the interface files and type definitions for the # virtual handler. _iface_files_are_used_by_overload(spec, spec.module.used, overload, need_types=True) else: virtual_handler = None # Add it to the class. virtual_overload = VirtualOverload(overload, virtual_handler) klass.virtual_overloads.insert(0, virtual_overload) def _get_virtual_error_handler(spec, overload, klass, error_log): """ Get the virtual error handler for a function. """ # Handle the trivial case. if overload.no_virtual_error_handler: return None klass_mod = klass.iface_file.module # Check the function itself. handler_name = overload.virtual_error_handler if handler_name is None: # Check the class hierarchy. for mro_klass in klass.mro: handler_name = mro_klass.virtual_error_handler if handler_name is not None: break else: # Check the class's module. handler_name = klass_mod.default_virtual_error_handler if handler_name is None: # Check the module hierarchy. for mod in klass_mod.all_imports: handler_name = mod.default_virtual_error_handler if handler_name is not None: break else: return None # Find the handler with the name. for handler in spec.virtual_error_handlers: if handler.name == handler_name: break else: error_log.log( "unknown virtual error handler '{0}'".format(handler_name)) return None # Assign it an index if we need to import the handler. if klass_mod is not handler.module and handler.handler_nr < 0: handler.handler_nr = handler.module.nr_virtual_error_handlers handler.module.nr_virtual_error_handlers += 1 return handler def _get_virtual_handler(spec, overload, klass, error_log): """ Get the virtual handler for an overload. """ # See if there is an existing handler that is suitable. for handler in spec.virtual_handlers: if _check_virtual_handler(spec, overload, handler): return handler # Create a new one. handler = VirtualHandler(overload.cpp_signature, overload.py_signature, overload.virtual_catcher_code, _get_virtual_error_handler(spec, overload, klass, error_log)) handler.handler_nr = spec.nr_virtual_handlers spec.nr_virtual_handlers += 1 if overload.factory or overload.transfer is Transfer.TRANSFER_BACK: handler.transfer_result = True if overload.abort_on_exception: handler.abort_on_exception = True spec.virtual_handlers.insert(0, handler) return handler def _check_virtual_handler(spec, overload, virtual_handler): """ Return True if a virtual handler is appropriate for an overload. """ if overload.virtual_catcher_code is not virtual_handler.virtual_catcher_code: return False # If the overload has an explicit error handler then it must be the same as # the candidate. if overload.virtual_error_handler is not None: if virtual_handler.virtual_error_handler is None or overload.virtual_error_handler != virtual_handler.virtual_error_handler.name: return False if (overload.factory or overload.transfer is Transfer.TRANSFER_BACK) and not virtual_handler.transfer_result: return False if overload.abort_on_exception is not virtual_handler.abort_on_exception: return False if not same_argument_type(spec, overload.py_signature.result, virtual_handler.py_signature.result): return False if overload.py_signature.result.allow_none is not virtual_handler.py_signature.result.allow_none: return False if overload.py_signature.result.disallow_none is not virtual_handler.py_signature.result.disallow_none: return False if not same_signature(spec, overload.py_signature, virtual_handler.py_signature): return False # Take into account the argument directions in the Python signatures. for arg1, arg2 in zip(overload.py_signature.args, virtual_handler.py_signature.args): if arg1.is_in is not arg2.is_in: return False if arg1.is_out is not arg2.is_out: return False if overload.py_signature is overload.cpp_signature and virtual_handler.py_signature is virtual_handler.cpp_signature: return True if not same_argument_type(spec, overload.cpp_signature.result, virtual_handler.cpp_signature.result): return False return same_signature(spec, overload.cpp_signature, virtual_handler.cpp_signature) def _resolve_mapped_type_types(spec, mapped_type, error_log): """ Resolve the types of a mapped type based on a template. """ template_types = mapped_type.type.definition.types for template_type in template_types.args: # Leave templates as they are. if template_type.type is not ArgumentType.TEMPLATE: _resolve_type(spec, mapped_type.iface_file.module, None, template_type, error_log, allow_defined=True) # Make sure that the signature result won't cause problems. template_types.result = None _iface_files_are_used_by_signature(mapped_type.iface_file.used, template_types) def _resolve_ctor_types(spec, scope, ctor, error_log): """ Resolve the types of a ctor. """ # Handle any exceptions. _set_needed_exceptions(spec, scope.iface_file.module, ctor.throw_args) # Handle any C++ signature. if ctor.cpp_signature is not None and ctor.cpp_signature is not ctor.py_signature: for arg in ctor.cpp_signature.args: _resolve_type(spec, scope.iface_file.module, scope, arg, error_log, allow_defined=True) # Handle the Python signature. for arg_nr, arg in enumerate(ctor.py_signature.args): _resolve_type(spec, scope.iface_file.module, scope, arg, error_log) if arg.type is ArgumentType.NONE: # The error has already been logged. continue if not _supported_type(scope, None, arg, error_log): error_log.log( "argument {0} of ctor '{1}' has an unsupported type for a Python signature - provide a valid type, %MethodCode and a C++ signature".format( arg_nr + 1, scope.iface_file.fq_cpp_name)) continue _iface_file_is_used(scope.iface_file.used, arg) _scope_default_value(spec, scope, arg) def _resolve_func_types(spec, mod, scope, overload, error_log, final_checks): """ Resolve the types of a function. """ # Handle any exceptions. _set_needed_exceptions(spec, mod, overload.throw_args) # Handle any C++ signature. if overload.cpp_signature is not overload.py_signature: result = overload.cpp_signature.result _resolve_type(spec, mod, scope, result, error_log, allow_defined=True) if (result.type is not ArgumentType.VOID or len(result.derefs) != 0) and overload.is_virtual and not _supported_type(scope, overload, result, error_log) and overload.virtual_catcher_code is None: _log_overload_error(error_log, "has an unsupported virtual function return type - provide %VirtualCatcherCode", overload, scope=scope) for arg in overload.cpp_signature.args: _resolve_type(spec, mod, scope, arg, error_log, allow_defined=True) # Handle the Python signature. _resolve_py_signature_types(spec, mod, scope, overload, error_log, final_checks) result = overload.py_signature.result # These slots must return Py_ssize_t. if is_ssize_return_slot(overload.common.py_slot): if spec.abi_version >= (13, 0): required_types = (ArgumentType.SSIZE, ) else: required_types = (ArgumentType.SSIZE, ArgumentType.INT) if result.type not in required_types or len(result.derefs) != 0 or result.is_reference or result.is_const: _log_overload_error(error_log, "must return a Py_ssize_t", overload, scope=scope) # These slots must return int. if is_int_return_slot(overload.common.py_slot): if result.type is not ArgumentType.INT or len(result.derefs) != 0 or result.is_reference or result.is_const: _log_overload_error(error_log, "must return an int", overload, scope=scope) # These slots must return void. if is_void_return_slot(overload.common.py_slot): if result.type is not ArgumentType.VOID or len(result.derefs) != 0 or result.is_reference or result.is_const: _log_overload_error(error_log, "must return void", overload, scope=scope) # These slots must return Py_hash_t. if is_hash_return_slot(overload.common.py_slot): if spec.abi_version >= (13, 0): required_type = ArgumentType.HASH required_type_name = 'Py_hash_t' else: required_type = ArgumentType.LONG required_type_name = 'long' if result.type is not required_type or len(result.derefs) != 0 or result.is_reference or result.is_const: _log_overload_error(error_log, "must return a {0}".format(required_type_name), overload, scope=scope) def _resolve_py_signature_types(spec, mod, scope, overload, error_log, final_checks): """ Resolve the types of a Python signature. """ result = overload.py_signature.result if result.type is not ArgumentType.VOID or len(result.derefs) != 0: if overload.pyqt_method_specifier is PyQtMethodSpecifier.SIGNAL: _log_overload_error(error_log, "is a signal and must return void", overload, scope=scope) _resolve_type(spec, mod, scope, result, error_log) # Results must be simple. if result.type is ArgumentType.NONE: # The error has already been logged. pass elif not _supported_type(scope, overload, result, error_log): if overload.cpp_signature is overload.py_signature or overload.method_code is None: _log_overload_error(error_log, "has an unsupported return type - provide %MethodCode and a {0} signature".format( 'C' if spec.c_bindings else 'C++'), overload, scope=scope) for arg_nr, arg in enumerate(overload.py_signature.args): _resolve_type(spec, mod, scope, arg, error_log) if arg.type is ArgumentType.NONE: continue # Note signal arguments are restricted in their types because we don't # (yet) support handwritten code for them. if overload.pyqt_method_specifier is PyQtMethodSpecifier.SIGNAL: if not _supported_type(scope, overload, arg, error_log): _log_overload_error(error_log, "argument {0} has an unsupported type for a Python signature".format( arg_nr + 1), overload, scope=scope) elif not _supported_type(scope, overload, arg, error_log, outputs=True): if overload.is_virtual: _log_overload_error(error_log, "argument {0} has an unsupported type for a Python signature - provide a valid type, %MethodCode, %VirtualCatcherCode and a C++ signature".format( arg_nr + 1), overload, scope=scope) _log_overload_error(error_log, "argument {0} has an unsupported type for a Python signature - provide a valid type, %MethodCode and a C++ signature".format( arg_nr + 1), overload, scope=scope) # Check that the argument support /Array/. if arg.type in (ArgumentType.CLASS, ArgumentType.MAPPED) and arg.array is ArrayArgument.ARRAY: # This is delayed because the class must be complete. Be careful # not to pass any non-local changing variables. local_arg_nr = arg_nr final_checks.append( lambda: _check_array_support(overload, local_arg_nr, scope, error_log)) if scope is not None: _scope_default_value(spec, scope, arg) def _check_array_support(overload, arg_nr, scope, error_log): """ Check that an argument supports /Array/. """ arg = overload.py_signature.args[arg_nr] if len(arg.derefs) != 1 or not arg.is_in or arg.is_reference: _log_overload_error(error_log, "argument {0} is a mapped type or class with /Array/ and so must be a pointer".format( arg_nr + 1), overload, scope=scope) if arg.type is ArgumentType.MAPPED and arg.definition.no_release: _log_overload_error(error_log, "argument {0} is a mapped type that does not support /Array/ because /NoRelease/ has been specified for it".format( arg_nr + 1), overload, scope=scope) if arg.type is ArgumentType.CLASS and not arg.definition.needs_array_helper: _log_overload_error(error_log, "argument {0} is a class that does not support /Array/".format( arg_nr + 1), overload, scope=scope) # Various type classifications. _CLASS_TYPES = (ArgumentType.MAPPED, ArgumentType.CLASS) _SIMPLE_TYPES = (ArgumentType.CFLOAT, ArgumentType.FLOAT, ArgumentType.CDOUBLE, ArgumentType.DOUBLE, ArgumentType.ENUM, ArgumentType.BOOL, ArgumentType.CBOOL, ArgumentType.BYTE, ArgumentType.SBYTE, ArgumentType.UBYTE, ArgumentType.USHORT, ArgumentType.SHORT, ArgumentType.UINT, ArgumentType.CINT, ArgumentType.INT, ArgumentType.ULONG, ArgumentType.LONG, ArgumentType.ULONGLONG, ArgumentType.LONGLONG, ArgumentType.HASH, ArgumentType.SSIZE, ArgumentType.SIZE, ArgumentType.PYOBJECT, ArgumentType.PYTUPLE, ArgumentType.PYLIST, ArgumentType.PYDICT, ArgumentType.PYCALLABLE, ArgumentType.PYSLICE, ArgumentType.PYTYPE, ArgumentType.PYBUFFER, ArgumentType.PYENUM, ArgumentType.CAPSULE) _POINTER_TYPES = (ArgumentType.STRUCT, ArgumentType.UNION, ArgumentType.VOID) _STRING_TYPES = (ArgumentType.ASCII_STRING, ArgumentType.LATIN1_STRING, ArgumentType.UTF8_STRING, ArgumentType.SSTRING, ArgumentType.USTRING, ArgumentType.STRING, ArgumentType.WSTRING) def _resolve_variable_type(spec, variable, error_log): """ Resolve the type of a variable. """ bad_type = True variable_type = variable.type _resolve_type(spec, variable.module, variable.scope, variable_type, error_log) if variable_type is ArgumentType.NONE: return if variable_type.type in _CLASS_TYPES: # Class, Class & and Class * are supported. if len(variable_type.derefs) <= 1: bad_type = False elif variable_type.type in _STRING_TYPES: # (signed/unsigned) char, (signed/unsigned) char *, wchar_t, wchar_t * # are supported. if not variable_type.is_reference and len(variable_type.derefs) <= 1: bad_type = False elif variable_type.type in _SIMPLE_TYPES: # These are supported without pointers or references. if not variable_type.is_reference and len(variable_type.derefs) == 0: bad_type = False elif variable_type.type in _POINTER_TYPES: # A simple pointer is supported. if not variable_type.is_reference and len(variable_type.derefs) == 1: bad_type = False if bad_type and (variable.get_code is None or (not variable.no_setter and variable.set_code is None)): if variable.no_setter: set_s = '' else: set_s = " and %SetCode" error_log.log( "'{0}' has an unsupported type - provide %GetCode{1}".format( variable.fq_cpp_name, set_s)) if variable_type.type is not ArgumentType.CLASS and variable.access_code is not None: error_log.log( "'{0}' has %AccessCode but isn't a class instance".format( variable.fq_cpp_name)) if variable.scope is not None: _iface_file_is_used(variable.scope.iface_file.used, variable_type) else: _iface_file_is_used(variable.module.used, variable_type) # Scoped variables need a handler unless they have %AccessCode. if variable.access_code is None: if variable.scope is not None and not variable.scope.is_hidden_namespace: variable.needs_handler = True variable.scope.has_variable_handlers = True def _supported_type(klass, overload, arg, error_log, outputs=False): """ See if a type is supported by the generated code. """ if arg.type in _CLASS_TYPES: if arg.is_reference: if len(arg.derefs) == 0: _default_input(arg) return True if len(arg.derefs) == 1 and outputs: _default_output(arg) return True elif len(arg.derefs) == 0: _ensure_input(klass, overload, arg, error_log) return True elif len(arg.derefs) == 1: if outputs: _default_input(arg) else: _ensure_input(klass, overload, arg, error_log) return True elif len(arg.derefs) == 2 and outputs: _default_output(arg) return True elif arg.type in _STRING_TYPES: if arg.is_reference: if len(arg.derefs) <= 1 and outputs: _default_output(arg) return True elif len(arg.derefs) == 0: _ensure_input(klass, overload, arg, error_log) return True elif len(arg.derefs) == 1: if outputs: _default_input(arg) else: _ensure_input(klass, overload, arg, error_log) return True elif len(arg.derefs) == 2 and outputs: _default_output(arg) return True elif arg.type in _SIMPLE_TYPES: if arg.is_reference: if arg.is_const: _ensure_input(klass, overload, arg, error_log) return True if len(arg.derefs) == 0 and outputs: _default_output(arg) return True elif len(arg.derefs) == 0: _ensure_input(klass, overload, arg, error_log) return True elif len(arg.derefs) == 1 and outputs: _default_output(arg) return True elif arg.type in _POINTER_TYPES: if arg.is_reference: if len(arg.derefs) == 1 and outputs: _default_output(arg) return True elif len(arg.derefs) == 1: _ensure_input(klass, overload, arg, error_log) return True elif len(arg.derefs) == 2 and outputs: _default_output(arg) return True elif arg.type is ArgumentType.ELLIPSIS: # This can only appear in argument lists without * or &. _ensure_input(klass, overload, arg, error_log) return True # Unsupported if we got this far. return False def _ensure_input(klass, overload, arg, error_log): """ Ensure the direction of an argument is an input. """ if arg.is_out: _log_overload_error(error_log, "has an invalid argument type for /Out/", overload, scope=klass) arg.is_in = True def _default_input(arg): """ Default the direction of an argument to an input. """ if not arg.is_out: arg.is_in = True def _default_output(arg): """ Default the direction of an argument to an output unless the argument is const. """ if not arg.is_out and not arg.is_in: if arg.is_const: arg.is_in = True else: arg.is_out = True def _same_cpp_overload(spec, overload1, overload2): """ Compare two overloads and return True if they are the same. """ # They must both be const, or both not. if overload1.is_const is not overload2.is_const: return False return same_signature(spec, overload1.cpp_signature, overload2.cpp_signature) def _same_python_signature(spec, signature1, signature2): """ See if two Python signatures are the same as far as Python is concerned. Return None if any argument's type is unknown. """ a1 = a2 = -1 while True: a1, arg1 = _next_significant_arg(signature1, a1) a2, arg2 = _next_significant_arg(signature2, a2) if arg1 is not None and arg1.type is ArgumentType.NONE: return None if arg2 is not None and arg2.type is ArgumentType.NONE: return None if a1 < 0 or a2 < 0: break if not same_argument_type(spec, arg1, arg2, strict=False): return False return a1 < 0 and a2 < 0 def _next_significant_arg(signature, a): """ Return the next significant argument from a Python signature (ie. one that is not optional or an output only argument. Return -1 if there isn't one. """ a += 1 while a < len(signature.args): arg = signature.args[a] if arg.default_value is not None: break if arg.type is ArgumentType.NONE or arg.is_in: return a, arg a += 1 return -1, None def _scope_default_value(spec, klass, arg): """ Add an explicit scope to the default value of an argument if possible. """ # We do a quick check to see if we need to do anything. This means we can # limit the times we need to copy the default value. It needs to be copied # because it will be shared by class versions that have been created on the # fly and it may need to be scoped differently for each of those versions. # Note that, as we no longer support API versions, this comment may no # longer apply. if arg.default_value is None: return for value in arg.default_value: if value.value_type is ValueType.SCOPED and value.value.is_simple: break else: return # It's not certain that we will do anything, but we assume we will and # start copying. new_default_value = [] for value in arg.default_value: # Make the copy. new_value = copy(value) new_default_value.append(new_value) # Skip this part of the expression if it isn't a named value or it # already has a scope. if value.value_type is not ValueType.SCOPED or not value.value.is_simple: continue # Search the class hierarchy for an enum member with the same name. If # we don't find one, leave it as it is (the compiler will find out if # this is a problem). original_name = value.value.base_name for mro_klass in klass.mro: for enum in spec.enums: if enum.scope is not mro_klass: continue for enum_member in enum.members: if enum_member.cpp_name == original_name: # Take the scope from the class that the enum was # defined in. new_name = ScopedName(mro_klass.iface_file.fq_cpp_name) new_name.append(original_name) new_value.value = new_name # Nothing more to do. break else: continue break else: continue break arg.default_value = new_default_value def _resolve_type(spec, mod, scope, type, error_log, allow_defined=False): """ Resolve a type if possible. """ # Loop until we've got to a base type. while type.type is ArgumentType.DEFINED: scoped_name = type.definition type.type = ArgumentType.NONE # Search the local scopes unless what we are looking for has an # explicit global scope. if not scoped_name.is_absolute: klass_scope = scope while klass_scope is not None: if klass_scope.iface_file.type is IfaceFileType.CLASS: _search_class_scope(spec, klass_scope, scoped_name, type) else: _search_scope(spec, klass_scope, scoped_name, type) if type.type is not ArgumentType.NONE: break klass_scope = klass_scope.scope if type.type is not ArgumentType.NONE: break # We now need an absolute name. scoped_name = scoped_name.absolute _name_lookup(spec, mod, scoped_name, type) if type.type is ArgumentType.NONE: if allow_defined: type.type = ArgumentType.DEFINED else: error_log.log("'{0}' is undefined".format(scoped_name), source_location=type.source_location) return # See if the type refers to an instantiated template. _resolve_instantiated_class_template(spec, type) # Replace the base type if it has been mapped. if type.type in (ArgumentType.STRUCT, ArgumentType.UNION, ArgumentType.TEMPLATE): _search_mapped_types(spec, mod, type) # If we still have a template then see if we need to automatically # instantiate it. if type.type is ArgumentType.TEMPLATE: for mapped_type_template in spec.mapped_type_templates: if mapped_type_template.mapped_type.type.definition.cpp_name == type.definition.cpp_name and same_template_signature(mapped_type_template.mapped_type.type.definition.types, type.definition.types, deep=True): _instantiate_mapped_type_template(spec, mod, mapped_type_template, type, error_log) break # If we are in the main module then mark any generated types as being # needed. if mod is spec.module: _set_needed_type(type) def _set_needed_type(arg): """ Specify that a generated type is needed. """ if arg.type in (ArgumentType.CLASS, ArgumentType.MAPPED): arg.definition.iface_file.needed = True elif arg.type is ArgumentType.ENUM: arg.definition.needed = True def _set_needed_exceptions(spec, mod, throw_args): """ Specify that a set of thrown arguments are needed. """ if mod is spec.module and throw_args is not None and throw_args.arguments is not None: for exception in throw_args.arguments: _set_needs_exception(exception) def _set_needs_exception(exception): """ Specify that an exception is needed. """ if exception.class_exception is not None: exception.class_exception.iface_file.needed = True else: exception.needed = True def _resolve_instantiated_class_template(spec, type): """ If the type corresponds to a previously instantiated class template then replace it with the class that was created. """ if type.type is not ArgumentType.TEMPLATE: return template = type.definition template_signature = template.types for arg in template_signature.args: _resolve_instantiated_class_template(spec, arg) for klass in spec.classes: if klass.template is not None and klass.template.cpp_name == template.cpp_name and same_signature(spec, klass.template.types, template_signature): type.type = ArgumentType.CLASS type.definition = klass break def _instantiate_mapped_type_template(spec, mod, mapped_type_template, type, error_log): """ Instantiate a mapped type template. """ expansions = template_expansions( mapped_type_template.mapped_type.type.definition.types, type.definition.types, declared_names=mapped_type_template.signature) iface_file = find_iface_file(spec, mod, encoded_template_name(type.definition), IfaceFileType.MAPPED_TYPE, error_log.log, cpp_type=type) iface_file.module = mod mapped_type = MappedType(iface_file, copy(type)) mapped_type.type.derefs = [] mapped_type.type.is_const = False mapped_type.type.is_reference = False mapped_type.cpp_name = cached_name(spec, argument_as_str(mapped_type.type)) if mod is spec.module: mapped_type.cpp_name.used = True proto_mapped_type = mapped_type_template.mapped_type mapped_type.handles_none = proto_mapped_type.handles_none mapped_type.needs_user_state = proto_mapped_type.needs_user_state mapped_type.no_assignment_operator = proto_mapped_type.no_assignment_operator mapped_type.no_copy_ctor = proto_mapped_type.no_copy_ctor mapped_type.no_default_ctor = proto_mapped_type.no_default_ctor mapped_type.no_release = proto_mapped_type.no_release mapped_type.pyqt_flags = proto_mapped_type.pyqt_flags if proto_mapped_type.type_hints is not None: mapped_type.type_hints = instantiate_type_hints(spec, proto_mapped_type.type_hints, expansions) used = mapped_type.iface_file.used mapped_type.iface_file.type_header_code = template_code_blocks(spec, used, proto_mapped_type.iface_file.type_header_code, expansions) if proto_mapped_type.convert_from_type_code is not None: mapped_type.convert_from_type_code = template_code(spec, used, proto_mapped_type.convert_from_type_code, expansions) if proto_mapped_type.convert_to_type_code is not None: mapped_type.convert_to_type_code = template_code(spec, used, proto_mapped_type.convert_to_type_code, expansions) if proto_mapped_type.release_code is not None: mapped_type.release_code = template_code(spec, used, proto_mapped_type.release_code, expansions) spec.mapped_types.insert(0, mapped_type) _replace_template_type(mapped_type, type) def _search_class_scope(spec, scope, scoped_name, type): """ Search for a name in a class scope and resolve the corresponding type. """ for mro_klass in scope.mro: _search_scope(spec, mro_klass, scoped_name, type) if type.type is not ArgumentType.NONE: break def _search_scope(spec, scope, scoped_name, type): """ Search for a name in a scope and resolve the corresponding type. """ # Prepend the scope and see if it exists. fq_name = ScopedName(scoped_name) fq_name.prepend(scope.iface_file.fq_cpp_name) _name_lookup(spec, scope.iface_file.module, fq_name, type) def _name_lookup(spec, mod, scoped_name, type): """ Look up a name and resole the corresponding type. """ _search_mapped_types(spec, mod, type, scoped_name) if type.type is not ArgumentType.NONE: return search_typedefs(spec, scoped_name, type) if type.type is not ArgumentType.NONE: return _search_enums(spec, scoped_name, type) if type.type is not ArgumentType.NONE: return _search_classes(spec, mod, scoped_name, type) def _search_mapped_types(spec, mod, type, scoped_name=None): """ Search the mapped types for a name and resolve the type. """ # Patch back to defined types so we can use same_base_type(). if scoped_name is not None: orig_name = type.definition type.definition = scoped_name type.type = ArgumentType.DEFINED for mapped_type in spec.mapped_types: if same_base_type(mapped_type.type, type): break else: # Restore because we didn't find anything. if scoped_name is not None: type.definition = orig_name type.type = ArgumentType.NONE return _replace_template_type(mapped_type, type) def _replace_template_type(mapped_type, type): """ If a mapped type is based on a template then update the type with a copy that keeps the original types of the template arguments. """ if mapped_type.type.type is ArgumentType.TEMPLATE: dst_types = None for arg_nr, arg in enumerate(type.definition.types.args): if arg.original_typedef is None: continue # Create an appropriately deep copy now that we know it is needed # and if it hasn't already been done. if dst_types is None: mapped_type = copy(mapped_type) mapped_type.type = copy(mapped_type.type) mapped_type.type.definition = copy(mapped_type.type.definition) mapped_type.type.definition.types = copy(mapped_type.type.definition.types) mapped_type.type.definition.types.args = [copy(arg) for arg in mapped_type.type.definition.types.args] dst_types = mapped_type.type.definition.types dst_types.args[arg_nr].original_typedef = arg.original_typedef # Replace the template with the mapped type. type.type = ArgumentType.MAPPED type.definition = mapped_type type.type_hints = mapped_type.type_hints def _merged_type_hints(type_hints, defaults): """ Return a TypeHints object that is the merge of another and some defaults. """ if type_hints is None: return defaults if defaults is None: return type_hints if type_hints.hint_in is None: type_hints.hint_in = defaults.hint_in if type_hints.hint_out is None: type_hints.hint_out = defaults.hint_out if type_hints.default_value is None: type_hints.default_value = defaults.default_value return type_hints def _search_enums(spec, scoped_name, type): """ Search the enums for a name and resolve the type. """ for enum in spec.enums: if enum.fq_cpp_name is None: continue if enum.fq_cpp_name == scoped_name: type.type = ArgumentType.ENUM type.definition = enum break def _search_classes(spec, mod, scoped_name, type): """ Search the classes for one with a particular name and resolve it as a type. """ for klass in spec.classes: # Ignore an external class unless it was declared in the same module as # the name is being used. if klass.external and klass.iface_file.module is not mod: continue if klass.iface_file.fq_cpp_name == scoped_name: type.type = ArgumentType.CLASS type.definition = klass type.type_hints = _merged_type_hints(type.type_hints, klass.type_hints) break def _iface_files_are_used_by_signature(used, signature, need_types=False): """ Make sure all interface files for a signature are used. """ if signature.result is not None: _iface_file_is_used(used, signature.result, need_types=need_types) for arg in signature.args: _iface_file_is_used(used, arg, need_types=need_types) def _iface_files_are_used_by_overload(spec, used, overload, need_types=False): """ Make sure all interface files for an overload are used. """ _iface_files_are_used_by_signature(used, overload.py_signature, need_types=need_types) if overload.cpp_signature is not overload.py_signature: _iface_files_are_used_by_signature(used, overload.cpp_signature, need_types=need_types) # Don't bother with %TypeHeaderCode from %Exception for later ABI versions. if spec.abi_version >= (13, 1) or (spec.abi_version >= (12, 9) and spec.abi_version < (13, 0)): return throw_args = overload.throw_args if throw_args is not None and throw_args.arguments is not None: for exception in throw_args.arguments: append_iface_file(used, exception.iface_file) if need_types: _set_needs_exception(exception) def _iface_file_is_used(used, arg, need_types=False): """ If a type has an interface file then add it to the the given list of used interface files so that the header file is #included in the generated code. """ if arg.type in (ArgumentType.CLASS, ArgumentType.MAPPED): iface_file = arg.definition.iface_file elif arg.type is ArgumentType.ENUM: iface_file = _get_iface_file_for_enum(arg.definition) else: iface_file = None if iface_file is not None: append_iface_file(used, iface_file) # For mapped type templates we also need the template arguments. These # will be in the mapped type's used list (which itself will be empty # for non-template mapped types). if arg.type is ArgumentType.MAPPED: for used_iface_file in iface_file.used: append_iface_file(used, used_iface_file) if need_types: _set_needed_type(arg) def _enum_iface_file_is_used(enum, mod): """ Add an enum's interface file to that used by a module. """ enum_iface_file = _get_iface_file_for_enum(enum) if enum_iface_file is not None: append_iface_file(mod.used, enum_iface_file) def _get_iface_file_for_enum(enum): """ Return the interface file for an enum, or None if it doesn't have one. """ if enum.fq_cpp_name is not None: if enum.scope is not None: return enum.scope.iface_file return None def _create_sorted_numbered_types(spec, mod, error_log): """ Create the sorted list of numbered types for a module. For the main module this will be every type defined in the module. For other modules this will be every type needed by the main module. """ # Collect the needed types. for klass in spec.classes: if klass.iface_file.module is not mod: continue if mod is spec.module or klass.iface_file.needed: if not klass.is_hidden_namespace: mod.needed_types.append(Argument(ArgumentType.CLASS, definition=klass, name=klass.iface_file.cpp_name)) for mapped_type in spec.mapped_types: if mapped_type.iface_file.module is not mod: continue if mod is spec.module or mapped_type.iface_file.needed: mod.needed_types.append(Argument(ArgumentType.MAPPED, definition=mapped_type, name=mapped_type.cpp_name)) for enum in spec.enums: if enum.module is not mod: continue if enum.fq_cpp_name is None: continue if mod is spec.module or enum.needed: mod.needed_types.append(Argument(ArgumentType.ENUM, definition=enum, name=enum.cached_fq_cpp_name)) # Sort the list and assign type numbers. mod.needed_types.sort(key=lambda t: t.name.name) needed_type_nr = 0 for needed_type in mod.needed_types: if needed_type.type is ArgumentType.CLASS: needed_type.definition.iface_file.type_nr = needed_type_nr # If we find a class called QObject, assume it's Qt. if needed_type.name.name == 'QObject': if spec.pyqt_qobject is not None: error_log.log( "class 'QObject' has been defined more than once") spec.pyqt_qobject = needed_type.definition elif needed_type.type is ArgumentType.MAPPED: needed_type.definition.iface_file.type_nr = needed_type_nr elif needed_type.type is ArgumentType.ENUM: needed_type.definition.type_nr = needed_type_nr needed_type_nr += 1 def _check_properties(klass, error_log): """ Check that any properties are valid. """ for prop in klass.properties: if find_method(klass, prop.getter) is None: error_log.log( "property '{0}.{1}' has no getter '{3}'".format( klass.py_name.name, prop.name.name, prop.getter)) if prop.setter is not None and find_method(klass, prop.setter) is None: error_log.log( "property '{0}.{1}' has no setter '{3}'".format( klass.py_name.name, prop.name.name, prop.setter)) def _log_overload_error(error_log, text, overload, scope=None): """ Log an error about an overload. """ if scope is None: fq_cpp_name = overload.cpp_name else: fq_cpp_name = f'{scope.iface_file.fq_cpp_name}::{overload.cpp_name}' error_log.log(f"'{fq_cpp_name}' {text}", source_location=overload.source_location) sip-6.8.6/sipbuild/generator/scoped_name.py000066400000000000000000000132771464421045000207700ustar00rootroot00000000000000# SPDX-License-Identifier: BSD-2-Clause # Copyright (c) 2024 Phil Thompson # Specify how scopes should be stripped. Any other value is the number of # scopes to strip. STRIP_NONE = 0 # Don't strip any scopes. STRIP_GLOBAL = -1 # Strip the global scope. class ScopedName: """ Encapsulate a scoped name. """ def __init__(self, name): """ Initialise the scoped name. """ if isinstance(name, list): # This use is internal to this class. self._name = name elif isinstance(name, ScopedName): # Make a copy of another ScopedName object. self._name = list(name._name) else: # Create a simple name. self._name = [name] def __eq__(self, other): """ Compare with another scoped name for equality. """ if isinstance(other, ScopedName): return self._name == other._name return NotImplemented def __delitem__(self, index): """ Remove the requested name. """ del self._name[self._normalised_index(index)] def __getitem__(self, index): """ Get the requested name. """ return self._name[self._normalised_index(index)] def __len__(self): """ Return the length of the name, ie. the number of individual names. """ nr_names = len(self._name) if self.is_absolute: nr_names -= 1 return nr_names def __lt__(self, other): """ Compare with another scoped name to allow lists to be sorted. """ if isinstance(other, ScopedName): return self._name < other._name return NotImplemented def __setitem__(self, index, name): """ Set the requested name. """ self._name[self._normalised_index(index)] = name def __str__(self): """ Return the C++ string representation. """ # The special treatment is a hack to simplify how the result is used. # We ignore any leading '::' and truncate the conversion if we think we # are converting an encoded template name (ie. we find a word that # starts with a digit). s_l = [] for s in self: if s[0].isdigit(): break s_l.append(s) return '::'.join(s_l) @property def absolute(self): """ The absolute version of the name. """ if self.is_absolute: return self copy = type(self)(self) copy.make_absolute() return copy def append(self, name): """ Append a simple name. """ self._name.append(name) @property def as_cpp(self): """ The C++ representation of the name. """ return '::'.join(self._name) @property def as_py(self): """ The Python representation of the name. """ start = 1 if self.is_absolute else 0 return '.'.join(self._name[start:]) @property def as_word(self): """ The word representation of the name. """ start = 1 if self.is_absolute else 0 return '_'.join(self._name[start:]) @property def base_name(self): """ The base name of the scoped name. """ return self._name[-1] def cpp_stripped(self, strip): """ Return the C++ representation of the name with leading scopes stripped. """ if strip == STRIP_NONE: start = 0 else: start = 1 if self.is_absolute else 0 if strip != STRIP_GLOBAL: start += strip # Never strip the base name. if start >= len(self._name): return self._name[-1] return '::'.join(self._name[start:]) @property def is_absolute(self): """ True if the scoped name is absolute. """ return self._name[0] == '' @property def is_simple(self): """ Return True if the name is simple, ie. relative and unscoped. """ return len(self) == 1 def make_absolute(self): """ Make sure the scoped name is absolute. """ if self._name[0] != '': self._name.insert(0, '') def matches(self, scoped_name, scope=None): """ Return True if a scoped name matches this taking account of an optional scope if the scoped name is relative. """ assert self.is_absolute # Check for the simple case. if scoped_name.is_absolute: return self == scoped_name # Try each scope, inner to outer. while scope is not None: fq_name = ScopedName(scope.iface_file.fq_cpp_name) fq_name._name.extend(scoped_name._name) if self == fq_name: return True scope = scope.scope # Treat the name as if it were absolute. return self._name[1:] == scoped_name._name @classmethod def parse(cls, raw): """ Return a ScopedName object by parsing a raw string. """ return cls(raw.split('::')) def prepend(self, scoped_name): """ Prepend a scoped name. """ new_name = list(scoped_name._name) new_name.extend(self._name) self._name = new_name @property def scope(self): """ The scoped name that is the enclosing scope of this one. It will be None if there isn't one. """ if len(self) == 1: return None return type(self)(self._name[:-1]) def _normalised_index(self, index): """ Return a normalised index. """ if not isinstance(index, int): raise TypeError("'{}' is an invalid index".format(type(index))) if index >= 0 and self.is_absolute: index += 1 return index sip-6.8.6/sipbuild/generator/specification.py000066400000000000000000001232561464421045000213320ustar00rootroot00000000000000# SPDX-License-Identifier: BSD-2-Clause # Copyright (c) 2024 Phil Thompson from dataclasses import dataclass, field from enum import auto, Enum from typing import Any, Dict, List, Optional, Union from .scoped_name import ScopedName class AccessSpecifier(Enum): """ The class access specifiers. """ # Private access. PRIVATE = auto() # Protected access. PROTECTED = auto() # Public access. PUBLIC = auto() class ArgumentType(Enum): """ The types of either C/C++ or Python arguments. The numerical values of these can occur in generated code so so types must always be appended and old (unused) types must never be removed. """ # The type hasn't been specified. NONE = 0 # A user defined type. DEFINED = 1 # A class. CLASS = 2 # A struct. STRUCT = 3 # A void. VOID = 4 # An enum. ENUM = 5 # A template. TEMPLATE = 6 # No longer used. SIGNAL_UNUSED = 7 # No longer used. SLOT_UNUSED = 8 # No longer used. RXCON_UNUSED = 9 # No longer used. RXDIS_UNUSED = 10 # No longer used. SLOTCON_UNUSED = 11 # No longer used. SLOTDIS_UNUSED = 12 # An unsigned char. USTRING = 13 # A char. STRING = 14 # A short. SHORT = 15 # An unsigned short. USHORT = 16 # A constrained int. CINT = 17 # An int. INT = 18 # An unsigned int. UINT = 19 # A long. LONG = 20 # An unsigned long. ULONG = 21 # A float. FLOAT = 22 # A constrained float. CFLOAT = 23 # A double. DOUBLE = 24 # A constrained double. CDOUBLE = 25 # A bool. BOOL = 26 # A mapped type. MAPPED = 27 # A Python object. PYOBJECT = 28 # A Python tuple. PYTUPLE = 29 # A Python list. PYLIST = 30 # A Python dict. PYDICT = 31 # A Python callable. PYCALLABLE = 32 # A Python slice. PYSLICE = 33 # No longer used. QOBJECT_UNUSED = 34 # A function. FUNCTION = 35 # A Python type. PYTYPE = 36 # An ellipsis. ELLIPSIS = 37 # A long long. LONGLONG = 38 # An unsigned long long. ULONGLONG = 39 # No longer used. ANYSLOT_UNUSED = 40 # A constrained bool. CBOOL = 41 # A signed char. SSTRING = 42 # A wchar_t. WSTRING = 43 # A temporary void *. FAKE_VOID = 44 # A Py_ssize_t. SSIZE = 45 # An ASCII encoded string. ASCII_STRING = 46 # A Latin-1 encoded string. LATIN1_STRING = 47 # A UTF-8 encoded string. UTF8_STRING = 48 # A char used as an int. BYTE = 49 # A signed char used as an int. SBYTE = 50 # An unsigned char used as an unsigned int. UBYTE = 51 # A Python capsule. CAPSULE = 52 # A Python object that implements the buffer protocol. PYBUFFER = 53 # A size_t. SIZE = 54 # A Python enum. PYENUM = 55 # A union. UNION = 56 # A Py_hash_t. HASH = 57 class ArrayArgument(Enum): """ The array support provided by an argument. """ # /Array/ was specified. ARRAY = auto() # /ArraySize/ was specified. ARRAY_SIZE = auto() # The argument provides no array support. NONE = auto() class ClassKey(Enum): """ The key that identifies a particular type of class. """ # A class. CLASS = auto() # A struct. STRUCT = auto() # A union. UNION = auto() class DocstringFormat(Enum): """ The formatting applied to the text of the docstring. """ # Any leading spaces common to all non-blank lines in the docstring are # removed. DEINDENTED = auto() # The docstring is used as it is specified in the .sip file. RAW = auto() class DocstringSignature(Enum): """ The position of the automatically generated function or method signature relative to the docstring text. In the context of a class's docstring then it applies to all the class's ctors. """ # The signature is appended to the docstring. APPENDED = auto() # The signature is discard. DISCARDED = auto() # The signature is prepended to the docstring. PREPENDED = auto() class EnumBaseType(Enum): """ The different base types fo an enum. """ # enum.Enum ENUM = auto() # enum.Flag FLAG = auto() # enum.IntEnum INT_ENUM = auto() # enum.IntFlag INT_FLAG = auto() # enum.IntEnum with unsigned values. UINT_ENUM = auto() class GILAction(Enum): """ The action to take with the GIL when calling C/C++ code. """ # The default action. DEFAULT = auto() # Hold the GIL. HOLD = auto() # Release the GIL. RELEASE = auto() class IfaceFileType(Enum): """ The type of an interface file. """ # A class. CLASS = auto() # An %Exception. EXCEPTION = auto() # A %MappedType. MAPPED_TYPE = auto() # A namespace. NAMESPACE = auto() class KwArgs(Enum): """ The level of support for passing argument as keyword arguments. """ # All named arguments can be passed as keyword arguments. ALL = auto() # Keyword arguments are not supported. NONE = auto() # All named optional arguments (ie. those with a default value) can be # passed as keyword arguments. OPTIONAL = auto() class PyQtMethodSpecifier(Enum): """ The PyQt-specific method specifier. """ # A signal. SIGNAL = auto() # A slot. SLOT = auto() class PySlot(Enum): """ The Python slots corresponding to entries in a type object. """ # tp_str STR = auto() # tp_as_number.nb_int INT = auto() # tp_as_number.nb_float FLOAT = auto() # tp.as_mapping.mp_length and tp.as_sequence.sq_length LEN = auto() # tp.as_sequence.sq_contains CONTAINS = auto() # tp_as_number.nb_add ADD = auto() # tp.as_sequence.sq_concat CONCAT = auto() # tp_as_number.nb_subtract SUB = auto() # tp_as_number.nb_multiply MUL = auto() # tp.as_sequence.sq_repeat REPEAT = auto() # tp_as_number.nb_remainder MOD = auto() # tp_as_number.nb_floor_divide FLOORDIV = auto() # tp_as_number.nb_true_divide TRUEDIV = auto() # tp_as_number.nb_and AND = auto() # tp_as_number.nb_or OR = auto() # tp_as_number.nb_xor XOR = auto() # tp_as_number.nb_lshift LSHIFT = auto() # tp_as_number.nb_rshift RSHIFT = auto() # tp_as_number.nb_inplace_add IADD = auto() # tp.as_sequence.sq_inplace_concat ICONCAT = auto() # tp_as_number.nb_inplace_subtract ISUB = auto() # tp_as_number.nb_inplace_multiply IMUL = auto() # tp.as_sequence.sq_inplace_repeat IREPEAT = auto() # tp_as_number.nb_inplace_remainder IMOD = auto() # tp_as_number.nb_inplace_floor_divide IFLOORDIV = auto() # tp_as_number.nb_inplace_true_divide ITRUEDIV = auto() # tp_as_number.nb_inplace_and IAND = auto() # tp_as_number.nb_inplace_or IOR = auto() # tp_as_number.nb_inplace_xor IXOR = auto() # tp_as_number.nb_inplace_lshift ILSHIFT = auto() # tp_as_number.nb_inplace_rshift IRSHIFT = auto() # tp_as_number.nb_invert INVERT = auto() # tp_call CALL = auto() # tp.as_mapping.mp_subscript and tp.as_sequence.sq_item GETITEM = auto() # tp.as_mapping.mp_ass_subscript and tp.as_sequence.sq_ass_item SETITEM = auto() # tp.as_mapping.mp_ass_subscript and tp.as_sequence.sq_ass_item DELITEM = auto() # tp_richcompare (Py_LT) LT = auto() # tp_richcompare (Py_LE) LE = auto() # tp_richcompare (Py_EQ) EQ = auto() # tp_richcompare (Py_NE) NE = auto() # tp_richcompare (Py_GT) GT = auto() # tp_richcompare (Py_GE) GE = auto() # tp_as_number.nb_bool BOOL = auto() # tp_as_number.nb_negative NEG = auto() # tp_as_number.nb_positive POS = auto() # tp_as_number.nb_absolute ABS = auto() # tp_repr REPR = auto() # tp_hash HASH = auto() # tp_as_number.nb_index INDEX = auto() # tp_iter ITER = auto() # tp_iter_next NEXT = auto() # tp_setattr SETATTR = auto() # Internal to the parser (implemented as tp_setattr) DELATTR = auto() # tp_as_number.nb_matrix_multiply MATMUL = auto() # tp_as_number.nb_inplace_matrix_multiply IMATMUL = auto() # tp_as_async.am_await AWAIT = auto() # tp_as_async.am_aiter AITER = auto() # tp_as_async.am_anext ANEXT = auto() class QualifierType(Enum): """ The type of a qualifier used in %If/%End directives. """ # The qualifier is a feature. FEATURE = auto() # The qualifier is a platform. PLATFORM = auto() # The qualifier is part of a timeline. TIME = auto() class Transfer(Enum): """ The different types of ownership transfer. """ # No transfer of ownership. NONE = auto() # /Transfer/ was specified. TRANSFER = auto() # /TransferBack/ was specified. TRANSFER_BACK = auto() # /TransferThis/ was specified. TRANSFER_THIS = auto() class ValueType(Enum): """ The different types of a value in an expression. """ # A quoted character. QCHAR = auto() # A string. STRING = auto() # A number. NUMERIC = auto() # A floating point number. REAL = auto() # A scoped name. SCOPED = auto() # A function call. FCALL = auto() # A placeholder. EMPTY = auto() @dataclass class Argument: """ Encapsulate a callable argument (or return value or variable type). """ # The type. type: ArgumentType # Set if /AllowNone/ was specified. allow_none: bool = False # The support the argument provides for arrays. array: ArrayArgument = ArrayArgument.NONE # The optional default value. default_value: Optional[List['Value']] = None # The optional definition. What this is depends on the type. definition: Any = None # The sequence of dereferences. An element is True if the corresponding # dereference is const. derefs: List[bool] = field(default_factory=list) # Set if /DisallowNone/ was specified. disallow_none: bool = False # Set if /GetWrapper/ was specified. get_wrapper: bool = False # Set if the argument is const. is_const: bool = False # Set if /Constrained/ was specified. is_constrained: bool = False # Set if the argument is passing a value in. is_in: bool = False # Set if the argument is passing a value out. is_out: bool = False # Set if the argument is a reference. is_reference: bool = False # The key for a reference. If None then /KeepReference/ wasn't specified. key: Optional[int] = None # The optional name. name: Optional['CachedName'] = None # Set if /NoCopy/ was specified. no_copy: bool = False # The original type if it was a typedef. original_typedef: Optional['WrappedTypedef'] = None # Set if /ResultSize/ was specified. result_size: bool = False # The value of /ScopesStripped/. scopes_stripped: int = 0 # The source location. source_location: Optional['SourceLocation'] = None # Any transfer of ownership. transfer: Transfer = Transfer.NONE # The non-default type hints. type_hints: Optional['TypeHints'] = None def __hash__(self): """ Reimplemented so an Argument object can be used as a dict key. """ return id(self) @dataclass class CachedName: """ Encapsulate a name that may be needed as a string in the generated code. """ # The name. name: str # Set if the name is a substring of another. (resolver) is_substring: bool = False # The offset of the name in the string pool. (resolver) offset: int = 0 # Set if the name is used in the generated code. used: bool = False def __str__(self): """ Return the string representation. """ return self.name @dataclass class CodeBlock: """ Encapsulate a code block, ie. the literal body of a directive. """ # The name of the .sip file containing the code. sip_file: str # The line number in the .sip file that the code starts at. line_nr: int = 1 # The text of the code block. text: str = '' @dataclass class Constructor: """ Encapsulate a constructor. """ # The access specifier. access_specifier: AccessSpecifier # The Python signature. py_signature: 'Signature' # The C/C++ signature. It will be none if /NoDerived/ was specified. cpp_signature: Optional['Signature'] = None # Set if /Deprecated/ was specified. deprecated: bool = False # The docstring. docstring: Optional['Docstring'] = None # The action required on the GIL. gil_action: GILAction = GILAction.DEFAULT # Set if the ctor is the implementation of a cast. (resolver) is_cast: bool = False # The keyword argument support. kw_args: KwArgs = KwArgs.NONE # The code specified by any %MethodCode directive. method_code: Optional[CodeBlock] = None # Set if the type hint should be suppressed. no_type_hint: bool = False # The /PostHook/ name. posthook: Optional[str] = None # The /PreHook/ name. prehook: Optional[str] = None # The code specified by any %PreMethodCode directive. premethod_code: Optional[CodeBlock] = None # Set if a Python exception is raised. raises_py_exception: bool = False # The optional throw arguments. Replace with 'noexcept' in SIP v7. throw_args: Optional['ThrowArguments'] = None # Any transfer of ownership. transfer: Transfer = Transfer.NONE @dataclass class Docstring: """ Encapsulate a docstring. """ # The position of the automatically generated signature. signature: DocstringSignature # The text of the docstring. text: str @dataclass class Extract: """ Encapsulate a part of an extract. """ # The ID of the extract. id: str # The order. A negative value implies the part is appended to the extract. order: int # The text of the extract part. text: str @dataclass class FunctionCall: """ Encapsulate a call to a function in an expression. """ # The type of the result. result: Argument # The list of arguments. args: List[List['Value']] @dataclass class IfaceFile: """ Encapsulate an interface file, ie. a generated source file. """ # The type. type: IfaceFileType # The C/C++ name. It will be None if the interface file relates to a # template. Note that this is fully qualified and so overlaps with # 'fq_cpp_name'. cpp_name: Optional[CachedName] = None # The filename extension. file_extension: Optional[str] = None # The fully qualified C/C++ name. It will be None if the interface file # relates to a template. fq_cpp_name: Optional[ScopedName] = None # The generated type number. (resolver) type_nr: int = -1 # The defining module. It will be None if the interface file relates to a # template. module: Optional['Module'] = None # Set if this interface file is needed by the module for which code is to # be generated. needed: bool = False # The %TypeHeaderCode. type_header_code: List[CodeBlock] = field(default_factory=list) # The interface files used by this one (either directly or indirectly). used: List['IfaceFile'] = field(default_factory=list) @dataclass class License: """ Encapsulate a license. """ # The type of the license. type: str # The licensee. licensee: Optional[str] = None # The timestamp. timestamp: Optional[str] = None # The signature. signature: Optional[str] = None @dataclass class MappedType: """ Encapsulate a mapped type. """ # The interface file. iface_file: IfaceFile # The type. type: Argument # The %ConvertFromTypeCode. convert_from_type_code: Optional[CodeBlock] = None # The %ConvertToTypeCode. convert_to_type_code: Optional[CodeBlock] = None # The C/C++ name. It will be None for mapped type templates. cpp_name: Optional[CachedName] = None # Set if /AllowNone/ was specified. handles_none: bool = False # The %InstanceCode. instance_code: Optional[CodeBlock] = None # The member functions. members: List['Member'] = field(default_factory=list) # Set if the handwritten code requires user state information. needs_user_state: bool = False # Set if /NoAssignmentOperator/ was specified. no_assignment_operator: bool = False # Set if /NoCopyCtor/ was specified. no_copy_ctor: bool = False # Set if /NoDefaultCtor/ was specified. no_default_ctor: bool = False # Set if /NoRelease/ was specified. no_release: bool = False # The overloaded member functions. overloads: List['Overload'] = field(default_factory=list) # The Python name. It will be None for mapped type templates. py_name: Optional[CachedName] = None # The /PyQtFlags/. pyqt_flags: int = 0 # The %ReleaseCode. release_code: Optional[CodeBlock] = None # The %TypeCode. type_code: List[CodeBlock] = field(default_factory=list) # The %TypeHintCode. type_hint_code: Optional[CodeBlock] = None # The type hints. type_hints: Optional['TypeHints'] = None @dataclass class MappedTypeTemplate: """ Encapsulate a mapped type template. """ # The prototype mapped type. mapped_type: MappedType # The template arguments. signature: 'Signature' @dataclass class Member: """ Encapsulate a member function. """ # The defining module. module: 'Module' # The Python name. py_name: CachedName # Set if keyword arguments are allowed. allow_keyword_args: bool = False # Set if at least one of the overloads is protected. has_protected: bool = False # Set if /Numeric/ was specified. is_numeric: bool = False # Set if /Sequence/ was specified. is_sequence: bool = False # The number of the member. (outputter) member_nr: int = -1 # The original interface file if the function was defined in a namespace. namespace_iface_file: Optional[IfaceFile] = None # Set if /NoArgParser/ was specified. no_arg_parser: bool = False # The Python slot if it is not an ordinary member function. py_slot: Optional[PySlot] = None @dataclass class Module: """ Encapsulate a module. """ # The list of all (explicit and implied) imports. (resolver) all_imports: List['Module'] = field(default_factory=list) # Set if wrapped ctors should support cooperative multi-inheritance. call_super_init: bool = False # The text specified by any %Copying directives. copying: List[CodeBlock] = field(default_factory=list) # The default docstring format. default_docstring_format: DocstringFormat = DocstringFormat.RAW # The default docstring signature position. default_docstring_signature: DocstringSignature = DocstringSignature.DISCARDED # The default exception. default_exception: Optional['WrappedException'] = None # The default meta-type. default_metatype: Optional[CachedName] = None # The default super-type. default_supertype: Optional[CachedName] = None # The handler called when a Python re-implementation of a virtual C++ # function raises an exception. default_virtual_error_handler: Optional[str] = None # The module's docstring. docstring: Optional[Docstring] = None # The fully qualified name of the module. It is only None until %Module is # specified. fq_py_name: Optional[CachedName] = None # The global functions. global_functions: List[Member] = field(default_factory=list) # Set if any class defined in the module has a delayed dtor. has_delayed_dtors: bool = False # The list of direct imports. imports: List['Module'] = field(default_factory=list) # The code specified by any %InitialisationCode directives. initialisation_code: List[CodeBlock] = field(default_factory=list) # The software license. license: Optional[License] = None # The %ModuleCode. module_code: List[CodeBlock] = field(default_factory=list) # The %ModuleHeaderCode. module_header_code: List[CodeBlock] = field(default_factory=list) # The next key to auto-allocate. next_key: int = -1 # The number of exceptions defined in this module. (resolver) nr_exceptions: int = 0 # The generated types needed by this module. (resolver) needed_types: List[Argument] = field(default_factory=list) # The number of typedefs defined in this module. nr_typedefs: int = 0 # The number of virtual error handlers defined in this module. (resolver) nr_virtual_error_handlers: int = 0 # The overloaded global functions. overloads: List['Overload'] = field(default_factory=list) # The code specified by any %PostInitialisationCode directives. postinitialisation_code: List[CodeBlock] = field(default_factory=list) # The proxy classes. proxies: List['WrappedClass'] = field(default_factory=list) # The code specified by any %PreInitialisationCode directives. preinitialisation_code: List[CodeBlock] = field(default_factory=list) # The name of the module. (resolver) py_name: Optional[str] = None # Set if the generated bindings are Py_ssize_t clean. py_ssize_t_clean: bool = False # The list of qualifiers. qualifiers: List['Qualifier'] = field(default_factory=list) # The %TypeHintCode. type_hint_code: List[CodeBlock] = field(default_factory=list) # The %UnitCode. unit_code: List[CodeBlock] = field(default_factory=list) # The %UnitPostIncludeCode. unit_postinclude_code: List[CodeBlock] = field(default_factory=list) # Set if the actual argument names to wrapped callables should be used in # the generated bindings rather than automatically generated ones. use_arg_names: bool = False # Set if the generated bindings should only use the limited Python API. use_limited_api: bool = False # The interface files used by the module. used: List[IfaceFile] = field(default_factory=list) @dataclass class Overload: """ Encapsulate an overloaded member function. """ # The access specifier. access_specifier: Optional[AccessSpecifier] # The member common to all overloads. common: Member # The C/C++ name if not a operator/slot. cpp_name: str # The C/C++ signature. cpp_signature: 'Signature' # The Python signature. py_signature: 'Signature' # Set if /AbortOnException/ is specified. abort_on_exception: bool = False # Set if the overload is really protected. access_is_really_protected: bool = False # Set if /Deprecated/ was specified. deprecated: bool = False # The docstring. docstring: Optional[Docstring] = None # Set if /Factory/ was specified. factory: bool = False # The action required on the GIL. gil_action: GILAction = GILAction.DEFAULT # Set if the overload is abstract. is_abstract: bool = False # Set if /AutoGen/ was specified and the associated feature was enabled. is_auto_generated: bool = False # Set if the overload is a complementary slot. (resolver) is_complementary: bool = False # Set if the overload is const. is_const: bool = False # Set if the overload implements __delattr__ (as opposed to __setattr__). is_delattr: bool = False # Set if the overload is final. is_final: bool = False # Set if the C++ overload is global. (resolver) is_global: bool = False # Set if self should not be dereferenced. (resolver) dont_deref_self: bool = False # Set if the overload is a reflected slot. (resolver) is_reflected: bool = False # Set if the overload is static. is_static: bool = False # Set if the overload is virtual. is_virtual: bool = False # Set if the overload is a virtual reimplementation. (resolver) is_virtual_reimplementation: bool = False # The keyword argument support. kw_args: KwArgs = KwArgs.NONE # The code specified by any %MethodCode directive. method_code: Optional[CodeBlock] = None # Set if /NewThread/ was specified. new_thread: bool = False # Set if the type hint should be suppressed. no_type_hint: bool = False # Set if any virtual error handler should be ignored. no_virtual_error_handler: bool = False # The /PostHook/ name. posthook: Optional[str] = None # The /PreHook/ name. prehook: Optional[str] = None # The code specified by any %PreMethodCode directive. premethod_code: Optional[CodeBlock] = None # The PyQt method specifier. pyqt_method_specifier: Optional[PyQtMethodSpecifier] = None # Set if a Python exception is raised. raises_py_exception: bool = False # The source location. source_location: Optional['SourceLocation'] = None # The optional throw arguments. Replace with 'noexcept' in SIP v7. throw_args: Optional['ThrowArguments'] = None # Any transfer of ownership. transfer: Transfer = Transfer.NONE # The code specified by any %VirtualCallCode directive. virtual_call_code: Optional[CodeBlock] = None # The code specified by any %VirtualCatcherCode directive. virtual_catcher_code: Optional[CodeBlock] = None # The name of the virtual error handler to use. virtual_error_handler: Optional[str] = None @dataclass class Property: """ Encapsulate a property. """ # The name of the getter. getter: str # The name. name: CachedName # The docstring. docstring: Optional[Docstring] = None # The name of the optional setter. setter: Optional[str] = None @dataclass class Qualifier: """ Encapsulate a qualifier used in %If/%End directives. """ # The defining module. module: 'Module' # The name of the qualifier. name: str # The type of the qualifier type: QualifierType # Set if the qualifier is enabled by default. enabled_by_default: bool = False # The order if it is a TIME qualifier (ie. the position within the # timeline). order: int = 0 # The timeline number within the defining module if it is a TIME qualifier. timeline: int = 0 @dataclass class Signature: """ Encapsulate a signature (including the optional result). """ # The list of arguments. args: List[Argument] = field(default_factory=list) # The type of the result. result: Optional[Argument] = None @dataclass class SourceLocation: """ Encapsulate a location in a .sip source file. """ # The .sip file name. sip_file: str # The column number. column: int = 0 # The line number. line: int = 0 @dataclass class Specification: """ Encapsulate a parsed .sip file. """ # The version of the ABI being targeted. abi_version: tuple # Set if the specification is strict. is_strict: bool # The fully qualified name of the sip module. If it is None then there is # no shared sip module. sip_module: Optional[str] # Set if the bindings are for C rather than C++. c_bindings: bool = False # The list of classes. classes: List['WrappedClass'] = field(default_factory=list) # The list of enums. enums: List['WrappedEnum'] = field(default_factory=list) # The list of exceptions. exceptions: List['WrappedException'] = field(default_factory=list) # The %ExportedHeaderCode. exported_header_code: List[CodeBlock] = field(default_factory=list) # The %ExportedTypeHintCode. exported_type_hint_code: List[CodeBlock] = field(default_factory=list) # The extracts. extracts: List[Extract] = field(default_factory=list) # The interface files. iface_files: List[IfaceFile] = field(default_factory=list) # Set if the specification is for a composite module. is_composite: bool = False # The mapped type templates. mapped_type_templates: List[MappedTypeTemplate] = field(default_factory=list) # The mapped types. mapped_types: List[MappedType] = field(default_factory=list) # The module for which code is to be generated. module: Module = field(default_factory=Module) # The cache of names that may be required as strings in the generated code. name_cache: Dict[int, List[CachedName]] = field(default_factory=dict) # The number of virtual handlers. (resolver) nr_virtual_handlers: int = 0 # The list of plugins. Note that these are PyQt-specific and will be # removed in SIP v7. plugins: List[str] = field(default_factory=list) # The QObject class. pyqt_qobject: Optional['WrappedClass'] = None # The list of typedefs. typedefs: List['WrappedTypedef'] = field(default_factory=list) # The list of variables. variables: List['WrappedVariable'] = field(default_factory=list) # The list of virtual error handlers. virtual_error_handlers: List['VirtualErrorHandler'] = field(default_factory=list) # The list of virtual handlers. (resolver) virtual_handlers: List['VirtualHandler'] = field(default_factory=list) def __hash__(self): """ Reimplemented so a Specification object can be used as a dict key. """ return id(self) @dataclass class Template: """ Encapsulate a template. """ # The C++ name. cpp_name: ScopedName # The types. types: Signature @dataclass class ThrowArguments: """ Encapsulate the arguments to a C++ throw(). """ # The list of the argument names. If it is None then 'noexcept' was # specified, otherwise there will be at least one argument. arguments: Optional[List['WrappedException']] = None @dataclass class TypeHints: """ Encapsulate a set of PEP 484 type hints for a type. """ # The type hint when used to pass a value into a callable. hint_in: Optional[str] # The type hint used to return a value from a callable. hint_out: Optional[str] # The representation of a default value in a type hint. default_value: Optional[str] @dataclass class Value: """ Encapsulate a literal value. """ # The type of the value. value_type: ValueType # Any literal value. value: Optional[Union[str, int, float, FunctionCall, ScopedName]] # Any binary operator. binary_operator: Optional[str] = None # Any cast. cast: Optional[ScopedName] = None # Any unary operator. unary_operator: Optional[str] = None @dataclass class VirtualErrorHandler: """ Encapsulate a virtual error handler. """ # The code implementing the handler. code: CodeBlock # The defining module. module: Module # The name of the handler. name: str # The number of the handler. (resolver) handler_nr: int = -1 @dataclass class VirtualHandler: """ Encapsulate a virtual overload handler. (resolver) """ # The C/C++ signature. cpp_signature: Signature # The Python signature. py_signature: Signature # The code specified by any %VirtualCatcherCode directive. virtual_catcher_code: Optional[CodeBlock] # The virtual error handler. virtual_error_handler: VirtualErrorHandler # Set if execution should abort if there is an exception. abort_on_exception: bool = False # The number of the handler. handler_nr: int = -1 # Set if ownership of the result should be transferred. transfer_result: bool = False @dataclass class VirtualOverload: """ Encapsulate a virtual overloaded member function. (resolver) """ # The overload overload: Overload # The handler for the overload. It is only set for the module for which # code is being generated. handler: Optional[VirtualHandler] @dataclass class VisibleMember: """ Encapsulate a visible member function. (resolver) """ # The member function. member: Member # The defining class. scope: 'WrappedClass' @dataclass class WrappedClass: """ Encapsulate a wrapped C/C++ namespace/class/struct/union. """ # The interface file. iface_file: IfaceFile # The Python name. py_name: CachedName # The enclosing scope. scope: Optional['WrappedClass'] # The %BIGetBufferCode. bi_get_buffer_code: Optional[CodeBlock] = None # The %BIReleaseBufferCode. bi_release_buffer_code: Optional[CodeBlock] = None # Set if the class has usable constructors. can_create: bool = False # Set if an instance of the class cannot be assigned. cannot_assign: bool = False # Set if an instance of the class cannot be copied. (resolver) cannot_copy: bool = False # The list of operator casts. casts: List[Argument] = field(default_factory=list) # The specific type of class. It will be None for namespaces. class_key: Optional[ClassKey] = None # The %ConvertFromTypeCode. convert_from_type_code: Optional[CodeBlock] = None # The %ConvertToSubClassCode. convert_to_subclass_code: Optional[CodeBlock] = None # The %ConvertToTypeCode. convert_to_type_code: Optional[CodeBlock] = None # The constructors. ctors: List[Constructor] = field(default_factory=list) # The dtor's %PreMethodCode and %MethodCode. dealloc_code: List[CodeBlock] = field(default_factory=list) # The constructor that has /Default/ specified. default_ctor: Optional[Constructor] = None # Set if /DelayDtor/ was specified. delay_dtor: bool = False # Set if /Deprecated/ was specified. deprecated: bool = False # The docstring. docstring: Optional[Docstring] = None # The access specifier of any dtor. dtor: Optional[AccessSpecifier] = None # The action required on the GIL. dtor_gil_action: GILAction = GILAction.DEFAULT # The optional dtor throw arguments. Replace with 'noexcept' in SIP v7. dtor_throw_args: Optional[ThrowArguments] = None # The code specified by any dtor %VirtualCatcherCode directive. dtor_virtual_catcher_code: Optional[CodeBlock] = None # Set if /ExportDerived/ was specified. export_derived: bool = False # Set if /External/ was specified. external: bool = False # The %FinalisationCode. finalisation_code: Optional[CodeBlock] = None # The %GCClearCode. gc_clear_code: Optional[CodeBlock] = None # The %GCTraverseCode. gc_traverse_code: Optional[CodeBlock] = None # Set if /AllowNone/ was specified. handles_none: bool = False # Set if the class has a non-lazy method. has_nonlazy_method: bool = False # Set if the class actually has a shadow (ie. derived) class. (resolver) has_shadow: bool = False # Set if the class has variables that need handlers. (resolver) has_variable_handlers: bool = False # The %InstanceCode. instance_code: Optional[CodeBlock] = None # Set if the class is abstract. is_abstract: bool = False # Set if the class is a hidden namespace. is_hidden_namespace: bool = False # Set if the class is incomplete. is_incomplete: bool = False # Set if the class is opaque. is_opaque: bool = False # Set if the class is defined in a protected section. is_protected: bool = False # Set if the class is QObject or a sub-class. (resolver) is_qobject: bool = False # The C++ name of any overload annotated with __len__. len_cpp_name: Optional[str] = None # The methods. members: List[Member] = field(default_factory=list) # The value of /Metatype/ if specified. metatype: Optional[CachedName] = None # Set if /Mixin/ was specified. mixin: bool = False # The list of all classes in the class hierarchy starting with itself. # (resolver) mro: List['WrappedClass'] = field(default_factory=list) # Set if the class needs an array helper. (resolver) needs_array_helper: bool = False # Set if the class needs a copy helper. (resolver) needs_copy_helper: bool = False # Set if the class needs a shadow (ie. derived) class. needs_shadow: bool = False # Set if /NoDefaultCtors/ was specified. no_default_ctors: bool = False # Set if /NoTypeHint/ was specified. no_type_hint: bool = False # Set if the class name should not be used in the generated code (and the # instantiated template name should be used instead). no_type_name: bool = False # The overloaded methods. overloads: List[Overload] = field(default_factory=list) # The %PickleCode. pickle_code: Optional[CodeBlock] = None # The properties. properties: List[Property] = field(default_factory=list) # The /PyQtFlags/. pyqt_flags: int = 0 # The /PyQtFlagsEnums/. pyqt_flags_enums: Optional[List[str]] = None # The /PyQtInterface/. pyqt_interface: Optional[str] = None # Set if /PyQtNoQMetaObject/ was specified. pyqt_no_qmetaobject: bool = False # The real class if this is a proxy or a namespace extender. real_class: Optional['WrappedClass'] = None # The sub-class base class. (resolver) subclass_base: Optional['WrappedClass'] = None # The super-classes. superclasses: List['WrappedClass'] = field(default_factory=list) # The value of /Supertype/ if specified. supertype: Optional[CachedName] = None # The template that was instantiated to create this class. template: Optional[Template] = None # The %TypeCode. type_code: List[CodeBlock] = field(default_factory=list) # The %TypeHintCode. type_hint_code: Optional[CodeBlock] = None # The type hints. type_hints: Optional[TypeHints] = None # The name of the virtual error handler to use. virtual_error_handler: Optional[str] = None # The virtual overloaded methods. (resolver) virtual_overloads: List[VirtualOverload] = field(default_factory=list) # The visible member functions. (resolver) visible_members: List[VisibleMember] = field(default_factory=list) def __hash__(self): """ Reimplemented so an Argument object can be used as a dict key. """ return id(self) @dataclass class WrappedEnum: """ Encapsulate a wrapped enum. """ # The base type. base_type: EnumBaseType # The fully qualified C++ name. fq_cpp_name: Optional[ScopedName] # The defining module. module: Module # The cached fully qualified C++ name. cached_fq_cpp_name: Optional[CachedName] = None # Set if the enum is defined in a protected section. is_protected: bool = False # Set if the enum is a scoped enum. is_scoped: bool = False # The members. members: List['WrappedEnumMember'] = field(default_factory=list) # Set if this enum is needed by the module for which code is to be # generated. (resolver) needed: bool = False # Set if /NoScope/ was specified. no_scope: bool = False # Set if the type hint should be suppressed. no_type_hint: bool = False # The overloaded slot member functions. (resolver) overloads: List['Overload'] = field(default_factory=list) # The Python name. py_name: Optional[CachedName] = None # The enclosing scope. scope: Optional[Union[MappedType, WrappedClass]] = None # The slot member functions. These can only be created by global operators # being moved. (resolver) slots: List[Member] = field(default_factory=list) # The generated type number. (resolver) type_nr: int = -1 def __hash__(self): """ Reimplemented so an Argument object can be used as a dict key. """ return id(self) @dataclass class WrappedEnumMember: """ Encapsulate a member of a wrapped enum. """ # The C++ name. cpp_name: str # The Python name. py_name: CachedName # The enclosing enum. scope: 'WrappedEnum' # Set if the type hint should be suppressed. no_type_hint: bool = False @dataclass class WrappedException: """ Encapsulate a wrapped exception. """ # The interface file. iface_file: IfaceFile # The code specified by the %RaiseCode directive. raise_code: CodeBlock # The base exception if it is a builtin. builtin_base_exception: Optional[str] = None # The class that implements the exception (if the exception is not a Python # exception). class_exception: Optional[WrappedClass] = None # The base exception if it is defined in the specification. defined_base_exception: Optional['WrappedException'] = None # The number of the exception (only if a Python exception object is # required. (resolver) exception_nr: int = -1 # Set if this exception is needed by the module for which code is to be # generated. (resolver) needed: bool = False # The Python name. py_name: Optional[str] = None @dataclass class WrappedTypedef: """ Encapsulate a wrapped typedef. """ # The fully qualified C++ name. fq_cpp_name: ScopedName # The defining module. module: Module # The enclosing scope. scope: Optional[WrappedClass] # The type. type: Argument # Set if the typedef name should not be used in the generated code. no_type_name: bool = False @dataclass class WrappedVariable: """ Encapsulate a wrapped variable. """ # The fully qualified C++ name. fq_cpp_name: ScopedName # The defining module. module: Module # The Python name. py_name: CachedName # The enclosing scope. scope: Optional[WrappedClass] # The type. type: Argument # The code specified by any %AccessCode directive. access_code: Optional[CodeBlock] = None # The code specified by any %GetCode directive. get_code: Optional[CodeBlock] = None # Set if the variable is static. is_static: bool = False # Set if the variable needs a handler. (resolver) needs_handler: bool = False # Set if the type hint should be suppressed. no_type_hint: bool = False # Set if the variable has no setter and will be read-only. no_setter: bool = False # The code specified by any %SetCode directive. set_code: Optional[CodeBlock] = None sip-6.8.6/sipbuild/generator/templates.py000066400000000000000000000217271464421045000205100ustar00rootroot00000000000000# SPDX-License-Identifier: BSD-2-Clause # Copyright (c) 2024 Phil Thompson from copy import copy from .scoped_name import ScopedName from .specification import ArgumentType, IfaceFileType from .utils import append_iface_file, argument_as_str, same_base_type def encoded_template_name(template): """ Return the encoded name of a template. """ snd = ScopedName(template.cpp_name) for ad in template.types.args: flags = 0 if ad.is_const: flags |= 1 if ad.is_reference: flags |= 2 # We use numbers so they don't conflict with names. encoding = '{:02d}{}{}'.format(ad.type.value, flags, len(ad.derefs)) if ad.type is ArgumentType.DEFINED: arg_snd = ScopedName(ad.definition) elif ad.type is ArgumentType.TEMPLATE: arg_snd = encoded_template_name(ad.definition) elif ad.type is ArgumentType.STRUCT: arg_snd = ScopedName(ad.definition) else: arg_snd = None if arg_snd is None: snd.append(encoding) else: # Replace the first element of the argument name with a copy with # the encoding prepended. arg_snd[0] = encoding + arg_snd[0] for name in arg_snd: snd.append(name) return snd def same_template_signature(sig1, sig2, deep=False): """ Return True if the template signatures are the same. A deep comparison is used for mapped type templates where we want to recurse into any nested templates. """ if len(sig1.args) != len(sig2.args): return False for type1, type2 in zip(sig1.args, sig2.args): # If we are doing a shallow comparision (ie. for class templates) then # a type name in the first signature matches anything in the second # signature. if type1.type is ArgumentType.DEFINED and not deep: continue # For type names only compare the references and pointers, and do the # same for any nested templates. if type1.type is ArgumentType.DEFINED and type2.type is ArgumentType.DEFINED: if type1.is_reference != type2.is_reference or len(type1.derefs) != len(type2.derefs): return False elif type1.type is ArgumentType.TEMPLATE and type2.type is ArgumentType.TEMPLATE: if not same_template_signature(type1.definition.types, type2.definition.types, deep=deep): return False elif not same_base_type(type1, type2): return False return True def template_code(spec, used, proto_code, expansions): """ Return a copy of an optional CodeBlock object with sub-strings replaced by corresponding values. """ # Handle the trivial case. if proto_code is None: return None return _template_code_block(spec, used, proto_code, expansions) def template_code_blocks(spec, used, proto_code_blocks, expansions): """ Return a copy of a list of CodeBlock objects with sub-strings replaced by corresponding values. """ return [_template_code_block(spec, used, pc, expansions) for pc in proto_code_blocks] def template_expansions(template_names, instantiation_values, declared_names=None): """ Return a dict of expansions to be applied when instantiating mapped type of class templates (including handwritten code). The key is the symbolic name of a template argument and the value is the replacement to be used in a particular instantiation. """ # TODO: This is broken (or possibly just over-complicated). The # declaration of template parameters is used for two purposes: firstly to # allow real types to be used to distinguish between overloaded templates; # secondly to define the names of the parameters that will be replaced by # values provided at instantiation. It's possible that only the latter # will ever be expanded. expansions = {} for arg_nr, name_arg in enumerate(template_names.args): if name_arg.type is ArgumentType.DEFINED: # If the type names have been declared (as they are with a mapped # type template) check that this is one of them. if declared_names is not None: # Only consider unscoped names. if not name_arg.definition.is_simple: continue for declared in declared_names.args: # Skip anything but simple names. if declared.type is not ArgumentType.DEFINED: continue if name_arg.definition.base_name == declared.definition.base_name: name = name_arg.definition.base_name break else: continue else: name = name_arg.definition.base_name # Get the corresponding value. For defined types we don't want any # indirection or references. value_arg = instantiation_values.args[arg_nr] if value_arg.type is ArgumentType.DEFINED: value = str(value_arg.definition) else: value = argument_as_str(value_arg) # We do want const. if value_arg.is_const: value = 'const ' + value; expansions[name] = value elif name_arg.type is ArgumentType.TEMPLATE: value_arg = instantiation_values.args[arg_nr] # These checks shouldn't be necessary, but... if value_arg.type is ArgumentType.TEMPLATE and len(name_arg.definition.types.args) == len(value_arg.definition.types.args): expansions.update( template_expansions(name_arg.definition.types, value_arg.definition.types, declared_names)) return expansions def template_string(proto_str, expansions, scope_replacement=None): """ Return a copy of a string with sub-strings replaced by corresponding values. """ for name, value in expansions.items(): value = _strip_const(value) # Translate any C++ scoping. if scope_replacement is not None: value = value.replace('::', scope_replacement) # Perform any replacement. proto_str = proto_str.replace(name, value) return proto_str def _strip_const(s): """ Strip any leading 'const' from a string. """ if s.startswith('const '): s = s[6:] return s def _template_code_block(spec, used, proto_code, expansions): """ Return a copy of a CodeBlock object with sub-strings replaced by corresponding values or the original if there were no substitutions. """ i_code = copy(proto_code) i_lines = [] for proto_line in proto_code.text.split('\n'): i_line = proto_line # Don't do any substitution in lines that appear to be preprocessor # directives. This prevents #include'd file names being broken. if not proto_line.lstrip().startswith('#'): # Go through each expansion. for name, value in expansions.items(): # Look for the name at the current position in the current # line. pos = i_line.find(name) while pos >= 0: # See if the name is referring to a generated type # structure. for gen_type in ('sipType_', 'sipException_'): if i_line[:pos].endswith(gen_type): value = _strip_const(value) _add_used_from_code(spec, used, value) # Convert the value to the rest of the name of the # generated type structure. if value.startswith('::'): value = value[2:] value = value.replace('::', '_') break # Perform the substitution and update the current position. i_line = i_line[0:pos] + value + i_line[pos + len(name):] pos = i_line.find(name, pos + len(value)) i_lines.append(i_line) i_code.text = '\n'.join(i_lines) # Return the prototype itself if nothing changed. if proto_code.text == i_code.text: return proto_code return i_code def _add_used_from_code(spec, used, name): """ Add any interface files to a used list that are defined for a name. """ name = ScopedName.parse(name) name.make_absolute() for iface_file in spec.iface_files: if iface_file.type in (IfaceFileType.CLASS, IfaceFileType.EXCEPTION): if iface_file.fq_cpp_name == name: append_iface_file(used, iface_file) return for enum in spec.enums: if enum.scope is not None: if enum.fq_cpp_name == name: append_iface_file(used, enum.scope.iface_file) return sip-6.8.6/sipbuild/generator/utils.py000066400000000000000000000366371464421045000176600ustar00rootroot00000000000000# SPDX-License-Identifier: BSD-2-Clause # Copyright (c) 2024 Phil Thompson from .scoped_name import ScopedName from .specification import ArgumentType, CachedName, IfaceFile, IfaceFileType def append_iface_file(iface_file_list, iface_file): """ Append an IfaceFile object to a list of them. """ # Make sure we don't try and add an interface file to its own list. if iface_file.used is iface_file_list: return # Don't bother if it is already there. if iface_file in iface_file_list: return iface_file_list.append(iface_file) def argument_as_str(arg): """ Convert an Argument object to a string of valid C++. """ if arg.original_typedef is None or arg.original_typedef.no_type_name: if arg.type is ArgumentType.TEMPLATE: s = str(arg.definition.cpp_name) s += '<' need_comma = False for sub_arg in arg.definition.types.args: if need_comma: s += ',' else: need_comma = True s += argument_as_str(sub_arg) if s.endswith('>'): # For compilers earlier than C++11. s += ' ' s += '>' elif arg.type in (ArgumentType.STRUCT, ArgumentType.DEFINED): s = str(arg.definition) elif arg.type in (ArgumentType.UBYTE, ArgumentType.USTRING): s = 'unsigned char' elif arg.type in (ArgumentType.BYTE, ArgumentType.STRING, ArgumentType.ASCII_STRING, ArgumentType.LATIN1_STRING, ArgumentType.UTF8_STRING): s = 'char' elif arg.type in (ArgumentType.SBYTE, ArgumentType.SSTRING): s = 'signed char' elif arg.type is ArgumentType.WSTRING: s = 'wchar_t' elif arg.type is ArgumentType.USHORT: s = 'unsigned short' elif arg.type is ArgumentType.SHORT: s = 'short' elif arg.type is ArgumentType.UINT: s = 'uint' elif arg.type in (ArgumentType.INT, ArgumentType.CINT): s = 'int' elif arg.type is ArgumentType.ULONG: s = 'unsigned long' elif arg.type is ArgumentType.LONG: s = 'long' elif arg.type is ArgumentType.ULONGLONG: s = 'unsigned long long' elif arg.type is ArgumentType.LONGLONG: s = 'long long' elif arg.type in (ArgumentType.FLOAT, ArgumentType.CFLOAT): s = 'float' elif arg.type in (ArgumentType.DOUBLE, ArgumentType.CDOUBLE): s = 'double' elif arg.type in (ArgumentType.BOOL, ArgumentType.CBOOL): s = 'bool' elif arg.type is ArgumentType.VOID: s = 'void' elif arg.type is ArgumentType.CAPSULE: s = 'void *' elif arg.type is ArgumentType.SSIZE: s = 'Py_ssize_t' elif arg.type is ArgumentType.SIZE: s = 'size_t' elif arg.type is ArgumentType.HASH: s = 'Py_hash_t' else: # Use the original typedef. s = str(arg.original_typedef.fq_cpp_name) # Remove any global scope specifier (simply to make generated less # cluttered). if s.startswith('::'): s = s[2:] for _ in arg.derefs: s += '*' if arg.is_reference: s += '&' return s def cached_name(spec, name): """ Add a name to the cache if necessary and return the cached name. """ # Get the line of the cache for the length of this name creating it if # necessary. line = spec.name_cache.setdefault(len(name), []) # See if the name has already been cached. for nd in line: if nd.name == name: return nd # Create a new entry. nd = CachedName(name) line.append(nd) return nd def find_iface_file(spec, mod, fq_cpp_name, iface_file_type, error_logger, cpp_type=None, scope=None): """ Return an interface file for a fully qualified C/C++ name and type creating it if necessary. """ # See if the name is already used. for iff in spec.iface_files: if not iff.fq_cpp_name.matches(fq_cpp_name, scope=scope): continue # They must be the same type except that we allow a class if we want an # exception. This is because we allow classes to be used before they # are defined. if iff.type is not iface_file_type: if iface_file_type is not IfaceFileType.EXCEPTION or iff.type is not IfaceFileType.CLASS: error_logger( "a class, exception, namespace or mapped type has already been defined with the same name") # Ignore an external class declared in another module. if iface_file_type is IfaceFileType.CLASS and iff.module is not None and iff.module is not mod: for cd in spec.classes: if cd.iface_file is iff: external = cd.external break else: external = False if external: continue # If this is a mapped type with the same name defined in a different # module, then check that this type isn't the same as any of the mapped # types defined in that module. if iface_file_type == IfaceFileType.MAPPED_TYPE and iff.module is not mod: for mtd in spec.mapped_types: if mtd.iface_file is not iff: continue if cpp_type.type is not ArgumentType.TEMPLATE or \ mtd.type.type is not ArgumentType.TEMPLATE or \ same_base_type(cpp_type, mtd.type): error_logger( "the mapped type has already been defined in another module") # If we got here then we have a mapped type based on an existing # template, but with unique parameters. We don't want to use # interface files from other modules, so skip this one. continue # Ignore a namespace defined in another module. if iface_file_type is IfaceFileType.NAMESPACE and iff.module is not mod: continue return iff # Create a new interface file. fq_cpp_name = normalised_scoped_name(fq_cpp_name, scope) iff = IfaceFile(iface_file_type, cpp_name=cached_name(spec, str(fq_cpp_name)), fq_cpp_name=fq_cpp_name) # Use the same ordering as the old parser. spec.iface_files.insert(0, iff) return iff def find_method(klass, name): """ Return the Member object for a named member of a class or None if there was none. """ for member in klass.members: if member.py_name.name == name: return member return None def normalised_scoped_name(scoped_name, scope): """ Convert a scoped name to a fully qualified name. """ # Clone the name. fq_scoped_name = ScopedName(scoped_name) if fq_scoped_name.is_absolute: pass elif scope is None: fq_scoped_name.make_absolute() elif fq_scoped_name.is_simple: fq_scoped_name.prepend(scope.iface_file.fq_cpp_name) else: # The relative name has a scope and appears within a scope so we need # lookup the name's scope within the current scope. names_scope = fq_scoped_name[0] scope_fq_cpp_name = scope.iface_file.fq_cpp_name while scope_fq_cpp_name is not None: if scope_fq_cpp_name.base_name == names_scope: del fq_scoped_name[0] fq_scoped_name.prepend(scope_fq_cpp_name) break scope_fq_cpp_name = scope_fq_cpp_name.scope else: # The lookup failed so just make the name absolute. fq_scoped_name.make_absolute() return fq_scoped_name # Argument type qualifiers. _PY_STRING = (ArgumentType.USTRING, ArgumentType.SSTRING, ArgumentType.STRING, ArgumentType.ASCII_STRING, ArgumentType.LATIN1_STRING, ArgumentType.UTF8_STRING) _PY_FLOAT = (ArgumentType.CFLOAT, ArgumentType.FLOAT, ArgumentType.CDOUBLE, ArgumentType.DOUBLE) _PY_INT = (ArgumentType.BOOL, ArgumentType.HASH, ArgumentType.SSIZE, ArgumentType.SIZE, ArgumentType.BYTE, ArgumentType.SBYTE, ArgumentType.UBYTE, ArgumentType.SHORT, ArgumentType.USHORT, ArgumentType.CINT, ArgumentType.INT, ArgumentType.UINT) _PY_LONG = (ArgumentType.LONG, ArgumentType.LONGLONG) _PY_ULONG = (ArgumentType.ULONG, ArgumentType.ULONGLONG) _PY_AUTO = (ArgumentType.BOOL, ArgumentType.BYTE, ArgumentType.SBYTE, ArgumentType.UBYTE, ArgumentType.SHORT, ArgumentType.USHORT, ArgumentType.INT, ArgumentType.UINT, ArgumentType.FLOAT, ArgumentType.DOUBLE) _PY_CONSTRAINED = (ArgumentType.CBOOL, ArgumentType.CINT, ArgumentType.CFLOAT, ArgumentType.CDOUBLE) def py_as_int(type): """ Return True if Python will interpret a type as int. """ return type.type in _PY_INT or type.type in _PY_LONG or type.type in _PY_ULONG def same_argument_type(spec, arg1, arg2, strict=True): """ Compare two argument types and return True if they are the same. 'strict' means as C++ would see it, rather than Python. """ if arg1.is_reference != arg2.is_reference: return False if len(arg1.derefs) != len(arg2.derefs): return False if strict: # The const should be the same. if arg1.is_const != arg2.is_const: return False return same_base_type(arg1, arg2) # If both are constrained fundamental types then the types must match. if arg1.type in _PY_CONSTRAINED and arg2.type in _PY_CONSTRAINED: return arg1.type is arg2.type if spec.abi_version >= (13, 0): # Anonymous enums are ints. if arg1.type in _PY_INT and arg2.type is ArgumentType.ENUM and arg2.definition.fq_cpp_name is None: return True if arg1.type is ArgumentType.ENUM and arg1.definition.fq_cpp_name is None and arg2.type in _PY_INT: return True else: # An unconstrained enum also acts as a (very) constrained int. if arg1.type in _PY_INT and arg2.type is ArgumentType.ENUM and not arg2.is_constrained: return True if arg1.type is ArgumentType.ENUM and not arg1.is_constrained and arg2.type in _PY_INT: return True # Python will see all these as strings. if arg1.type in _PY_STRING and arg2.type in _PY_STRING: return True # Python will see all these as floats. if arg1.type in _PY_FLOAT and arg2.type in _PY_FLOAT: return True # Python will see all these as ints. if arg1.type in _PY_INT and arg2.type in _PY_INT: return True # Python will see all these as longs. if arg1.type in _PY_LONG and arg2.type in _PY_LONG: return True # Python will see all these as unsigned longs. if arg1.type in _PY_ULONG and arg2.type in _PY_ULONG: return True # Python will automatically convert between these. if arg1.type in _PY_AUTO and arg2.type in _PY_AUTO: return True # All the special cases have been handled. return same_base_type(arg1, arg2) def same_base_type(type1, type2): """ Return True if two Argument objects refer to the same base type, ie. without taking into account const and pointers. """ # The types must be the same. if type1.type is not type2.type: # If we are comparing a template with those that have already been used # to instantiate a class or mapped type then we need to compare with # the class or mapped type name. if type1.type is ArgumentType.CLASS and type2.type is ArgumentType.DEFINED: return type1.definition.iface_file.fq_cpp_name.matches(type2.definition) if type1.type is ArgumentType.DEFINED and type2.type is ArgumentType.CLASS: return type2.definition.iface_file.fq_cpp_name.matches(type1.definition) if type1.type is ArgumentType.MAPPED and type2.type is ArgumentType.DEFINED: return type1.definition.iface_file.fq_cpp_name.matches(type2.definition) if type1.type is ArgumentType.DEFINED and type2.type is ArgumentType.MAPPED: return type2.definition.iface_file.fq_cpp_name.matches(type1.definition) if type1.type is ArgumentType.ENUM and type2.type is ArgumentType.DEFINED: return type1.definition.fq_cpp_name.matches(type2.definition) if type1.type is ArgumentType.DEFINED and type2.type is ArgumentType.ENUM: return type2.definition.fq_cpp_name.matches(type1.definition) return False if type1.type is ArgumentType.CLASS: return type1.definition is type2.definition if type1.type is ArgumentType.ENUM: return type1.definition is type2.definition if type1.type is ArgumentType.TEMPLATE: td1 = type1.definition td2 = type2.definition if td1.cpp_name.absolute != td2.cpp_name.absolute: return False if len(td1.types.args) != len(td2.types.args): return False for ad1, ad2 in zip(td1.types.args, td2.types.args): if len(ad1.derefs) != len(ad2.derefs): return False if not same_base_type(ad1, ad2): return False return True if type1.type in (ArgumentType.STRUCT, ArgumentType.UNION): return type1.definition == type2.definition if type1.type is ArgumentType.DEFINED: return type1.definition == type2.definition if type1.type is ArgumentType.MAPPED: return type1.definition is type2.definition # They must be the same if we've got this far. return True def same_signature(spec, sig1, sig2, strict=True): """ Compare two signatures and return True if they are the same. """ if strict: # Count all the arguments. na1 = len(sig1.args) na2 = len(sig2.args) else: # Count only the compulsory arguments. na1 = 0 for arg in sig1.args: if arg.default_value is not None: break na1 += 1 na2 = 0 for arg in sig2.args: if arg.default_value is not None: break; na2 += 1 # The number of arguments must be the same. if na1 != na2: return False # The arguments must be the same. for a in range(len(sig1.args)): if not strict and sig1.args[a].default_value is not None: break if not same_argument_type(spec, sig1.args[a], sig2.args[a], strict=strict): return False # Must be the same if we've got this far. return True def search_typedefs(spec, cpp_name, type): """ Search the typedefs and update the given type from any definition. """ # Look for the name. fq_cpp_name = ScopedName(cpp_name) fq_cpp_name.make_absolute() for typedef in spec.typedefs: if typedef.fq_cpp_name == fq_cpp_name: break else: return # Update the type. type.type = typedef.type.type type.allow_none = type.allow_none or typedef.type.allow_none type.definition = typedef.type.definition type.derefs.extend(typedef.type.derefs) type.disallow_none = type.disallow_none or typedef.type.disallow_none type.is_const = type.is_const or typedef.type.is_const type.is_reference = type.is_reference or typedef.type.is_reference type.type_hints = type.type_hints or typedef.type.type_hints # Remember the original typedef. if type.original_typedef is None: type.original_typedef = typedef sip-6.8.6/sipbuild/installable.py000066400000000000000000000031761464421045000170140ustar00rootroot00000000000000# SPDX-License-Identifier: BSD-2-Clause # Copyright (c) 2024 Phil Thompson import os from shutil import copy2, copytree class Installable: """ Encapsulate a list of files and directories that will be installed into a target directory. """ def __init__(self, name, *, target_subdir=None): """ Initialise the installable. The optional target_subdir is the path of a sub-directory of the eventual target where the files will be installed. If target_subdir is an absolute pathname then it is used as the eventual target name. """ self.name = name self.target_subdir = target_subdir self.files = [] def get_full_target_dir(self, target_dir): """ Return the full target directory name. """ if self.target_subdir: if os.path.isabs(self.target_subdir): target_dir = self.target_subdir else: target_dir = os.path.join(target_dir, self.target_subdir) return target_dir def install(self, target_dir, installed, *, do_install=True): """ Optionally install the files in a target directory and update the given list of installed files. """ target_dir = self.get_full_target_dir(target_dir) if do_install: os.makedirs(target_dir, exist_ok=True) for fn in self.files: t_path = os.path.join(target_dir, os.path.basename(fn)) installed.append(t_path) if do_install: copy_fn = copytree if os.path.isdir(fn) else copy2 copy_fn(fn, t_path) sip-6.8.6/sipbuild/module/000077500000000000000000000000001464421045000154265ustar00rootroot00000000000000sip-6.8.6/sipbuild/module/__init__.py000066400000000000000000000004361464421045000175420ustar00rootroot00000000000000# SPDX-License-Identifier: BSD-2-Clause # Copyright (c) 2024 Phil Thompson # Publish the API. This is private to the rest of sip. from .abi_version import resolve_abi_version from .module import copy_nonshared_sources, copy_sip_h, copy_sip_pyi, module sip-6.8.6/sipbuild/module/abi_version.py000066400000000000000000000063201464421045000203010ustar00rootroot00000000000000# SPDX-License-Identifier: BSD-2-Clause # Copyright (c) 2024 Phil Thompson import os from ..exceptions import UserException # The directory containing the different module implementations. _module_source_dir = os.path.join(os.path.dirname(__file__), 'source') def get_module_source_dir(abi_major_version): """ Return the name of the directory containing the latest source of the sip module that implements the given ABI version. """ return os.path.join(_module_source_dir, abi_major_version) def get_sip_module_version(abi_major_version): """ Return the version number of the latest implementation of the sip module with the given ABI as a string. """ abi_minor_version = patch_version = None # Read the version from the header file shared with the code generator. with open(os.path.join(get_module_source_dir(abi_major_version), 'sip.h.in')) as vf: for line in vf: parts = line.strip().split() if len(parts) == 3 and parts[0] == '#define': name = parts[1] value = parts[2] if name == 'SIP_ABI_MINOR_VERSION': abi_minor_version = value elif name == 'SIP_MODULE_PATCH_VERSION': patch_version = value # These are internal errors and should never happen. if abi_minor_version is None: raise ValueError( f"'SIP_ABI_MINOR_VERSION' not found for ABI {abi_major_version}") if patch_version is None: raise ValueError( f"'SIP_MODULE_PATCH_VERSION' not found for ABI {abi_major_version}") return f'{abi_major_version}.{abi_minor_version}.{patch_version}' def resolve_abi_version(abi_version, module=True): """ Return a valid ABI version or the latest if none was given. """ # Get the major and minimum minor version of what we need. if abi_version: parts = abi_version.split('.') abi_major_version = parts[0] if not os.path.isdir(get_module_source_dir(abi_major_version)): raise UserException( f"'{abi_version}' is not a supported ABI version") if len(parts) == 1: minimum_minor_version = 0 else: try: minimum_minor_version = int(parts[1]) except ValueError: minimum_minor_version = None if len(parts) > 2 or minimum_minor_version is None: raise UserException( f"'{abi_version}' is not a valid ABI version") else: abi_major_version = sorted(os.listdir(_module_source_dir), key=int)[-1] minimum_minor_version = 0 # Get the minor version of what we actually have. module_version = get_sip_module_version(abi_major_version) _, abi_minor_version, _ = module_version.split('.') # Check we meet the minimum requirement. if int(abi_minor_version) < minimum_minor_version: raise UserException(f"'{abi_version}' is not a supported ABI version") if module: # Return the module's version. return f'{abi_major_version}.{abi_minor_version}' # Return the required version. return f'{abi_major_version}.{minimum_minor_version}' sip-6.8.6/sipbuild/module/module.py000066400000000000000000000144741464421045000172770ustar00rootroot00000000000000# SPDX-License-Identifier: BSD-2-Clause # Copyright (c) 2024 Phil Thompson import os import shutil import subprocess import sys from ..version import SIP_VERSION, SIP_VERSION_STR from .abi_version import (get_module_source_dir, get_sip_module_version, resolve_abi_version) def module(sip_module, abi_version, project, sdist, setup_cfg, sip_h, sip_rst, target_dir): """ Create the various elements of a sip module. """ # Provide some defaults. abi_major_version = resolve_abi_version(abi_version).split('.')[0] if project is None: project = sip_module.replace('.', '_') # Create the patches. patches = _create_patches(sip_module, abi_major_version, project) # The names of generated files. sdist_dir = project + '-' + patches['@SIP_MODULE_VERSION@'] sip_h_fn = 'sip.h' sip_rst_fn = 'sip.rst' if target_dir: sdist_dir = os.path.join(target_dir, sdist_dir) sip_h_fn = os.path.join(target_dir, sip_h_fn) sip_rst_fn = os.path.join(target_dir, sip_rst_fn) # Generate the required files. if sdist: _create_sdist(sdist_dir, abi_major_version, patches, setup_cfg) if sip_h: _create_sip_file(sip_h_fn, abi_major_version, patches) if sip_rst: _create_sip_file(sip_rst_fn, abi_major_version, patches) def copy_sip_h(abi_major_version, target_dir, sip_module='', version_info=True): """ Copy the sip.h file. """ patches = _create_patches(sip_module, abi_major_version, version_info=version_info) _install_source_file('sip.h', get_module_source_dir(abi_major_version), target_dir, patches) def copy_sip_pyi(abi_major_version, target_dir): """ Copy the sip.pyi file. """ shutil.copy( os.path.join(get_module_source_dir(abi_major_version), 'sip.pyi'), target_dir) def copy_nonshared_sources(abi_major_version, target_dir): """ Copy the module sources as a non-shared module. """ # Copy the patched sip.h. copy_sip_h(abi_major_version, target_dir) # Copy the remaining source code. module_source_dir = get_module_source_dir(abi_major_version) sources = [] for fn in os.listdir(module_source_dir): if fn.endswith('.c') or fn.endswith('.cpp') or fn.endswith('.h'): src_fn = os.path.join(module_source_dir, fn) dst_fn = os.path.join(target_dir, fn) shutil.copyfile(src_fn, dst_fn) if not fn.endswith('.h'): sources.append(dst_fn) return sources def _create_patches(sip_module, abi_major_version, project='', version_info=True): """ Return a dict of the patches. """ sip_module_parts = sip_module.split('.') sip_module_package_name = '.'.join(sip_module_parts[:-1]) sip_module_name = sip_module_parts[-1] # We special case this because this should be the only package requiring # the support. legacy = (sip_module == 'PyQt5.sip') if version_info: sip_version = SIP_VERSION sip_version_str = SIP_VERSION_STR else: sip_version = 0 sip_version_str = '' return { # The public patches are those that might be needed in setup.cfg or any # automatically generated user documentation. '@SIP_MODULE_FQ_NAME@': sip_module, '@SIP_MODULE_PROJECT_NAME@': project, '@SIP_MODULE_PACKAGE_NAME@': sip_module_package_name, '@SIP_MODULE_VERSION@': get_sip_module_version( abi_major_version), # These are internal to sip.h. '@_SIP_MODULE_FQ_NAME@': sip_module, '@_SIP_MODULE_NAME@': sip_module_name, '@_SIP_MODULE_SHARED@': '1' if sip_module else '0', '@_SIP_MODULE_ENTRY@': 'PyInit_' + sip_module_name, '@_SIP_MODULE_LEGACY@': "1" if legacy else "0", '@_SIP_VERSION@': hex(sip_version), '@_SIP_VERSION_STR@': sip_version_str } def _create_sdist(sdist_dir, abi_version, patches, setup_cfg): """ Create the sdist. """ # Remove any existing source directory. shutil.rmtree(sdist_dir, ignore_errors=True) os.mkdir(sdist_dir) # The source directory doesn't have sub-directories. module_source_dir = get_module_source_dir(abi_version) for name in os.listdir(module_source_dir): if name in ('setup.cfg.in', 'sip.pyi', 'sip.rst.in'): continue if name != 'MANIFEST.in' and name.endswith('.in'): name = name[:-3] # Don't install the default README if we are not using the default # setup.cfg. if name != 'README' or setup_cfg is None: _install_source_file(name, module_source_dir, sdist_dir, patches) else: shutil.copy(os.path.join(module_source_dir, name), sdist_dir) # Write setup.cfg as required. if setup_cfg is None: setup_cfg = os.path.join(module_source_dir, 'setup.cfg.in') _install_file(setup_cfg, os.path.join(sdist_dir, 'setup.cfg'), patches) # Create the sdist file using setuptools. This means any user supplied # setup.cfg should be handled correctly. saved_cwd = os.getcwd() os.chdir(sdist_dir) subprocess.run( [sys.executable, 'setup.py', '--quiet', 'sdist', '--dist-dir', '..']) os.chdir(saved_cwd) # Tidy up. shutil.rmtree(sdist_dir) def _create_sip_file(sip_file_fn, abi_version, patches): """ Create a patched file from the module source directory. """ dname, fname = os.path.split(os.path.abspath(sip_file_fn)) _install_source_file(fname, get_module_source_dir(abi_version), dname, patches) def _install_source_file(name, module_source_dir, target_dir, patches): """ Install a source file in a target directory. """ _install_file(os.path.join(module_source_dir, name) + '.in', os.path.join(target_dir, name), patches) def _install_file(name_in, name_out, patches): """ Install a file. """ # Read the file. with open(name_in) as f: data = f.read() # Patch the file. for patch_name, patch in patches.items(): data = data.replace(patch_name, patch) # Write the file. with open(name_out, 'w') as f: f.write(data) sip-6.8.6/sipbuild/module/source/000077500000000000000000000000001464421045000167265ustar00rootroot00000000000000sip-6.8.6/sipbuild/module/source/12/000077500000000000000000000000001464421045000171505ustar00rootroot00000000000000sip-6.8.6/sipbuild/module/source/12/LICENSE000066400000000000000000000024241464421045000201570ustar00rootroot00000000000000Copyright 2024 Phil Thompson Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. sip-6.8.6/sipbuild/module/source/12/MANIFEST.in000066400000000000000000000000431464421045000207030ustar00rootroot00000000000000include *.h include pyproject.toml sip-6.8.6/sipbuild/module/source/12/README.in000066400000000000000000000002001464421045000204250ustar00rootroot00000000000000sip Extension Module ==================== The sip extension module provides support for the @SIP_MODULE_PACKAGE_NAME@ package. sip-6.8.6/sipbuild/module/source/12/apiversions.c000066400000000000000000000152221464421045000216600ustar00rootroot00000000000000/* SPDX-License-Identifier: BSD-2-Clause */ /* * The implementation of the supprt for setting API versions. * * Copyright (c) 2024 Phil Thompson */ /* Remove when Python v3.12 is no longer supported. */ #define PY_SSIZE_T_CLEAN #include #include #include "sipint.h" /* * The structure that defines the version number of an API. */ typedef struct _apiVersionDef { /* The name of the API. */ const char *api_name; /* * The version number of the API. This will either be set explicitly via * a call to sip.setapi() or implicitly by an imported module. */ int version_nr; /* The next in the list of APIs. */ struct _apiVersionDef *next; } apiVersionDef; /* * The list of API versions. */ static apiVersionDef *api_versions = NULL; /* * Forward declarations. */ static int add_api(const char *api, int version_nr); static apiVersionDef *find_api(const char *api); /* * See if a range of versions of a particular API is enabled. */ int sip_api_is_api_enabled(const char *name, int from, int to) { const apiVersionDef *avd; if ((avd = find_api(name)) == NULL) return FALSE; if (from > 0 && avd->version_nr < from) return FALSE; if (to > 0 && avd->version_nr >= to) return FALSE; return TRUE; } /* * Initialise the the API for a module and return a negative value on error. */ int sipInitAPI(sipExportedModuleDef *em, PyObject *mod_dict) { int *apis, i; sipVersionedFunctionDef *vf; sipTypeDef **tdp; /* See if the module defines any APIs. */ if ((apis = em->em_versions) != NULL) { while (apis[0] >= 0) { /* * See if it is an API definition rather than a range * definition. */ if (apis[2] < 0) { const char *api_name; const apiVersionDef *avd; api_name = sipNameFromPool(em, apis[0]); /* Use the default version if not already set explicitly. */ if ((avd = find_api(api_name)) == NULL) if (add_api(api_name, apis[1]) < 0) return -1; } apis += 3; } } /* Add any versioned global functions to the module dictionary. */ if ((vf = em->em_versioned_functions) != NULL) { while (vf->vf_name >= 0) { if (sipIsRangeEnabled(em, vf->vf_api_range)) { const char *func_name = sipNameFromPool(em, vf->vf_name); PyMethodDef *pmd; PyObject *py_func; if ((pmd = sip_api_malloc(sizeof (PyMethodDef))) == NULL) return -1; pmd->ml_name = func_name; pmd->ml_meth = vf->vf_function; pmd->ml_flags = vf->vf_flags; pmd->ml_doc = vf->vf_docstring; if ((py_func = PyCFunction_New(pmd, NULL)) == NULL) return -1; if (PyDict_SetItemString(mod_dict, func_name, py_func) < 0) { Py_DECREF(py_func); return -1; } Py_DECREF(py_func); } ++vf; } } /* Update the types table according to any version information. */ for (tdp = em->em_types, i = 0; i < em->em_nrtypes; ++i, ++tdp) { sipTypeDef *td; if ((td = *tdp) != NULL && td->td_version >= 0) { do { if (sipIsRangeEnabled(em, td->td_version)) { /* Update the type with the enabled version. */ *tdp = td; break; } } while ((td = td->td_next_version) != NULL); /* * If there is no enabled version then stub the disabled version * so that we don't lose the name from the (sorted) types table. */ if (td == NULL) sipTypeSetStub(*tdp); } } return 0; } /* * Get the version number for an API. */ PyObject *sipGetAPI(PyObject *self, PyObject *args) { const char *api; const apiVersionDef *avd; (void)self; if (sip_api_deprecated(NULL, "getapi") < 0) return NULL; if (!PyArg_ParseTuple(args, "s:getapi", &api)) return NULL; if ((avd = find_api(api)) == NULL) { PyErr_Format(PyExc_ValueError, "unknown API '%s'", api); return NULL; } return PyLong_FromLong(avd->version_nr); } /* * Set the version number for an API. */ PyObject *sipSetAPI(PyObject *self, PyObject *args) { const char *api; int version_nr; const apiVersionDef *avd; (void)self; if (sip_api_deprecated(NULL, "setapi") < 0) return NULL; if (!PyArg_ParseTuple(args, "si:setapi", &api, &version_nr)) return NULL; if (version_nr < 1) { PyErr_Format(PyExc_ValueError, "API version numbers must be greater or equal to 1, not %d", version_nr); return NULL; } if ((avd = find_api(api)) == NULL) { char *api_copy; /* Make a deep copy of the name. */ if ((api_copy = sip_api_malloc(strlen(api) + 1)) == NULL) return NULL; strcpy(api_copy, api); if (add_api(api_copy, version_nr) < 0) return NULL; } else if (avd->version_nr != version_nr) { PyErr_Format(PyExc_ValueError, "API '%s' has already been set to version %d", api, avd->version_nr); return NULL; } Py_INCREF(Py_None); return Py_None; } /* * Add a new API to the global list returning a negative value on error. */ static int add_api(const char *api, int version_nr) { apiVersionDef *avd; if ((avd = sip_api_malloc(sizeof (apiVersionDef))) == NULL) return -1; avd->api_name = api; avd->version_nr = version_nr; avd->next = api_versions; api_versions = avd; return 0; } /* * Return the definition for the given API, or NULL if there was none. */ static apiVersionDef *find_api(const char *api) { apiVersionDef *avd; for (avd = api_versions; avd != NULL; avd = avd->next) if (strcmp(avd->api_name, api) == 0) break; return avd; } /* * Return TRUE if a range defined by a range index is enabled. */ int sipIsRangeEnabled(sipExportedModuleDef *em, int range_index) { int *range = &em->em_versions[range_index * 3]; const char *api_name = sipNameFromPool(em, range[0]); return sip_api_is_api_enabled(api_name, range[1], range[2]); } sip-6.8.6/sipbuild/module/source/12/descriptors.c000066400000000000000000000314341464421045000216620ustar00rootroot00000000000000/* SPDX-License-Identifier: BSD-2-Clause */ /* * The implementation of the different descriptors. * * Copyright (c) 2024 Phil Thompson */ /* Remove when Python v3.12 is no longer supported. */ #define PY_SSIZE_T_CLEAN #include #include "sipint.h" /***************************************************************************** * A method descriptor. We don't use the similar Python descriptor because it * doesn't support a method having static and non-static overloads, and we * handle mixins via a delegate. *****************************************************************************/ /* Forward declarations of slots. */ static PyObject *sipMethodDescr_descr_get(PyObject *self, PyObject *obj, PyObject *type); static PyObject *sipMethodDescr_repr(PyObject *self); static int sipMethodDescr_traverse(PyObject *self, visitproc visit, void *arg); static int sipMethodDescr_clear(PyObject *self); static void sipMethodDescr_dealloc(PyObject *self); /* * The object data structure. */ typedef struct _sipMethodDescr { PyObject_HEAD /* The method definition. */ PyMethodDef *pmd; /* The mixin name, if any. */ PyObject *mixin_name; } sipMethodDescr; /* * The type data structure. */ PyTypeObject sipMethodDescr_Type = { PyVarObject_HEAD_INIT(NULL, 0) "sip.methoddescriptor", /* tp_name */ sizeof (sipMethodDescr), /* tp_basicsize */ 0, /* tp_itemsize */ sipMethodDescr_dealloc, /* tp_dealloc */ 0, /* tp_print */ 0, /* tp_getattr */ 0, /* tp_setattr */ 0, /* tp_compare */ sipMethodDescr_repr, /* tp_repr */ 0, /* tp_as_number */ 0, /* tp_as_sequence */ 0, /* tp_as_mapping */ 0, /* tp_hash */ 0, /* tp_call */ 0, /* tp_str */ 0, /* tp_getattro */ 0, /* tp_setattro */ 0, /* tp_as_buffer */ Py_TPFLAGS_DEFAULT|Py_TPFLAGS_HAVE_GC, /* tp_flags */ 0, /* tp_doc */ sipMethodDescr_traverse,/* tp_traverse */ sipMethodDescr_clear, /* tp_clear */ 0, /* tp_richcompare */ 0, /* tp_weaklistoffset */ 0, /* tp_iter */ 0, /* tp_iternext */ 0, /* tp_methods */ 0, /* tp_members */ 0, /* tp_getset */ 0, /* tp_base */ 0, /* tp_dict */ sipMethodDescr_descr_get, /* tp_descr_get */ 0, /* tp_descr_set */ 0, /* tp_dictoffset */ 0, /* tp_init */ 0, /* tp_alloc */ 0, /* tp_new */ 0, /* tp_free */ 0, /* tp_is_gc */ 0, /* tp_bases */ 0, /* tp_mro */ 0, /* tp_cache */ 0, /* tp_subclasses */ 0, /* tp_weaklist */ 0, /* tp_del */ 0, /* tp_version_tag */ 0, /* tp_finalize */ 0, /* tp_vectorcall */ }; /* * Return a new method descriptor for the given method. */ PyObject *sipMethodDescr_New(PyMethodDef *pmd) { PyObject *descr = PyType_GenericAlloc(&sipMethodDescr_Type, 0); if (descr != NULL) { ((sipMethodDescr *)descr)->pmd = pmd; ((sipMethodDescr *)descr)->mixin_name = NULL; } return descr; } /* * Return a new method descriptor based on an existing one and a mixin name. */ PyObject *sipMethodDescr_Copy(PyObject *orig, PyObject *mixin_name) { PyObject *descr = PyType_GenericAlloc(&sipMethodDescr_Type, 0); if (descr != NULL) { ((sipMethodDescr *)descr)->pmd = ((sipMethodDescr *)orig)->pmd; ((sipMethodDescr *)descr)->mixin_name = mixin_name; Py_INCREF(mixin_name); } return descr; } /* * The descriptor's descriptor get slot. */ static PyObject *sipMethodDescr_descr_get(PyObject *self, PyObject *obj, PyObject *type) { sipMethodDescr *md = (sipMethodDescr *)self; (void)type; if (obj == Py_None) obj = NULL; else if (md->mixin_name != NULL) obj = PyObject_GetAttr(obj, md->mixin_name); return PyCFunction_New(md->pmd, obj); } /* * The descriptor's repr slot. This is for the benefit of cProfile which seems * to determine attribute names differently to the rest of Python. */ static PyObject *sipMethodDescr_repr(PyObject *self) { sipMethodDescr *md = (sipMethodDescr *)self; return PyUnicode_FromFormat("", md->pmd->ml_name); } /* * The descriptor's traverse slot. */ static int sipMethodDescr_traverse(PyObject *self, visitproc visit, void *arg) { if (((sipMethodDescr *)self)->mixin_name != NULL) { int vret = visit(((sipMethodDescr *)self)->mixin_name, arg); if (vret != 0) return vret; } return 0; } /* * The descriptor's clear slot. */ static int sipMethodDescr_clear(PyObject *self) { PyObject *tmp = ((sipMethodDescr *)self)->mixin_name; ((sipMethodDescr *)self)->mixin_name = NULL; Py_XDECREF(tmp); return 0; } /* * The descriptor's dealloc slot. */ static void sipMethodDescr_dealloc(PyObject *self) { PyObject_GC_UnTrack(self); sipMethodDescr_clear(self); Py_TYPE(self)->tp_free(self); } /***************************************************************************** * A variable descriptor. We don't use the similar Python descriptor because * it doesn't support static variables. *****************************************************************************/ /* Forward declarations of slots. */ static PyObject *sipVariableDescr_descr_get(PyObject *self, PyObject *obj, PyObject *type); static int sipVariableDescr_descr_set(PyObject *self, PyObject *obj, PyObject *value); static int sipVariableDescr_traverse(PyObject *self, visitproc visit, void *arg); static int sipVariableDescr_clear(PyObject *self); static void sipVariableDescr_dealloc(PyObject *self); /* * The object data structure. */ typedef struct _sipVariableDescr { PyObject_HEAD /* The getter/setter definition. */ sipVariableDef *vd; /* The generated type definition. */ const sipTypeDef *td; /* The generated container definition. */ const sipContainerDef *cod; /* The mixin name, if any. */ PyObject *mixin_name; } sipVariableDescr; /* * The type data structure. */ PyTypeObject sipVariableDescr_Type = { PyVarObject_HEAD_INIT(NULL, 0) "sip.variabledescriptor", /* tp_name */ sizeof (sipVariableDescr), /* tp_basicsize */ 0, /* tp_itemsize */ sipVariableDescr_dealloc, /* tp_dealloc */ 0, /* tp_print */ 0, /* tp_getattr */ 0, /* tp_setattr */ 0, /* tp_compare */ 0, /* tp_repr */ 0, /* tp_as_number */ 0, /* tp_as_sequence */ 0, /* tp_as_mapping */ 0, /* tp_hash */ 0, /* tp_call */ 0, /* tp_str */ 0, /* tp_getattro */ 0, /* tp_setattro */ 0, /* tp_as_buffer */ Py_TPFLAGS_DEFAULT|Py_TPFLAGS_HAVE_GC, /* tp_flags */ 0, /* tp_doc */ sipVariableDescr_traverse, /* tp_traverse */ sipVariableDescr_clear, /* tp_clear */ 0, /* tp_richcompare */ 0, /* tp_weaklistoffset */ 0, /* tp_iter */ 0, /* tp_iternext */ 0, /* tp_methods */ 0, /* tp_members */ 0, /* tp_getset */ 0, /* tp_base */ 0, /* tp_dict */ sipVariableDescr_descr_get, /* tp_descr_get */ sipVariableDescr_descr_set, /* tp_descr_set */ 0, /* tp_dictoffset */ 0, /* tp_init */ 0, /* tp_alloc */ 0, /* tp_new */ 0, /* tp_free */ 0, /* tp_is_gc */ 0, /* tp_bases */ 0, /* tp_mro */ 0, /* tp_cache */ 0, /* tp_subclasses */ 0, /* tp_weaklist */ 0, /* tp_del */ 0, /* tp_version_tag */ 0, /* tp_finalize */ 0, /* tp_vectorcall */ }; /* Forward declarations. */ static int get_instance_address(sipVariableDescr *vd, PyObject *obj, void **addrp); /* * Return a new method descriptor for the given getter/setter. */ PyObject *sipVariableDescr_New(sipVariableDef *vd, const sipTypeDef *td, const sipContainerDef *cod) { PyObject *descr = PyType_GenericAlloc(&sipVariableDescr_Type, 0); if (descr != NULL) { ((sipVariableDescr *)descr)->vd = vd; ((sipVariableDescr *)descr)->td = td; ((sipVariableDescr *)descr)->cod = cod; ((sipVariableDescr *)descr)->mixin_name = NULL; } return descr; } /* * Return a new variable descriptor based on an existing one and a mixin name. */ PyObject *sipVariableDescr_Copy(PyObject *orig, PyObject *mixin_name) { PyObject *descr = PyType_GenericAlloc(&sipVariableDescr_Type, 0); if (descr != NULL) { ((sipVariableDescr *)descr)->vd = ((sipVariableDescr *)orig)->vd; ((sipVariableDescr *)descr)->td = ((sipVariableDescr *)orig)->td; ((sipVariableDescr *)descr)->cod = ((sipVariableDescr *)orig)->cod; ((sipVariableDescr *)descr)->mixin_name = mixin_name; Py_INCREF(mixin_name); } return descr; } /* * The descriptor's descriptor get slot. */ static PyObject *sipVariableDescr_descr_get(PyObject *self, PyObject *obj, PyObject *type) { sipVariableDescr *vd = (sipVariableDescr *)self; void *addr; if (get_instance_address(vd, obj, &addr) < 0) return NULL; return ((sipVariableGetterFunc)vd->vd->vd_getter)(addr, obj, type); } /* * The descriptor's descriptor set slot. */ static int sipVariableDescr_descr_set(PyObject *self, PyObject *obj, PyObject *value) { sipVariableDescr *vd = (sipVariableDescr *)self; void *addr; /* Check that the value isn't const. */ if (vd->vd->vd_setter == NULL) { PyErr_Format(PyExc_AttributeError, "'%s' object attribute '%s' is read-only", sipPyNameOfContainer(vd->cod, vd->td), vd->vd->vd_name); return -1; } if (get_instance_address(vd, obj, &addr) < 0) return -1; return ((sipVariableSetterFunc)vd->vd->vd_setter)(addr, value, obj); } /* * Return the C/C++ address of any instance. */ static int get_instance_address(sipVariableDescr *vd, PyObject *obj, void **addrp) { void *addr; if (vd->vd->vd_type == ClassVariable) { addr = NULL; } else { /* Check that access was via an instance. */ if (obj == NULL || obj == Py_None) { PyErr_Format(PyExc_AttributeError, "'%s' object attribute '%s' is an instance attribute", sipPyNameOfContainer(vd->cod, vd->td), vd->vd->vd_name); return -1; } if (vd->mixin_name != NULL) obj = PyObject_GetAttr(obj, vd->mixin_name); /* Get the C++ instance. */ if ((addr = sip_api_get_cpp_ptr((sipSimpleWrapper *)obj, vd->td)) == NULL) return -1; } *addrp = addr; return 0; } /* * The descriptor's traverse slot. */ static int sipVariableDescr_traverse(PyObject *self, visitproc visit, void *arg) { if (((sipVariableDescr *)self)->mixin_name != NULL) { int vret = visit(((sipVariableDescr *)self)->mixin_name, arg); if (vret != 0) return vret; } return 0; } /* * The descriptor's clear slot. */ static int sipVariableDescr_clear(PyObject *self) { PyObject *tmp = ((sipVariableDescr *)self)->mixin_name; ((sipVariableDescr *)self)->mixin_name = NULL; Py_XDECREF(tmp); return 0; } /* * The descriptor's dealloc slot. */ static void sipVariableDescr_dealloc(PyObject *self) { PyObject_GC_UnTrack(self); sipVariableDescr_clear(self); Py_TYPE(self)->tp_free(self); } sip-6.8.6/sipbuild/module/source/12/int_convertors.c000066400000000000000000000174411464421045000224010ustar00rootroot00000000000000/* SPDX-License-Identifier: BSD-2-Clause */ /* * The implementation of the Python object to C/C++ integer convertors. * * Copyright (c) 2024 Phil Thompson */ /* * NOTES * * The legacy integer conversions (ie. without support for overflow checking) * are flawed and inconsistent. Large Python signed values were converted to * -1 whereas small values were truncated. When converting function arguments * all overlows were ignored, however when converting the results returned by * Python re-implementations then large Python values raised an exception * whereas small values were truncated. * * With the new integer conversions large Python signed values will always * raise an overflow exception (even if overflow checking is disabled). This * is because a truncated value is not available - it would have to be * computed. This may cause new exceptions to be raised but is justified in * that the value that was being used bore no relation to the original value. */ /* Remove when Python v3.12 is no longer supported. */ #define PY_SSIZE_T_CLEAN #include #include #include "sipint.h" /* Wrappers to deal with lack of long long support. */ #if defined(HAVE_LONG_LONG) #define SIPLong_AsLongLong PyLong_AsLongLong #define SIP_LONG_LONG PY_LONG_LONG #define SIP_LONG_LONG_FORMAT "%lld" #define SIP_UNSIGNED_LONG_LONG_FORMAT "%llu" #else #define SIPLong_AsLongLong PyLong_AsLong #define SIP_LONG_LONG long #define SIP_LONG_LONG_FORMAT "%ld" #define SIP_UNSIGNED_LONG_LONG_FORMAT "%lu" #endif static int overflow_checking = FALSE; /* Check for overflows. */ static SIP_LONG_LONG long_as_long_long(PyObject *o, SIP_LONG_LONG min, SIP_LONG_LONG max); static unsigned long long_as_unsigned_long(PyObject *o, unsigned long max); static void raise_signed_overflow(SIP_LONG_LONG min, SIP_LONG_LONG max); static void raise_unsigned_overflow(unsigned SIP_LONG_LONG max); /* * Enable or disable overflow checking (Python API). */ PyObject *sipEnableOverflowChecking(PyObject *self, PyObject *args) { int enable; (void)self; if (PyArg_ParseTuple(args, "i:enableoverflowchecking", &enable)) { PyObject *res; res = (sip_api_enable_overflow_checking(enable) ? Py_True : Py_False); Py_INCREF(res); return res; } return NULL; } /* * Enable or disable overflow checking (C API). */ int sip_api_enable_overflow_checking(int enable) { int was_enabled = overflow_checking; overflow_checking = enable; return was_enabled; } /* * Convert a Python object to a C++ bool (returned as an int). */ int sip_api_convert_to_bool(PyObject *o) { int was_enabled, v; /* Convert the object to an int while checking for overflow. */ was_enabled = sip_api_enable_overflow_checking(TRUE); v = sip_api_long_as_int(o); sip_api_enable_overflow_checking(was_enabled); if (PyErr_Occurred()) { if (PyErr_ExceptionMatches(PyExc_OverflowError)) { PyErr_Clear(); /* The value must have been non-zero. */ v = 1; } else { PyErr_Format(PyExc_TypeError, "a 'bool' is expected not '%s'", Py_TYPE(o)->tp_name); v = -1; } } else if (v != 0) { v = 1; } return v; } /* * Convert a Python object to a C char. */ char sip_api_long_as_char(PyObject *o) { return (char)long_as_long_long(o, CHAR_MIN, CHAR_MAX); } /* * Convert a Python object to a C signed char. */ signed char sip_api_long_as_signed_char(PyObject *o) { return (signed char)long_as_long_long(o, SCHAR_MIN, SCHAR_MAX); } /* * Convert a Python object to a C unsigned char. */ unsigned char sip_api_long_as_unsigned_char(PyObject *o) { return (unsigned char)long_as_unsigned_long(o, UCHAR_MAX); } /* * Convert a Python object to a C short. */ short sip_api_long_as_short(PyObject *o) { return (short)long_as_long_long(o, SHRT_MIN, SHRT_MAX); } /* * Convert a Python object to a C unsigned short. */ unsigned short sip_api_long_as_unsigned_short(PyObject *o) { return (unsigned short)long_as_unsigned_long(o, USHRT_MAX); } /* * Convert a Python object to a C int. */ int sip_api_long_as_int(PyObject *o) { return (int)long_as_long_long(o, INT_MIN, INT_MAX); } /* * Convert a Python object to a C unsigned int. */ unsigned sip_api_long_as_unsigned_int(PyObject *o) { return (unsigned)long_as_unsigned_long(o, UINT_MAX); } /* * Convert a Python object to a C size_t. */ size_t sip_api_long_as_size_t(PyObject *o) { return (size_t)long_as_unsigned_long(o, SIZE_MAX); } /* * Convert a Python object to a C long. */ long sip_api_long_as_long(PyObject *o) { return (long)long_as_long_long(o, LONG_MIN, LONG_MAX); } /* * Convert a Python object to a C unsigned long. */ unsigned long sip_api_long_as_unsigned_long(PyObject *o) { return long_as_unsigned_long(o, ULONG_MAX); } #if defined(HAVE_LONG_LONG) /* * Convert a Python object to a C long long. */ PY_LONG_LONG sip_api_long_as_long_long(PyObject *o) { return long_as_long_long(o, LLONG_MIN, LLONG_MAX); } /* * Convert a Python object to a C unsigned long long. */ unsigned PY_LONG_LONG sip_api_long_as_unsigned_long_long(PyObject *o) { unsigned PY_LONG_LONG value; /* * Note that this doesn't handle Python v2 int objects, but the old * convertors didn't either. */ PyErr_Clear(); if (overflow_checking) { value = PyLong_AsUnsignedLongLong(o); if (PyErr_Occurred()) { /* Provide a better exception message. */ if (PyErr_ExceptionMatches(PyExc_OverflowError)) raise_unsigned_overflow(ULLONG_MAX); } } else { value = PyLong_AsUnsignedLongLongMask(o); } return value; } #endif /* * Convert a Python object to a long long checking that the value is within a * range if overflow checking is enabled. */ static SIP_LONG_LONG long_as_long_long(PyObject *o, SIP_LONG_LONG min, SIP_LONG_LONG max) { SIP_LONG_LONG value; PyErr_Clear(); value = SIPLong_AsLongLong(o); if (PyErr_Occurred()) { /* Provide a better exception message. */ if (PyErr_ExceptionMatches(PyExc_OverflowError)) raise_signed_overflow(min, max); } else if (overflow_checking && (value < min || value > max)) { raise_signed_overflow(min, max); } return value; } /* * Convert a Python object to an unsigned long checking that the value is * within a range if overflow checking is enabled. */ static unsigned long long_as_unsigned_long(PyObject *o, unsigned long max) { unsigned long value; PyErr_Clear(); if (overflow_checking) { value = PyLong_AsUnsignedLong(o); if (PyErr_Occurred()) { /* Provide a better exception message. */ if (PyErr_ExceptionMatches(PyExc_OverflowError)) raise_unsigned_overflow(max); } else if (value > max) { raise_unsigned_overflow(max); } } else { value = PyLong_AsUnsignedLongMask(o); } return value; } /* * Raise an overflow exception if a signed value is out of range. */ static void raise_signed_overflow(SIP_LONG_LONG min, SIP_LONG_LONG max) { PyErr_Format(PyExc_OverflowError, "value must be in the range " SIP_LONG_LONG_FORMAT " to " SIP_LONG_LONG_FORMAT, min, max); } /* * Raise an overflow exception if an unsigned value is out of range. */ static void raise_unsigned_overflow(unsigned SIP_LONG_LONG max) { PyErr_Format(PyExc_OverflowError, "value must be in the range 0 to " SIP_UNSIGNED_LONG_LONG_FORMAT, max); } sip-6.8.6/sipbuild/module/source/12/objmap.c000066400000000000000000000317441464421045000205750ustar00rootroot00000000000000/* SPDX-License-Identifier: BSD-2-Clause */ /* * This module implements a hash table class for mapping C/C++ addresses to the * corresponding wrapped Python object. * * Copyright (c) 2024 Phil Thompson */ #include #include #include "sipint.h" #define hash_1(k,s) (((uintptr_t)(k)) % (s)) #define hash_2(k,s) ((s) - 2 - (hash_1((k),(s)) % ((s) - 2))) /* Prime numbers to use as hash table sizes. */ static uintptr_t hash_primes[] = { 521, 1031, 2053, 4099, 8209, 16411, 32771, 65537, 131101, 262147, 524309, 1048583, 2097169, 4194319, 8388617, 16777259, 33554467, 67108879, 134217757, 268435459, 536870923, 1073741827, 2147483659U,0 }; static sipHashEntry *newHashTable(uintptr_t); static sipHashEntry *findHashEntry(sipObjectMap *,void *); static void add_object(sipObjectMap *om, void *addr, sipSimpleWrapper *val); static void add_aliases(sipObjectMap *om, void *addr, sipSimpleWrapper *val, const sipClassTypeDef *base_ctd, const sipClassTypeDef *ctd); static int remove_object(sipObjectMap *om, void *addr, sipSimpleWrapper *val); static void remove_aliases(sipObjectMap *om, void *addr, sipSimpleWrapper *val, const sipClassTypeDef *base_ctd, const sipClassTypeDef *ctd); static void reorganiseMap(sipObjectMap *om); static void *getUnguardedPointer(sipSimpleWrapper *w); /* * Initialise an object map. */ void sipOMInit(sipObjectMap *om) { om -> primeIdx = 0; om -> unused = om -> size = hash_primes[om -> primeIdx]; om -> stale = 0; om -> hash_array = newHashTable(om -> size); } /* * Finalise an object map. */ void sipOMFinalise(sipObjectMap *om) { sip_api_free(om -> hash_array); } /* * Allocate and initialise a new hash table. */ static sipHashEntry *newHashTable(uintptr_t size) { size_t nbytes; sipHashEntry *hashtab; nbytes = sizeof (sipHashEntry) * size; if ((hashtab = (sipHashEntry *)sip_api_malloc(nbytes)) != NULL) memset(hashtab,0,nbytes); return hashtab; } /* * Return a pointer to the hash entry that is used, or should be used, for the * given C/C++ address. */ static sipHashEntry *findHashEntry(sipObjectMap *om,void *key) { uintptr_t hash, inc; void *hek; hash = hash_1(key,om -> size); inc = hash_2(key,om -> size); while ((hek = om -> hash_array[hash].key) != NULL && hek != key) hash = (hash + inc) % om -> size; return &om -> hash_array[hash]; } /* * Return the wrapped Python object of a specific type for a C/C++ address or * NULL if it wasn't found. */ sipSimpleWrapper *sipOMFindObject(sipObjectMap *om, void *key, const sipTypeDef *td) { sipHashEntry *he = findHashEntry(om, key); sipSimpleWrapper *sw; PyTypeObject *py_type = sipTypeAsPyTypeObject(td); /* Go through each wrapped object at this address. */ for (sw = he->first; sw != NULL; sw = sw->next) { sipSimpleWrapper *unaliased; unaliased = (sipIsAlias(sw) ? (sipSimpleWrapper *)sw->data : sw); /* * If the reference count is 0 then it is in the process of being * deleted, so ignore it. It's not completely clear how this can * happen (but it can) because it implies that the garbage collection * code is being re-entered (and there are guards in place to prevent * this). */ if (Py_REFCNT(unaliased) == 0) continue; /* Ignore it if the C/C++ address is no longer valid. */ if (sip_api_get_address(unaliased) == NULL) continue; /* * If this wrapped object is of the given type, or a sub-type of it, * then we assume it is the same C++ object. */ if (PyObject_TypeCheck(unaliased, py_type)) return unaliased; } return NULL; } /* * Add a C/C++ address and the corresponding wrapped Python object to the map. */ void sipOMAddObject(sipObjectMap *om, sipSimpleWrapper *val) { void *addr = getUnguardedPointer(val); const sipClassTypeDef *base_ctd; /* Add the object. */ add_object(om, addr, val); /* Add any aliases. */ base_ctd = (const sipClassTypeDef *)((sipWrapperType *)Py_TYPE(val))->wt_td; add_aliases(om, addr, val, base_ctd, base_ctd); } /* * Add an alias for any address that is different when cast to a super-type. */ static void add_aliases(sipObjectMap *om, void *addr, sipSimpleWrapper *val, const sipClassTypeDef *base_ctd, const sipClassTypeDef *ctd) { const sipEncodedTypeDef *sup; /* See if there are any super-classes. */ if ((sup = ctd->ctd_supers) != NULL) { sipClassTypeDef *sup_ctd = sipGetGeneratedClassType(sup, ctd); /* Recurse up the hierachy for the first super-class. */ add_aliases(om, addr, val, base_ctd, sup_ctd); /* * We only check for aliases for subsequent super-classes because the * first one can never need one. */ while (!sup++->sc_flag) { void *sup_addr; sup_ctd = sipGetGeneratedClassType(sup, ctd); /* Recurse up the hierachy for the remaining super-classes. */ add_aliases(om, addr, val, base_ctd, sup_ctd); sup_addr = (*base_ctd->ctd_cast)(addr, (sipTypeDef *)sup_ctd); if (sup_addr != addr) { sipSimpleWrapper *alias; /* Note that we silently ignore errors. */ if ((alias = sip_api_malloc(sizeof (sipSimpleWrapper))) != NULL) { /* * An alias is basically a bit-wise copy of the Python * object but only to ensure the fields we are subverting * are in the right place. An alias should never be passed * to the Python API. */ *alias = *val; alias->sw_flags = (val->sw_flags & SIP_SHARE_MAP) | SIP_ALIAS; alias->data = val; alias->next = NULL; add_object(om, sup_addr, alias); } } } } } /* * Add a wrapper (which may be an alias) to the map. */ static void add_object(sipObjectMap *om, void *addr, sipSimpleWrapper *val) { sipHashEntry *he = findHashEntry(om, addr); /* * If the bucket is in use then we appear to have several objects at the * same address. */ if (he->first != NULL) { /* * This can happen for three reasons. A variable of one class can be * declared at the start of another class. Therefore there are two * objects, of different classes, with the same address. The second * reason is that the old C/C++ object has been deleted by C/C++ but we * didn't get to find out for some reason, and a new C/C++ instance has * been created at the same address. The third reason is if we are in * the process of deleting a Python object but the C++ object gets * wrapped again because the C++ dtor called a method that has been * re-implemented in Python. The absence of the SIP_SHARE_MAP flag * tells us that a new C++ instance has just been created and so we * know the second reason is the correct one so we mark the old * pointers as invalid and reuse the entry. Otherwise we just add this * one to the existing list of objects at this address. */ if (!(val->sw_flags & SIP_SHARE_MAP)) { sipSimpleWrapper *sw = he->first; he->first = NULL; while (sw != NULL) { sipSimpleWrapper *next = sw->next; if (sipIsAlias(sw)) { sip_api_free(sw); } else { /* * We are removing it from the map here. We first have to * call the destructor as the destructor itself might end * up trying to remove the wrapper and its aliases from the * map. */ sip_api_instance_destroyed(sw); } sw = next; } } val->next = he->first; he->first = val; return; } /* See if the bucket was unused or stale. */ if (he->key == NULL) { he->key = addr; om->unused--; } else { om->stale--; } /* Add the rest of the new value. */ he->first = val; val->next = NULL; reorganiseMap(om); } /* * Reorganise a map if it is running short of space. */ static void reorganiseMap(sipObjectMap *om) { uintptr_t old_size, i; sipHashEntry *ohe, *old_tab; /* Don't bother if it still has more than 12% available. */ if (om -> unused > om -> size >> 3) return; /* * If reorganising (ie. making the stale buckets unused) using the same * sized table would make 25% available then do that. Otherwise use a * bigger table (if possible). */ if (om -> unused + om -> stale < om -> size >> 2 && hash_primes[om -> primeIdx + 1] != 0) om -> primeIdx++; old_size = om -> size; old_tab = om -> hash_array; om -> unused = om -> size = hash_primes[om -> primeIdx]; om -> stale = 0; om -> hash_array = newHashTable(om -> size); /* Transfer the entries from the old table to the new one. */ ohe = old_tab; for (i = 0; i < old_size; ++i) { if (ohe -> key != NULL && ohe -> first != NULL) { *findHashEntry(om,ohe -> key) = *ohe; om -> unused--; } ++ohe; } sip_api_free(old_tab); } /* * Remove a C/C++ object from the table. Return 0 if it was removed * successfully. */ int sipOMRemoveObject(sipObjectMap *om, sipSimpleWrapper *val) { void *addr; const sipClassTypeDef *base_ctd; /* Handle the trivial case. */ if (sipNotInMap(val)) return 0; if ((addr = getUnguardedPointer(val)) == NULL) return 0; /* Remove any aliases. */ base_ctd = (const sipClassTypeDef *)((sipWrapperType *)Py_TYPE(val))->wt_td; remove_aliases(om, addr, val, base_ctd, base_ctd); /* Remove the object. */ return remove_object(om, addr, val); } /* * Remove an alias for any address that is different when cast to a super-type. */ static void remove_aliases(sipObjectMap *om, void *addr, sipSimpleWrapper *val, const sipClassTypeDef *base_ctd, const sipClassTypeDef *ctd) { const sipEncodedTypeDef *sup; /* See if there are any super-classes. */ if ((sup = ctd->ctd_supers) != NULL) { sipClassTypeDef *sup_ctd = sipGetGeneratedClassType(sup, ctd); /* Recurse up the hierachy for the first super-class. */ remove_aliases(om, addr, val, base_ctd, sup_ctd); /* * We only check for aliases for subsequent super-classes because the * first one can never need one. */ while (!sup++->sc_flag) { void *sup_addr; sup_ctd = sipGetGeneratedClassType(sup, ctd); /* Recurse up the hierachy for the remaining super-classes. */ remove_aliases(om, addr, val, base_ctd, sup_ctd); sup_addr = (*base_ctd->ctd_cast)(addr, (sipTypeDef *)sup_ctd); if (sup_addr != addr) remove_object(om, sup_addr, val); } } } /* * Remove a wrapper from the map. */ static int remove_object(sipObjectMap *om, void *addr, sipSimpleWrapper *val) { sipHashEntry *he = findHashEntry(om, addr); sipSimpleWrapper **swp; for (swp = &he->first; *swp != NULL; swp = &(*swp)->next) { sipSimpleWrapper *sw, *next; int do_remove; sw = *swp; next = sw->next; if (sipIsAlias(sw)) { if (sw->data == val) { sip_api_free(sw); do_remove = TRUE; } else { do_remove = FALSE; } } else { do_remove = (sw == val); } if (do_remove) { *swp = next; /* * If the bucket is now empty then count it as stale. Note that we * do not NULL the key and count it as unused because that might * throw out the search for another entry that wanted to go here, * found it already occupied, and was put somewhere else. In other * words, searches must be repeatable until we reorganise the * table. */ if (he->first == NULL) om->stale++; return 0; } } return -1; } /* * Return the unguarded pointer to a C/C++ instance, ie. the pointer was valid * but may longer be. */ static void *getUnguardedPointer(sipSimpleWrapper *w) { return (w->access_func != NULL) ? w->access_func(w, UnguardedPointer) : w->data; } sip-6.8.6/sipbuild/module/source/12/pyproject.toml000066400000000000000000000000711464421045000220620ustar00rootroot00000000000000[build-system] requires = ["setuptools >=30.3", "wheel"] sip-6.8.6/sipbuild/module/source/12/qtlib.c000066400000000000000000000437001464421045000204330ustar00rootroot00000000000000/* SPDX-License-Identifier: BSD-2-Clause */ /* * The SIP library code that implements the interface to the optional module * supplied Qt support. * * Copyright (c) 2024 Phil Thompson */ /* Remove when Python v3.12 is no longer supported. */ #define PY_SSIZE_T_CLEAN #include #include #include #include "sipint.h" /* This is how Qt "types" signals and slots. */ #define isQtSlot(s) (*(s) == '1') #define isQtSignal(s) (*(s) == '2') static PyObject *getWeakRef(PyObject *obj); static char *sipStrdup(const char *); static void *createUniversalSlot(sipWrapper *txSelf, const char *sig, PyObject *rxObj, const char *slot, const char **member, int flags); static void *findSignal(void *txrx, const char **sig); static void *newSignal(void *txrx, const char **sig); /* * Find an existing signal. */ static void *findSignal(void *txrx, const char **sig) { if (sipQtSupport->qt_find_universal_signal != NULL) txrx = sipQtSupport->qt_find_universal_signal(txrx, sig); return txrx; } /* * Return a usable signal, creating a new universal signal if needed. */ static void *newSignal(void *txrx, const char **sig) { void *new_txrx = findSignal(txrx, sig); if (new_txrx == NULL && sipQtSupport->qt_create_universal_signal != NULL) new_txrx = sipQtSupport->qt_create_universal_signal(txrx, sig); return new_txrx; } /* * Create a universal slot. Returns a pointer to it or 0 if there was an * error. */ static void *createUniversalSlot(sipWrapper *txSelf, const char *sig, PyObject *rxObj, const char *slot, const char **member, int flags) { void *us; assert(sipQtSupport->qt_create_universal_slot); us = sipQtSupport->qt_create_universal_slot(txSelf, sig, rxObj, slot, member, flags); if (us && txSelf) sipSetPossibleProxy((sipSimpleWrapper *)txSelf); return us; } /* * Invoke a single slot (Qt or Python) and return the result. Don't check if * any receiver C++ object still exists. */ PyObject *sip_api_invoke_slot(const sipSlot *slot, PyObject *sigargs) { return sip_api_invoke_slot_ex(slot, sigargs, TRUE); } /* * Invoke a single slot (Qt or Python) and return the result. Optionally check * that any receiver C++ object still exist. */ PyObject *sip_api_invoke_slot_ex(const sipSlot *slot, PyObject *sigargs, int no_receiver_check) { PyObject *sa, *oxtype, *oxvalue, *oxtb, *sfunc, *sref; assert(sipQtSupport); /* Keep some compilers quiet. */ oxtype = oxvalue = oxtb = NULL; /* Fan out Qt signals. (Only PyQt3 would do this.) */ if (slot->name != NULL && slot->name[0] != '\0') { assert(sipQtSupport->qt_emit_signal); if (sipQtSupport->qt_emit_signal(slot->pyobj, slot->name, sigargs) < 0) return NULL; Py_INCREF(Py_None); return Py_None; } /* Get the object to call, resolving any weak references. */ if (slot->weakSlot == Py_True) { /* * The slot is guaranteed to be Ok because it has an extra reference or * is None. */ sref = slot->pyobj; Py_INCREF(sref); } else if (slot->weakSlot == NULL) { sref = NULL; } else { #if PY_VERSION_HEX >= 0x030d0000 if (PyWeakref_GetRef(slot->weakSlot, &sref) < 0) return NULL; if (sref == NULL) { sref = Py_None; Py_INCREF(sref); } #else if ((sref = PyWeakref_GetObject(slot->weakSlot)) == NULL) return NULL; Py_INCREF(sref); #endif } if (sref == Py_None) { /* * If the real object has gone then we pretend everything is Ok. This * mimics the Qt behaviour of not caring if a receiving object has been * deleted. */ Py_DECREF(sref); Py_INCREF(Py_None); return Py_None; } if (slot -> pyobj == NULL) { PyObject *self = (sref != NULL ? sref : slot->meth.mself); /* * If the receiver wraps a C++ object then ignore the call if it no * longer exists. */ if (!no_receiver_check && PyObject_TypeCheck(self, (PyTypeObject *)&sipSimpleWrapper_Type) && sip_api_get_address((sipSimpleWrapper *)self) == NULL) { Py_XDECREF(sref); Py_INCREF(Py_None); return Py_None; } if ((sfunc = PyMethod_New(slot->meth.mfunc, self)) == NULL) { Py_XDECREF(sref); return NULL; } } else if (slot -> name != NULL) { char *mname = slot -> name + 1; PyObject *self = (sref != NULL ? sref : slot->pyobj); if ((sfunc = PyObject_GetAttrString(self, mname)) == NULL || !PyCFunction_Check(sfunc)) { /* * Note that in earlier versions of SIP this error would be * detected when the slot was connected. */ PyErr_Format(PyExc_NameError,"Invalid slot %s",mname); Py_XDECREF(sfunc); Py_XDECREF(sref); return NULL; } } else { sfunc = slot->pyobj; Py_INCREF(sfunc); } /* * We make repeated attempts to call a slot. If we work out that it failed * because of an immediate type error we try again with one less argument. * We keep going until we run out of arguments to drop. This emulates the * Qt ability of the slot to accept fewer arguments than a signal provides. */ sa = sigargs; Py_INCREF(sa); for (;;) { PyObject *nsa, *xtype, *xvalue, *xtb, *resobj; if ((resobj = PyObject_CallObject(sfunc, sa)) != NULL) { Py_DECREF(sfunc); Py_XDECREF(sref); /* Remove any previous exception. */ if (sa != sigargs) { Py_XDECREF(oxtype); Py_XDECREF(oxvalue); Py_XDECREF(oxtb); PyErr_Clear(); } Py_DECREF(sa); return resobj; } /* Get the exception. */ PyErr_Fetch(&xtype,&xvalue,&xtb); /* * See if it is unacceptable. An acceptable failure is a type error * with no traceback - so long as we can still reduce the number of * arguments and try again. */ if (!PyErr_GivenExceptionMatches(xtype,PyExc_TypeError) || xtb != NULL || PyTuple_GET_SIZE(sa) == 0) { /* * If there is a traceback then we must have called the slot and * the exception was later on - so report the exception as is. */ if (xtb != NULL) { if (sa != sigargs) { Py_XDECREF(oxtype); Py_XDECREF(oxvalue); Py_XDECREF(oxtb); } PyErr_Restore(xtype,xvalue,xtb); } else if (sa == sigargs) PyErr_Restore(xtype,xvalue,xtb); else { /* * Discard the latest exception and restore the original one. */ Py_XDECREF(xtype); Py_XDECREF(xvalue); Py_XDECREF(xtb); PyErr_Restore(oxtype,oxvalue,oxtb); } break; } /* If this is the first attempt, save the exception. */ if (sa == sigargs) { oxtype = xtype; oxvalue = xvalue; oxtb = xtb; } else { Py_XDECREF(xtype); Py_XDECREF(xvalue); Py_XDECREF(xtb); } /* Create the new argument tuple. */ if ((nsa = PyTuple_GetSlice(sa,0,PyTuple_GET_SIZE(sa) - 1)) == NULL) { /* Tidy up. */ Py_XDECREF(oxtype); Py_XDECREF(oxvalue); Py_XDECREF(oxtb); break; } Py_DECREF(sa); sa = nsa; } Py_DECREF(sfunc); Py_XDECREF(sref); Py_DECREF(sa); return NULL; } /* * Compare two slots to see if they are the same. */ int sip_api_same_slot(const sipSlot *sp, PyObject *rxObj, const char *slot) { assert(sipQtSupport); assert(sipQtSupport->qt_same_name); /* See if they are signals or Qt slots, ie. they have a name. */ if (slot != NULL) { if (sp->name == NULL || sp->name[0] == '\0') return 0; return (sipQtSupport->qt_same_name(sp->name, slot) && sp->pyobj == rxObj); } /* See if they are pure Python methods. */ if (PyMethod_Check(rxObj)) { if (sp->pyobj != NULL) return 0; return (sp->meth.mfunc == PyMethod_GET_FUNCTION(rxObj) && sp->meth.mself == PyMethod_GET_SELF(rxObj)); } /* See if they are wrapped C++ methods. */ if (PyCFunction_Check(rxObj)) { if (sp->name == NULL || sp->name[0] != '\0') return 0; return (sp->pyobj == PyCFunction_GET_SELF(rxObj) && strcmp(&sp->name[1], ((PyCFunctionObject *)rxObj)->m_ml->ml_name) == 0); } /* The objects must be the same. */ return (sp->pyobj == rxObj); } /* * Convert a valid Python signal or slot to an existing universal slot. */ void *sipGetRx(sipSimpleWrapper *txSelf, const char *sigargs, PyObject *rxObj, const char *slot, const char **memberp) { assert(sipQtSupport); assert(sipQtSupport->qt_find_slot); if (slot != NULL) if (isQtSlot(slot) || isQtSignal(slot)) { void *rx; *memberp = slot; if ((rx = sip_api_get_cpp_ptr((sipSimpleWrapper *)rxObj, sipQObjectType)) == NULL) return NULL; if (isQtSignal(slot)) rx = findSignal(rx, memberp); return rx; } /* * The slot was either a Python callable or PyQt3 Python signal so there * should be a universal slot. */ return sipQtSupport->qt_find_slot(sip_api_get_address(txSelf), sigargs, rxObj, slot, memberp); } /* * Convert a Python receiver (either a Python signal or slot or a Qt signal or * slot) to a Qt receiver. It is only ever called when the signal is a Qt * signal. Return NULL is there was an error. */ void *sip_api_convert_rx(sipWrapper *txSelf, const char *sigargs, PyObject *rxObj, const char *slot, const char **memberp, int flags) { assert(sipQtSupport); if (slot == NULL) return createUniversalSlot(txSelf, sigargs, rxObj, NULL, memberp, flags); if (isQtSlot(slot) || isQtSignal(slot)) { void *rx; *memberp = slot; if ((rx = sip_api_get_cpp_ptr((sipSimpleWrapper *)rxObj, sipQObjectType)) == NULL) return NULL; if (isQtSignal(slot)) rx = newSignal(rx, memberp); return rx; } /* The slot is a Python signal so we need a universal slot to catch it. */ return createUniversalSlot(txSelf, sigargs, rxObj, slot, memberp, 0); } /* * Connect a Qt signal or a Python signal to a Qt slot, a Qt signal, a Python * slot or a Python signal. This is all possible combinations. */ PyObject *sip_api_connect_rx(PyObject *txObj, const char *sig, PyObject *rxObj, const char *slot, int type) { assert(sipQtSupport); assert(sipQtSupport->qt_connect); /* Handle Qt signals. */ if (isQtSignal(sig)) { void *tx, *rx; const char *member, *real_sig; int res; if ((tx = sip_api_get_cpp_ptr((sipSimpleWrapper *)txObj, sipQObjectType)) == NULL) return NULL; real_sig = sig; if ((tx = newSignal(tx, &real_sig)) == NULL) return NULL; if ((rx = sip_api_convert_rx((sipWrapper *)txObj, sig, rxObj, slot, &member, 0)) == NULL) return NULL; res = sipQtSupport->qt_connect(tx, real_sig, rx, member, type); return PyBool_FromLong(res); } /* Handle Python signals. Only PyQt3 would get this far. */ assert(sipQtSupport->qt_connect_py_signal); if (sipQtSupport->qt_connect_py_signal(txObj, sig, rxObj, slot) < 0) return NULL; Py_INCREF(Py_True); return Py_True; } /* * Disconnect a signal to a signal or a Qt slot. */ PyObject *sip_api_disconnect_rx(PyObject *txObj,const char *sig, PyObject *rxObj,const char *slot) { assert(sipQtSupport); assert(sipQtSupport->qt_disconnect); assert(sipQtSupport->qt_destroy_universal_slot); /* Handle Qt signals. */ if (isQtSignal(sig)) { sipSimpleWrapper *txSelf = (sipSimpleWrapper *)txObj; void *tx, *rx; const char *member; int res; if ((tx = sip_api_get_cpp_ptr(txSelf, sipQObjectType)) == NULL) return NULL; if ((rx = sipGetRx(txSelf, sig, rxObj, slot, &member)) == NULL) { Py_INCREF(Py_False); return Py_False; } /* Handle Python signals. */ tx = findSignal(tx, &sig); res = sipQtSupport->qt_disconnect(tx, sig, rx, member); /* * Delete it if it is a universal slot as this will be it's only * connection. If the slot is actually a universal signal then it * should leave it in place. */ sipQtSupport->qt_destroy_universal_slot(rx); return PyBool_FromLong(res); } /* Handle Python signals. Only PyQt3 would get this far. */ assert(sipQtSupport->qt_disconnect_py_signal); sipQtSupport->qt_disconnect_py_signal(txObj, sig, rxObj, slot); Py_INCREF(Py_True); return Py_True; } /* * Free the resources of a slot. */ void sip_api_free_sipslot(sipSlot *slot) { assert(sipQtSupport); if (slot->name != NULL) { sip_api_free(slot->name); } else if (slot->weakSlot == Py_True) { Py_DECREF(slot->pyobj); } /* Remove any weak reference. */ Py_XDECREF(slot->weakSlot); } /* * Implement strdup() using sip_api_malloc(). */ static char *sipStrdup(const char *s) { char *d; if ((d = (char *)sip_api_malloc(strlen(s) + 1)) != NULL) strcpy(d,s); return d; } /* * Initialise a slot, returning 0 if there was no error. If the signal was a * Qt signal, then the slot may be a Python signal or a Python slot. If the * signal was a Python signal, then the slot may be anything. */ int sip_api_save_slot(sipSlot *sp, PyObject *rxObj, const char *slot) { assert(sipQtSupport); sp -> weakSlot = NULL; if (slot == NULL) { sp -> name = NULL; if (PyMethod_Check(rxObj)) { /* * Python creates methods on the fly. We could increment the * reference count to keep it alive, but that would keep "self" * alive as well and would probably be a circular reference. * Instead we remember the component parts and hope they are still * valid when we re-create the method when we need it. */ sipSaveMethod(&sp -> meth,rxObj); /* Notice if the class instance disappears. */ sp -> weakSlot = getWeakRef(sp -> meth.mself); /* This acts a flag to say that the slot is a method. */ sp -> pyobj = NULL; } else { PyObject *self; /* * We know that it is another type of callable, ie. a * function/builtin. */ if (PyCFunction_Check(rxObj) && (self = PyCFunction_GET_SELF(rxObj)) != NULL && PyObject_TypeCheck(self, (PyTypeObject *)&sipSimpleWrapper_Type)) { /* * It is a wrapped C++ class method. We can't keep a copy * because they are generated on the fly and we can't take a * reference as that may keep the instance (ie. self) alive. * We therefore treat it as if the user had specified the slot * at "obj, SLOT('meth()')" rather than "obj.meth" (see below). */ const char *meth; /* Get the method name. */ meth = ((PyCFunctionObject *)rxObj) -> m_ml -> ml_name; if ((sp -> name = (char *)sip_api_malloc(strlen(meth) + 2)) == NULL) return -1; /* * Copy the name and set the marker that it needs converting to * a built-in method. */ sp -> name[0] = '\0'; strcpy(&sp -> name[1],meth); sp -> pyobj = self; sp -> weakSlot = getWeakRef(self); } else { /* * Give the slot an extra reference to keep it alive and * remember we have done so by treating weakSlot specially. */ Py_INCREF(rxObj); sp->pyobj = rxObj; Py_INCREF(Py_True); sp->weakSlot = Py_True; } } } else if ((sp -> name = sipStrdup(slot)) == NULL) return -1; else if (isQtSlot(slot)) { /* * The user has decided to connect a Python signal to a Qt slot and * specified the slot as "obj, SLOT('meth()')" rather than "obj.meth". */ char *tail; /* Remove any arguments. */ if ((tail = strchr(sp -> name,'(')) != NULL) *tail = '\0'; /* * A bit of a hack to indicate that this needs converting to a built-in * method. */ sp -> name[0] = '\0'; /* Notice if the class instance disappears. */ sp -> weakSlot = getWeakRef(rxObj); sp -> pyobj = rxObj; } else /* It's a Qt signal. */ sp -> pyobj = rxObj; return 0; } /* * Return a weak reference to the given object. */ static PyObject *getWeakRef(PyObject *obj) { PyObject *wr; if ((wr = PyWeakref_NewRef(obj,NULL)) == NULL) PyErr_Clear(); return wr; } sip-6.8.6/sipbuild/module/source/12/setup.cfg.in000066400000000000000000000003701464421045000213760ustar00rootroot00000000000000[metadata] description = The sip module support for @SIP_MODULE_PACKAGE_NAME@ long_description = file: README author = Phil Thompson author_email = phil@riverbankcomputing.com url = https://github.com/Python-SIP/sip platforms = X11, macOS, Windows sip-6.8.6/sipbuild/module/source/12/setup.py.in000066400000000000000000000007461464421045000212760ustar00rootroot00000000000000# SPDX-License-Identifier: BSD-2-Clause # Copyright (c) 2024 Phil Thompson import glob from setuptools import Extension, setup # Build the extension module. module_src = sorted(glob.glob('*.c')) module = Extension('@_SIP_MODULE_FQ_NAME@', module_src) # Do the setup. setup( name='@SIP_MODULE_PROJECT_NAME@', version='@SIP_MODULE_VERSION@', license='SIP', python_requires='>=3.8', ext_modules=[module] ) sip-6.8.6/sipbuild/module/source/12/sip.h.in000066400000000000000000001571661464421045000205410ustar00rootroot00000000000000/* SPDX-License-Identifier: BSD-2-Clause */ /* * The SIP module interface. * * Copyright (c) 2024 Phil Thompson */ #ifndef _SIP_H #define _SIP_H #include /* Sanity check on the Python version. */ #if PY_VERSION_HEX < 0x03080000 #error "This version of @_SIP_MODULE_FQ_NAME@ requires Python v3.8 or later" #endif #ifdef __cplusplus #include typedef bool (*sipExceptionHandler)(std::exception_ptr); #else typedef void *sipExceptionHandler; #endif #ifdef __cplusplus extern "C" { #endif /* The version of the ABI. */ #define SIP_ABI_MAJOR_VERSION 12 #define SIP_ABI_MINOR_VERSION 15 #define SIP_MODULE_PATCH_VERSION 0 /* * The change history of the ABI. * * v12.15 * - Added the 'I' conversion character to the argument and result parsers. * * v12.14 * - Added support for Python v3.13. * - Python v3.8 or later is required. * - C99 support is assumed. * * v12.13 * - Added support for Python v3.12. * - Added sipPyTypeDictRef(). * - Deprecated sipPyTypeDict(). * * v12.12 * - Added the '#' conversion character to the argument parsers. * * v12.11 * - Published the 'array' type. * - Added the ctd_sizeof, ctd_array_delete members to sipClassTypeDef. * - Added the '>' conversion character to the argument parsers. * * v12.10 * - Python v3.7 or later is required. * - Added support for Python v3.11. * * v12.9 * - Added sipNextExceptionHandler(). * * v12.8 * - Added a new thread-safe implementation of sipIsPyMethod(). */ /* The version of the code generator. */ #define SIP_VERSION @_SIP_VERSION@ #define SIP_VERSION_STR "@_SIP_VERSION_STR@" /* These are all dependent on the user-specified name of the sip module. */ #define _SIP_MODULE_FQ_NAME "@_SIP_MODULE_FQ_NAME@" #define _SIP_MODULE_NAME "@_SIP_MODULE_NAME@" #define _SIP_MODULE_SHARED @_SIP_MODULE_SHARED@ #define _SIP_MODULE_ENTRY @_SIP_MODULE_ENTRY@ #define _SIP_MODULE_LEGACY @_SIP_MODULE_LEGACY@ /* Support the historical names. */ #define SIP_API_MAJOR_NR SIP_ABI_MAJOR_VERSION #define SIP_API_MINOR_NR SIP_ABI_MINOR_VERSION /* * Qt includes this typedef and its meta-object system explicitly converts * types to uint. If these correspond to signal arguments then that conversion * is exposed. Therefore SIP generates code that uses it. This definition is * for the cases that SIP is generating non-Qt related bindings with compilers * that don't include it themselves (i.e. MSVC). */ typedef unsigned int uint; /* Some C++ compatibility stuff. */ #if defined(__cplusplus) /* * Cast a PyCFunctionWithKeywords to a PyCFunction in such a way that it * suppresses the GCC -Wcast-function-type warning. */ #define SIP_MLMETH_CAST(m) reinterpret_cast(reinterpret_cast(m)) #if __cplusplus >= 201103L || defined(_MSVC_LANG) /* C++11 and later. */ #define SIP_NULLPTR nullptr #define SIP_OVERRIDE override #else /* Earlier versions of C++. */ #define SIP_NULLPTR NULL #define SIP_OVERRIDE #endif #else /* Cast a PyCFunctionWithKeywords to a PyCFunction. */ #define SIP_MLMETH_CAST(m) ((PyCFunction)(m)) /* C. */ #define SIP_NULLPTR NULL #define SIP_OVERRIDE #endif #define SIP_SSIZE_T Py_ssize_t #define SIP_SSIZE_T_FORMAT "%zd" #define SIP_USE_PYCAPSULE #define SIP_MODULE_RETURN(v) return (v) #define SIPLong_Check PyLong_Check #define SIPLong_FromLong PyLong_FromLong #define SIPLong_AsLong PyLong_AsLong #define SIPBytes_Check PyBytes_Check #define SIPBytes_FromString PyBytes_FromString #define SIPBytes_FromStringAndSize PyBytes_FromStringAndSize #define SIPBytes_AsString PyBytes_AsString #define SIPBytes_Size PyBytes_Size #define SIPBytes_AS_STRING PyBytes_AS_STRING #define SIPBytes_GET_SIZE PyBytes_GET_SIZE /* * The mask that can be passed to sipTrace(). */ #define SIP_TRACE_CATCHERS 0x0001 #define SIP_TRACE_CTORS 0x0002 #define SIP_TRACE_DTORS 0x0004 #define SIP_TRACE_INITS 0x0008 #define SIP_TRACE_DEALLOCS 0x0010 #define SIP_TRACE_METHODS 0x0020 /* * Hide some thread dependent stuff. */ #ifdef WITH_THREAD typedef PyGILState_STATE sip_gilstate_t; #define SIP_RELEASE_GIL(gs) PyGILState_Release(gs); #define SIP_BLOCK_THREADS {PyGILState_STATE sipGIL = PyGILState_Ensure(); #define SIP_UNBLOCK_THREADS PyGILState_Release(sipGIL);} #else typedef int sip_gilstate_t; #define SIP_RELEASE_GIL(gs) #define SIP_BLOCK_THREADS #define SIP_UNBLOCK_THREADS #endif /* * Forward declarations of types. */ struct _sipBufferDef; typedef struct _sipBufferDef sipBufferDef; struct _sipBufferInfoDef; typedef struct _sipBufferInfoDef sipBufferInfoDef; struct _sipCFunctionDef; typedef struct _sipCFunctionDef sipCFunctionDef; struct _sipDateDef; typedef struct _sipDateDef sipDateDef; struct _sipEnumTypeObject; typedef struct _sipEnumTypeObject sipEnumTypeObject; struct _sipMethodDef; typedef struct _sipMethodDef sipMethodDef; struct _sipSimpleWrapper; typedef struct _sipSimpleWrapper sipSimpleWrapper; struct _sipTimeDef; typedef struct _sipTimeDef sipTimeDef; struct _sipTypeDef; typedef struct _sipTypeDef sipTypeDef; struct _sipWrapperType; typedef struct _sipWrapperType sipWrapperType; struct _sipWrapper; typedef struct _sipWrapper sipWrapper; /* * The different events a handler can be registered for. */ typedef enum { sipEventWrappedInstance, /* After wrapping a C/C++ instance. */ sipEventCollectingWrapper, /* When garbage collecting a wrapper object. */ sipEventNrEvents } sipEventType; /* * The event handlers. */ typedef void (*sipWrappedInstanceEventHandler)(void *sipCpp); typedef void (*sipCollectingWrapperEventHandler)(sipSimpleWrapper *sipSelf); /* * The operation an access function is being asked to perform. */ typedef enum { UnguardedPointer, /* Return the unguarded pointer. */ GuardedPointer, /* Return the guarded pointer, ie. 0 if it has gone. */ ReleaseGuard /* Release the guard, if any. */ } AccessFuncOp; /* * Some convenient function pointers. */ typedef void *(*sipInitFunc)(sipSimpleWrapper *, PyObject *, PyObject *, PyObject **, PyObject **, PyObject **); typedef int (*sipFinalFunc)(PyObject *, void *, PyObject *, PyObject **); typedef void *(*sipAccessFunc)(sipSimpleWrapper *, AccessFuncOp); typedef int (*sipTraverseFunc)(void *, visitproc, void *); typedef int (*sipClearFunc)(void *); typedef int (*sipGetBufferFuncLimited)(PyObject *, void *, sipBufferDef *); typedef void (*sipReleaseBufferFuncLimited)(PyObject *, void *); #if !defined(Py_LIMITED_API) typedef int (*sipGetBufferFunc)(PyObject *, void *, Py_buffer *, int); typedef void (*sipReleaseBufferFunc)(PyObject *, void *, Py_buffer *); #endif typedef void (*sipDeallocFunc)(sipSimpleWrapper *); typedef void *(*sipCastFunc)(void *, const sipTypeDef *); typedef const sipTypeDef *(*sipSubClassConvertFunc)(void **); typedef int (*sipConvertToFunc)(PyObject *, void **, int *, PyObject *); typedef PyObject *(*sipConvertFromFunc)(void *, PyObject *); typedef void (*sipVirtErrorHandlerFunc)(sipSimpleWrapper *, sip_gilstate_t); typedef int (*sipVirtHandlerFunc)(sip_gilstate_t, sipVirtErrorHandlerFunc, sipSimpleWrapper *, PyObject *, ...); typedef void (*sipAssignFunc)(void *, Py_ssize_t, void *); typedef void *(*sipArrayFunc)(Py_ssize_t); typedef void (*sipArrayDeleteFunc)(void *); typedef void *(*sipCopyFunc)(const void *, Py_ssize_t); typedef void (*sipReleaseFunc)(void *, int); typedef PyObject *(*sipPickleFunc)(void *); typedef int (*sipAttrGetterFunc)(const sipTypeDef *, PyObject *); typedef PyObject *(*sipVariableGetterFunc)(void *, PyObject *, PyObject *); typedef int (*sipVariableSetterFunc)(void *, PyObject *, PyObject *); typedef void *(*sipProxyResolverFunc)(void *); typedef int (*sipNewUserTypeFunc)(sipWrapperType *); typedef void (*sipWrapperVisitorFunc)(sipSimpleWrapper *, void *); #if !defined(Py_LIMITED_API) /* * The meta-type of a wrapper type. */ struct _sipWrapperType { /* * The super-metatype. This must be first in the structure so that it can * be cast to a PyTypeObject *. */ PyHeapTypeObject super; /* Set if the type is a user implemented Python sub-class. */ unsigned wt_user_type : 1; /* Set if the type's dictionary contains all lazy attributes. */ unsigned wt_dict_complete : 1; /* Unused and available for future use. */ unsigned wt_unused : 30; /* The generated type structure. */ sipTypeDef *wt_td; /* The list of init extenders. */ struct _sipInitExtenderDef *wt_iextend; /* The handler called whenever a new user type has been created. */ sipNewUserTypeFunc wt_new_user_type_handler; /* * For the user to use. Note that any data structure will leak if the * type is garbage collected. */ void *wt_user_data; }; /* * The type of a simple C/C++ wrapper object. */ struct _sipSimpleWrapper { PyObject_HEAD /* * The data, initially a pointer to the C/C++ object, as interpreted by the * access function. */ void *data; /* The optional access function. */ sipAccessFunc access_func; /* Object flags. */ unsigned sw_flags; /* The optional dictionary of extra references keyed by argument number. */ PyObject *extra_refs; /* For the user to use. */ PyObject *user; /* The instance dictionary. */ PyObject *dict; /* The main instance if this is a mixin. */ PyObject *mixin_main; /* Next object at this address. */ struct _sipSimpleWrapper *next; }; /* * The type of a C/C++ wrapper object that supports parent/child relationships. */ struct _sipWrapper { /* The super-type. */ sipSimpleWrapper super; /* First child object. */ struct _sipWrapper *first_child; /* Next sibling. */ struct _sipWrapper *sibling_next; /* Previous sibling. */ struct _sipWrapper *sibling_prev; /* Owning object. */ struct _sipWrapper *parent; }; /* * The meta-type of an enum type. (This is exposed only to support the * deprecated sipConvertFromNamedEnum() macro.) */ struct _sipEnumTypeObject { /* * The super-metatype. This must be first in the structure so that it can * be cast to a PyTypeObject *. */ PyHeapTypeObject super; /* The generated type structure. */ struct _sipTypeDef *type; }; #endif /* * The information describing an encoded type ID. */ typedef struct _sipEncodedTypeDef { /* The type number. */ unsigned sc_type : 16; /* The module number (255 for this one). */ unsigned sc_module : 8; /* A context specific flag. */ unsigned sc_flag : 1; } sipEncodedTypeDef; /* * The information describing an enum member. */ typedef struct _sipEnumMemberDef { /* The member name. */ const char *em_name; /* The member value. */ int em_val; /* The member enum, -ve if anonymous. */ int em_enum; } sipEnumMemberDef; /* * The information describing static instances. */ typedef struct _sipInstancesDef { /* The types. */ struct _sipTypeInstanceDef *id_type; /* The void *. */ struct _sipVoidPtrInstanceDef *id_voidp; /* The chars. */ struct _sipCharInstanceDef *id_char; /* The strings. */ struct _sipStringInstanceDef *id_string; /* The ints. */ struct _sipIntInstanceDef *id_int; /* The longs. */ struct _sipLongInstanceDef *id_long; /* The unsigned longs. */ struct _sipUnsignedLongInstanceDef *id_ulong; /* The long longs. */ struct _sipLongLongInstanceDef *id_llong; /* The unsigned long longs. */ struct _sipUnsignedLongLongInstanceDef *id_ullong; /* The doubles. */ struct _sipDoubleInstanceDef *id_double; } sipInstancesDef; /* * The information describing a type initialiser extender. */ typedef struct _sipInitExtenderDef { /* The API version range index. */ int ie_api_range; /* The extender function. */ sipInitFunc ie_extender; /* The class being extended. */ sipEncodedTypeDef ie_class; /* The next extender for this class. */ struct _sipInitExtenderDef *ie_next; } sipInitExtenderDef; /* * The information describing a sub-class convertor. */ typedef struct _sipSubClassConvertorDef { /* The convertor. */ sipSubClassConvertFunc scc_convertor; /* The encoded base type. */ sipEncodedTypeDef scc_base; /* The base type. */ struct _sipTypeDef *scc_basetype; } sipSubClassConvertorDef; /* * The structure populated by %BIGetBufferCode when the limited API is enabled. */ struct _sipBufferDef { /* The address of the buffer. */ void *bd_buffer; /* The length of the buffer. */ Py_ssize_t bd_length; /* Set if the buffer is read-only. */ int bd_readonly; }; /* * The structure describing a Python buffer. */ struct _sipBufferInfoDef { /* This is internal to sip. */ void *bi_internal; /* The address of the buffer. */ void *bi_buf; /* A reference to the object implementing the buffer interface. */ PyObject *bi_obj; /* The length of the buffer in bytes. */ Py_ssize_t bi_len; /* The number of dimensions. */ int bi_ndim; /* The format of each element of the buffer. */ char *bi_format; }; /* * The structure describing a Python C function. */ struct _sipCFunctionDef { /* The C function. */ PyMethodDef *cf_function; /* The optional bound object. */ PyObject *cf_self; }; /* * The structure describing a Python method. */ struct _sipMethodDef { /* The function that implements the method. */ PyObject *pm_function; /* The bound object. */ PyObject *pm_self; }; /* * The structure describing a Python date. */ struct _sipDateDef { /* The year. */ int pd_year; /* The month (1-12). */ int pd_month; /* The day (1-31). */ int pd_day; }; /* * The structure describing a Python time. */ struct _sipTimeDef { /* The hour (0-23). */ int pt_hour; /* The minute (0-59). */ int pt_minute; /* The second (0-59). */ int pt_second; /* The microsecond (0-999999). */ int pt_microsecond; }; /* * The different error states of handwritten code. */ typedef enum { sipErrorNone, /* There is no error. */ sipErrorFail, /* The error is a failure. */ sipErrorContinue /* It may not apply if a later operation succeeds. */ } sipErrorState; /* * The different Python slot types. New slots must be added to the end, * otherwise the major version of the internal ABI must be changed. */ typedef enum { str_slot, /* __str__ */ int_slot, /* __int__ */ float_slot, /* __float__ */ len_slot, /* __len__ */ contains_slot, /* __contains__ */ add_slot, /* __add__ for number */ concat_slot, /* __add__ for sequence types */ sub_slot, /* __sub__ */ mul_slot, /* __mul__ for number types */ repeat_slot, /* __mul__ for sequence types */ div_slot, /* __div__ */ mod_slot, /* __mod__ */ floordiv_slot, /* __floordiv__ */ truediv_slot, /* __truediv__ */ and_slot, /* __and__ */ or_slot, /* __or__ */ xor_slot, /* __xor__ */ lshift_slot, /* __lshift__ */ rshift_slot, /* __rshift__ */ iadd_slot, /* __iadd__ for number types */ iconcat_slot, /* __iadd__ for sequence types */ isub_slot, /* __isub__ */ imul_slot, /* __imul__ for number types */ irepeat_slot, /* __imul__ for sequence types */ idiv_slot, /* __idiv__ */ imod_slot, /* __imod__ */ ifloordiv_slot, /* __ifloordiv__ */ itruediv_slot, /* __itruediv__ */ iand_slot, /* __iand__ */ ior_slot, /* __ior__ */ ixor_slot, /* __ixor__ */ ilshift_slot, /* __ilshift__ */ irshift_slot, /* __irshift__ */ invert_slot, /* __invert__ */ call_slot, /* __call__ */ getitem_slot, /* __getitem__ */ setitem_slot, /* __setitem__ */ delitem_slot, /* __delitem__ */ lt_slot, /* __lt__ */ le_slot, /* __le__ */ eq_slot, /* __eq__ */ ne_slot, /* __ne__ */ gt_slot, /* __gt__ */ ge_slot, /* __ge__ */ bool_slot, /* __bool__, __nonzero__ */ neg_slot, /* __neg__ */ repr_slot, /* __repr__ */ hash_slot, /* __hash__ */ pos_slot, /* __pos__ */ abs_slot, /* __abs__ */ index_slot, /* __index__ */ iter_slot, /* __iter__ */ next_slot, /* __next__ */ setattr_slot, /* __setattr__, __delattr__ */ matmul_slot, /* __matmul__ (for Python v3.5 and later) */ imatmul_slot, /* __imatmul__ (for Python v3.5 and later) */ await_slot, /* __await__ (for Python v3.5 and later) */ aiter_slot, /* __aiter__ (for Python v3.5 and later) */ anext_slot, /* __anext__ (for Python v3.5 and later) */ } sipPySlotType; /* * The information describing a Python slot function. */ typedef struct _sipPySlotDef { /* The function. */ void *psd_func; /* The type. */ sipPySlotType psd_type; } sipPySlotDef; /* * The information describing a Python slot extender. */ typedef struct _sipPySlotExtenderDef { /* The function. */ void *pse_func; /* The type. */ sipPySlotType pse_type; /* The encoded class. */ sipEncodedTypeDef pse_class; } sipPySlotExtenderDef; /* * The information describing a typedef. */ typedef struct _sipTypedefDef { /* The typedef name. */ const char *tdd_name; /* The typedef value. */ const char *tdd_type_name; } sipTypedefDef; /* * The information describing a variable or property. */ typedef enum { PropertyVariable, /* A property. */ InstanceVariable, /* An instance variable. */ ClassVariable /* A class (i.e. static) variable. */ } sipVariableType; typedef struct _sipVariableDef { /* The type of variable. */ sipVariableType vd_type; /* The name. */ const char *vd_name; /* * The getter. If this is a variable (rather than a property) then the * actual type is sipVariableGetterFunc. */ PyMethodDef *vd_getter; /* * The setter. If this is a variable (rather than a property) then the * actual type is sipVariableSetterFunc. It is NULL if the property cannot * be set or the variable is const. */ PyMethodDef *vd_setter; /* The property deleter. */ PyMethodDef *vd_deleter; /* The docstring. */ const char *vd_docstring; } sipVariableDef; /* * The information describing a type, either a C++ class (or C struct), a C++ * namespace, a mapped type or a named enum. */ struct _sipTypeDef { /* The version range index, -1 if the type isn't versioned. */ int td_version; /* The next version of this type. */ struct _sipTypeDef *td_next_version; /* * The module, 0 if the type hasn't been initialised. */ struct _sipExportedModuleDef *td_module; /* Type flags, see the sipType*() macros. */ int td_flags; /* The C/C++ name of the type. */ int td_cname; /* The Python type object. */ PyTypeObject *td_py_type; /* Any additional fixed data generated by a plugin. */ void *td_plugin_data; }; /* * The information describing a container (ie. a class, namespace or a mapped * type). */ typedef struct _sipContainerDef { /* * The Python name of the type, -1 if this is a namespace extender (in the * context of a class) or doesn't require a namespace (in the context of a * mapped type). */ int cod_name; /* * The scoping type or the namespace this is extending if it is a namespace * extender. */ sipEncodedTypeDef cod_scope; /* The number of lazy methods. */ int cod_nrmethods; /* The table of lazy methods. */ PyMethodDef *cod_methods; /* The number of lazy enum members. */ int cod_nrenummembers; /* The table of lazy enum members. */ sipEnumMemberDef *cod_enummembers; /* The number of variables. */ int cod_nrvariables; /* The table of variables. */ sipVariableDef *cod_variables; /* The static instances. */ sipInstancesDef cod_instances; } sipContainerDef; /* * The information describing a C++ class (or C struct) or a C++ namespace. */ typedef struct _sipClassTypeDef { /* The base type information. */ sipTypeDef ctd_base; /* The container information. */ sipContainerDef ctd_container; /* The docstring. */ const char *ctd_docstring; /* * The meta-type name, -1 to use the meta-type of the first super-type * (normally sipWrapperType). */ int ctd_metatype; /* The super-type name, -1 to use sipWrapper. */ int ctd_supertype; /* The super-types. */ sipEncodedTypeDef *ctd_supers; /* The table of Python slots. */ sipPySlotDef *ctd_pyslots; /* The initialisation function. */ sipInitFunc ctd_init; /* The traverse function. */ sipTraverseFunc ctd_traverse; /* The clear function. */ sipClearFunc ctd_clear; /* The get buffer function. */ #if defined(Py_LIMITED_API) sipGetBufferFuncLimited ctd_getbuffer; #else sipGetBufferFunc ctd_getbuffer; #endif /* The release buffer function. */ #if defined(Py_LIMITED_API) sipReleaseBufferFuncLimited ctd_releasebuffer; #else sipReleaseBufferFunc ctd_releasebuffer; #endif /* The deallocation function. */ sipDeallocFunc ctd_dealloc; /* The optional assignment function. */ sipAssignFunc ctd_assign; /* The optional array allocation function. */ sipArrayFunc ctd_array; /* The optional copy function. */ sipCopyFunc ctd_copy; /* The release function, 0 if a C struct. */ sipReleaseFunc ctd_release; /* The cast function, 0 if a C struct. */ sipCastFunc ctd_cast; /* The optional convert to function. */ sipConvertToFunc ctd_cto; /* The optional convert from function. */ sipConvertFromFunc ctd_cfrom; /* The next namespace extender. */ struct _sipClassTypeDef *ctd_nsextender; /* The pickle function. */ sipPickleFunc ctd_pickle; /* The finalisation function. */ sipFinalFunc ctd_final; /* The mixin initialisation function. */ initproc ctd_init_mixin; /* The optional array delete function. */ sipArrayDeleteFunc ctd_array_delete; /* The sizeof the class. */ size_t ctd_sizeof; } sipClassTypeDef; /* * The information describing a mapped type. */ typedef struct _sipMappedTypeDef { /* The base type information. */ sipTypeDef mtd_base; /* The container information. */ sipContainerDef mtd_container; /* The optional assignment function. */ sipAssignFunc mtd_assign; /* The optional array allocation function. */ sipArrayFunc mtd_array; /* The optional copy function. */ sipCopyFunc mtd_copy; /* The optional release function. */ sipReleaseFunc mtd_release; /* The convert to function. */ sipConvertToFunc mtd_cto; /* The convert from function. */ sipConvertFromFunc mtd_cfrom; } sipMappedTypeDef; /* * The information describing a named enum. */ typedef struct _sipEnumTypeDef { /* The base type information. */ sipTypeDef etd_base; /* The Python name of the enum. */ int etd_name; /* The scoping type, -1 if it is defined at the module level. */ int etd_scope; /* The Python slots. */ struct _sipPySlotDef *etd_pyslots; } sipEnumTypeDef; /* * The information describing an external type. */ typedef struct _sipExternalTypeDef { /* The index into the type table. */ int et_nr; /* The name of the type. */ const char *et_name; } sipExternalTypeDef; /* * The information describing a mapped class. This (and anything that uses it) * is deprecated. */ typedef sipTypeDef sipMappedType; /* * Defines an entry in the module specific list of delayed dtor calls. */ typedef struct _sipDelayedDtor { /* The C/C++ instance. */ void *dd_ptr; /* The class name. */ const char *dd_name; /* Non-zero if dd_ptr is a derived class instance. */ int dd_isderived; /* Next in the list. */ struct _sipDelayedDtor *dd_next; } sipDelayedDtor; /* * Defines an entry in the table of global functions all of whose overloads * are versioned (so their names can't be automatically added to the module * dictionary). */ typedef struct _sipVersionedFunctionDef { /* The name, -1 marks the end of the table. */ int vf_name; /* The function itself. */ PyCFunction vf_function; /* The METH_* flags. */ int vf_flags; /* The docstring. */ const char *vf_docstring; /* The API version range index. */ int vf_api_range; } sipVersionedFunctionDef; /* * Defines a virtual error handler. */ typedef struct _sipVirtErrorHandlerDef { /* The name of the handler. */ const char *veh_name; /* The handler function. */ sipVirtErrorHandlerFunc veh_handler; } sipVirtErrorHandlerDef; /* * Defines a type imported from another module. */ typedef union _sipImportedTypeDef { /* The type name before the module is imported. */ const char *it_name; /* The type after the module is imported. */ sipTypeDef *it_td; } sipImportedTypeDef; /* * Defines a virtual error handler imported from another module. */ typedef union _sipImportedVirtErrorHandlerDef { /* The handler name before the module is imported. */ const char *iveh_name; /* The handler after the module is imported. */ sipVirtErrorHandlerFunc iveh_handler; } sipImportedVirtErrorHandlerDef; /* * Defines an exception imported from another module. */ typedef union _sipImportedExceptionDef { /* The exception name before the module is imported. */ const char *iexc_name; /* The exception object after the module is imported. */ PyObject *iexc_object; } sipImportedExceptionDef; /* * The information describing an imported module. */ typedef struct _sipImportedModuleDef { /* The module name. */ const char *im_name; /* The types imported from the module. */ sipImportedTypeDef *im_imported_types; /* The virtual error handlers imported from the module. */ sipImportedVirtErrorHandlerDef *im_imported_veh; /* The exceptions imported from the module. */ sipImportedExceptionDef *im_imported_exceptions; } sipImportedModuleDef; /* * The main client module structure. */ typedef struct _sipExportedModuleDef { /* The next in the list. */ struct _sipExportedModuleDef *em_next; /* The SIP API minor version number. */ unsigned em_api_minor; /* The module name. */ int em_name; /* The module name as an object. */ PyObject *em_nameobj; /* The string pool. */ const char *em_strings; /* The imported modules. */ sipImportedModuleDef *em_imports; /* The optional Qt support API. */ struct _sipQtAPI *em_qt_api; /* The number of types. */ int em_nrtypes; /* The table of types. */ sipTypeDef **em_types; /* The table of external types. */ sipExternalTypeDef *em_external; /* The number of members in global enums. */ int em_nrenummembers; /* The table of members in global enums. */ sipEnumMemberDef *em_enummembers; /* The number of typedefs. */ int em_nrtypedefs; /* The table of typedefs. */ sipTypedefDef *em_typedefs; /* The table of virtual error handlers. */ sipVirtErrorHandlerDef *em_virterrorhandlers; /* The sub-class convertors. */ sipSubClassConvertorDef *em_convertors; /* The static instances. */ sipInstancesDef em_instances; /* The license. */ struct _sipLicenseDef *em_license; /* The table of exception types. */ PyObject **em_exceptions; /* The table of Python slot extenders. */ sipPySlotExtenderDef *em_slotextend; /* The table of initialiser extenders. */ sipInitExtenderDef *em_initextend; /* The delayed dtor handler. */ void (*em_delayeddtors)(const sipDelayedDtor *); /* The list of delayed dtors. */ sipDelayedDtor *em_ddlist; /* * The array of API version definitions. Each definition takes up 3 * elements. If the third element of a 3-tuple is negative then the first * two elements define an API and its default version. All such * definitions will appear at the end of the array. If the first element * of a 3-tuple is negative then that is the last element of the array. */ int *em_versions; /* The optional table of versioned functions. */ sipVersionedFunctionDef *em_versioned_functions; /* The exception handler. */ sipExceptionHandler em_exception_handler; } sipExportedModuleDef; /* * The information describing a license to be added to a dictionary. */ typedef struct _sipLicenseDef { /* The type of license. */ const char *lc_type; /* The licensee. */ const char *lc_licensee; /* The timestamp. */ const char *lc_timestamp; /* The signature. */ const char *lc_signature; } sipLicenseDef; /* * The information describing a void pointer instance to be added to a * dictionary. */ typedef struct _sipVoidPtrInstanceDef { /* The void pointer name. */ const char *vi_name; /* The void pointer value. */ void *vi_val; } sipVoidPtrInstanceDef; /* * The information describing a char instance to be added to a dictionary. */ typedef struct _sipCharInstanceDef { /* The char name. */ const char *ci_name; /* The char value. */ char ci_val; /* The encoding used, either 'A', 'L', '8' or 'N'. */ char ci_encoding; } sipCharInstanceDef; /* * The information describing a string instance to be added to a dictionary. * This is also used as a hack to add (or fix) other types rather than add a * new table type and so requiring a new major version of the API. */ typedef struct _sipStringInstanceDef { /* The string name. */ const char *si_name; /* The string value. */ const char *si_val; /* * The encoding used, either 'A', 'L', '8' or 'N'. 'w' and 'W' are also * used to support the fix for wchar_t. */ char si_encoding; } sipStringInstanceDef; /* * The information describing an int instance to be added to a dictionary. */ typedef struct _sipIntInstanceDef { /* The int name. */ const char *ii_name; /* The int value. */ int ii_val; } sipIntInstanceDef; /* * The information describing a long instance to be added to a dictionary. */ typedef struct _sipLongInstanceDef { /* The long name. */ const char *li_name; /* The long value. */ long li_val; } sipLongInstanceDef; /* * The information describing an unsigned long instance to be added to a * dictionary. */ typedef struct _sipUnsignedLongInstanceDef { /* The unsigned long name. */ const char *uli_name; /* The unsigned long value. */ unsigned long uli_val; } sipUnsignedLongInstanceDef; /* * The information describing a long long instance to be added to a dictionary. */ typedef struct _sipLongLongInstanceDef { /* The long long name. */ const char *lli_name; /* The long long value. */ #if defined(HAVE_LONG_LONG) PY_LONG_LONG lli_val; #else long lli_val; #endif } sipLongLongInstanceDef; /* * The information describing an unsigned long long instance to be added to a * dictionary. */ typedef struct _sipUnsignedLongLongInstanceDef { /* The unsigned long long name. */ const char *ulli_name; /* The unsigned long long value. */ #if defined(HAVE_LONG_LONG) unsigned PY_LONG_LONG ulli_val; #else unsigned long ulli_val; #endif } sipUnsignedLongLongInstanceDef; /* * The information describing a double instance to be added to a dictionary. */ typedef struct _sipDoubleInstanceDef { /* The double name. */ const char *di_name; /* The double value. */ double di_val; } sipDoubleInstanceDef; /* * The information describing a class or enum instance to be added to a * dictionary. */ typedef struct _sipTypeInstanceDef { /* The type instance name. */ const char *ti_name; /* The actual instance. */ void *ti_ptr; /* A pointer to the generated type. */ struct _sipTypeDef **ti_type; /* The wrapping flags. */ int ti_flags; } sipTypeInstanceDef; /* * Define a mapping between a wrapped type identified by a string and the * corresponding Python type. */ typedef struct _sipStringTypeClassMap { /* The type as a string. */ const char *typeString; /* A pointer to the Python type. */ struct _sipWrapperType **pyType; } sipStringTypeClassMap; /* * Define a mapping between a wrapped type identified by an integer and the * corresponding Python type. */ typedef struct _sipIntTypeClassMap { /* The type as an integer. */ int typeInt; /* A pointer to the Python type. */ struct _sipWrapperType **pyType; } sipIntTypeClassMap; /* * A Python method's component parts. This allows us to re-create the method * without changing the reference counts of the components. */ typedef struct _sipPyMethod { /* The function. */ PyObject *mfunc; /* Self if it is a bound method. */ PyObject *mself; } sipPyMethod; /* * A slot (in the Qt, rather than Python, sense). */ typedef struct _sipSlot { /* Name if a Qt or Python signal. */ char *name; /* Signal or Qt slot object. */ PyObject *pyobj; /* Python slot method, pyobj is NULL. */ sipPyMethod meth; /* A weak reference to the slot, Py_True if pyobj has an extra reference. */ PyObject *weakSlot; } sipSlot; /* * The API exported by the SIP module, ie. pointers to all the data and * functions that can be used by generated code. */ typedef struct _sipAPIDef { /* * This must be the first entry and it's signature must not change so that * version number mismatches can be detected and reported. */ int (*api_export_module)(sipExportedModuleDef *client, unsigned api_major, unsigned api_minor, void *unused); /* * The following are part of the public API. */ PyTypeObject *api_simplewrapper_type; PyTypeObject *api_wrapper_type; PyTypeObject *api_wrappertype_type; PyTypeObject *api_voidptr_type; void (*api_bad_catcher_result)(PyObject *method); void (*api_bad_length_for_slice)(Py_ssize_t seqlen, Py_ssize_t slicelen); PyObject *(*api_build_result)(int *isErr, const char *fmt, ...); PyObject *(*api_call_method)(int *isErr, PyObject *method, const char *fmt, ...); void (*api_call_procedure_method)(sip_gilstate_t, sipVirtErrorHandlerFunc, sipSimpleWrapper *, PyObject *, const char *, ...); PyObject *(*api_connect_rx)(PyObject *txObj, const char *sig, PyObject *rxObj, const char *slot, int type); Py_ssize_t (*api_convert_from_sequence_index)(Py_ssize_t idx, Py_ssize_t len); int (*api_can_convert_to_type)(PyObject *pyObj, const sipTypeDef *td, int flags); void *(*api_convert_to_type)(PyObject *pyObj, const sipTypeDef *td, PyObject *transferObj, int flags, int *statep, int *iserrp); void *(*api_force_convert_to_type)(PyObject *pyObj, const sipTypeDef *td, PyObject *transferObj, int flags, int *statep, int *iserrp); /* * The following are deprecated parts of the public API. */ int (*api_can_convert_to_enum)(PyObject *pyObj, const sipTypeDef *td); /* * The following are part of the public API. */ void (*api_release_type)(void *cpp, const sipTypeDef *td, int state); PyObject *(*api_convert_from_type)(void *cpp, const sipTypeDef *td, PyObject *transferObj); PyObject *(*api_convert_from_new_type)(void *cpp, const sipTypeDef *td, PyObject *transferObj); PyObject *(*api_convert_from_enum)(int eval, const sipTypeDef *td); int (*api_get_state)(PyObject *transferObj); PyObject *(*api_disconnect_rx)(PyObject *txObj, const char *sig, PyObject *rxObj, const char *slot); void (*api_free)(void *mem); PyObject *(*api_get_pyobject)(void *cppPtr, const sipTypeDef *td); void *(*api_malloc)(size_t nbytes); int (*api_parse_result)(int *isErr, PyObject *method, PyObject *res, const char *fmt, ...); void (*api_trace)(unsigned mask, const char *fmt, ...); void (*api_transfer_back)(PyObject *self); void (*api_transfer_to)(PyObject *self, PyObject *owner); void (*api_transfer_break)(PyObject *self); unsigned long (*api_long_as_unsigned_long)(PyObject *o); PyObject *(*api_convert_from_void_ptr)(void *val); PyObject *(*api_convert_from_const_void_ptr)(const void *val); PyObject *(*api_convert_from_void_ptr_and_size)(void *val, Py_ssize_t size); PyObject *(*api_convert_from_const_void_ptr_and_size)(const void *val, Py_ssize_t size); void *(*api_convert_to_void_ptr)(PyObject *obj); int (*api_export_symbol)(const char *name, void *sym); void *(*api_import_symbol)(const char *name); const sipTypeDef *(*api_find_type)(const char *type); int (*api_register_py_type)(PyTypeObject *type); const sipTypeDef *(*api_type_from_py_type_object)(PyTypeObject *py_type); const sipTypeDef *(*api_type_scope)(const sipTypeDef *td); const char *(*api_resolve_typedef)(const char *name); int (*api_register_attribute_getter)(const sipTypeDef *td, sipAttrGetterFunc getter); int (*api_is_api_enabled)(const char *name, int from, int to); sipErrorState (*api_bad_callable_arg)(int arg_nr, PyObject *arg); void *(*api_get_address)(struct _sipSimpleWrapper *w); void (*api_set_destroy_on_exit)(int); int (*api_enable_autoconversion)(const sipTypeDef *td, int enable); void *(*api_get_mixin_address)(struct _sipSimpleWrapper *w, const sipTypeDef *td); PyObject *(*api_convert_from_new_pytype)(void *cpp, PyTypeObject *py_type, sipWrapper *owner, sipSimpleWrapper **selfp, const char *fmt, ...); PyObject *(*api_convert_to_typed_array)(void *data, const sipTypeDef *td, const char *format, size_t stride, Py_ssize_t len, int flags); PyObject *(*api_convert_to_array)(void *data, const char *format, Py_ssize_t len, int flags); int (*api_register_proxy_resolver)(const sipTypeDef *td, sipProxyResolverFunc resolver); PyInterpreterState *(*api_get_interpreter)(void); sipNewUserTypeFunc (*api_set_new_user_type_handler)(const sipTypeDef *, sipNewUserTypeFunc); void (*api_set_type_user_data)(sipWrapperType *, void *); void *(*api_get_type_user_data)(const sipWrapperType *); PyObject *(*api_py_type_dict)(const PyTypeObject *); const char *(*api_py_type_name)(const PyTypeObject *); int (*api_get_method)(PyObject *, sipMethodDef *); PyObject *(*api_from_method)(const sipMethodDef *); int (*api_get_c_function)(PyObject *, sipCFunctionDef *); int (*api_get_date)(PyObject *, sipDateDef *); PyObject *(*api_from_date)(const sipDateDef *); int (*api_get_datetime)(PyObject *, sipDateDef *, sipTimeDef *); PyObject *(*api_from_datetime)(const sipDateDef *, const sipTimeDef *); int (*api_get_time)(PyObject *, sipTimeDef *); PyObject *(*api_from_time)(const sipTimeDef *); int (*api_is_user_type)(const sipWrapperType *); struct _frame *(*api_get_frame)(int); int (*api_check_plugin_for_type)(const sipTypeDef *, const char *); PyObject *(*api_unicode_new)(Py_ssize_t, unsigned, int *, void **); void (*api_unicode_write)(int, void *, int, unsigned); void *(*api_unicode_data)(PyObject *, int *, Py_ssize_t *); int (*api_get_buffer_info)(PyObject *, sipBufferInfoDef *); void (*api_release_buffer_info)(sipBufferInfoDef *); PyObject *(*api_get_user_object)(const sipSimpleWrapper *); void (*api_set_user_object)(sipSimpleWrapper *, PyObject *); /* * The following are not part of the public API. */ int (*api_init_module)(sipExportedModuleDef *client, PyObject *mod_dict); int (*api_parse_args)(PyObject **parseErrp, PyObject *sipArgs, const char *fmt, ...); int (*api_parse_pair)(PyObject **parseErrp, PyObject *arg0, PyObject *arg1, const char *fmt, ...); /* * The following are part of the public API. */ void (*api_instance_destroyed)(sipSimpleWrapper *sipSelf); /* * The following are not part of the public API. */ void (*api_no_function)(PyObject *parseErr, const char *func, const char *doc); void (*api_no_method)(PyObject *parseErr, const char *scope, const char *method, const char *doc); void (*api_abstract_method)(const char *classname, const char *method); void (*api_bad_class)(const char *classname); void *(*api_get_cpp_ptr)(sipSimpleWrapper *w, const sipTypeDef *td); void *(*api_get_complex_cpp_ptr)(sipSimpleWrapper *w); PyObject *(*api_is_py_method)(sip_gilstate_t *gil, char *pymc, sipSimpleWrapper *sipSelf, const char *cname, const char *mname); void (*api_call_hook)(const char *hookname); void (*api_end_thread)(void); void (*api_raise_unknown_exception)(void); void (*api_raise_type_exception)(const sipTypeDef *td, void *ptr); int (*api_add_type_instance)(PyObject *dict, const char *name, void *cppPtr, const sipTypeDef *td); void (*api_bad_operator_arg)(PyObject *self, PyObject *arg, sipPySlotType st); PyObject *(*api_pyslot_extend)(sipExportedModuleDef *mod, sipPySlotType st, const sipTypeDef *type, PyObject *arg0, PyObject *arg1); void (*api_add_delayed_dtor)(sipSimpleWrapper *w); char (*api_bytes_as_char)(PyObject *obj); const char *(*api_bytes_as_string)(PyObject *obj); char (*api_string_as_ascii_char)(PyObject *obj); const char *(*api_string_as_ascii_string)(PyObject **obj); char (*api_string_as_latin1_char)(PyObject *obj); const char *(*api_string_as_latin1_string)(PyObject **obj); char (*api_string_as_utf8_char)(PyObject *obj); const char *(*api_string_as_utf8_string)(PyObject **obj); #if defined(HAVE_WCHAR_H) wchar_t (*api_unicode_as_wchar)(PyObject *obj); wchar_t *(*api_unicode_as_wstring)(PyObject *obj); #else int (*api_unicode_as_wchar)(PyObject *obj); int *(*api_unicode_as_wstring)(PyObject *obj); #endif int (*api_deprecated)(const char *classname, const char *method); void (*api_keep_reference)(PyObject *self, int key, PyObject *obj); int (*api_parse_kwd_args)(PyObject **parseErrp, PyObject *sipArgs, PyObject *sipKwdArgs, const char **kwdlist, PyObject **unused, const char *fmt, ...); void (*api_add_exception)(sipErrorState es, PyObject **parseErrp); int (*api_parse_result_ex)(sip_gilstate_t, sipVirtErrorHandlerFunc, sipSimpleWrapper *, PyObject *method, PyObject *res, const char *fmt, ...); void (*api_call_error_handler)(sipVirtErrorHandlerFunc, sipSimpleWrapper *, sip_gilstate_t); int (*api_init_mixin)(PyObject *self, PyObject *args, PyObject *kwds, const sipClassTypeDef *ctd); PyObject *(*api_get_reference)(PyObject *self, int key); /* * The following are part of the public API. */ int (*api_is_owned_by_python)(sipSimpleWrapper *); /* * The following are not part of the public API. */ int (*api_is_derived_class)(sipSimpleWrapper *); /* * The following may be used by Qt support code but no other handwritten * code. */ void (*api_free_sipslot)(sipSlot *slot); int (*api_same_slot)(const sipSlot *sp, PyObject *rxObj, const char *slot); void *(*api_convert_rx)(sipWrapper *txSelf, const char *sigargs, PyObject *rxObj, const char *slot, const char **memberp, int flags); PyObject *(*api_invoke_slot)(const sipSlot *slot, PyObject *sigargs); PyObject *(*api_invoke_slot_ex)(const sipSlot *slot, PyObject *sigargs, int check_receiver); int (*api_save_slot)(sipSlot *sp, PyObject *rxObj, const char *slot); void (*api_clear_any_slot_reference)(sipSlot *slot); int (*api_visit_slot)(sipSlot *slot, visitproc visit, void *arg); /* * The following are deprecated parts of the public API. */ PyTypeObject *(*api_find_named_enum)(const char *type); const sipMappedType *(*api_find_mapped_type)(const char *type); sipWrapperType *(*api_find_class)(const char *type); sipWrapperType *(*api_map_int_to_class)(int typeInt, const sipIntTypeClassMap *map, int maplen); sipWrapperType *(*api_map_string_to_class)(const char *typeString, const sipStringTypeClassMap *map, int maplen); /* * The following are part of the public API. */ int (*api_enable_gc)(int enable); void (*api_print_object)(PyObject *o); int (*api_register_event_handler)(sipEventType type, const sipTypeDef *td, void *handler); int (*api_convert_to_enum)(PyObject *obj, const sipTypeDef *td); int (*api_convert_to_bool)(PyObject *obj); int (*api_enable_overflow_checking)(int enable); char (*api_long_as_char)(PyObject *o); signed char (*api_long_as_signed_char)(PyObject *o); unsigned char (*api_long_as_unsigned_char)(PyObject *o); short (*api_long_as_short)(PyObject *o); unsigned short (*api_long_as_unsigned_short)(PyObject *o); int (*api_long_as_int)(PyObject *o); unsigned int (*api_long_as_unsigned_int)(PyObject *o); long (*api_long_as_long)(PyObject *o); #if defined(HAVE_LONG_LONG) PY_LONG_LONG (*api_long_as_long_long)(PyObject *o); unsigned PY_LONG_LONG (*api_long_as_unsigned_long_long)(PyObject *o); #else void *api_long_as_long_long; void *api_long_as_unsigned_long_long; #endif /* * The following are not part of the public API. */ void (*api_instance_destroyed_ex)(sipSimpleWrapper **sipSelfp); /* * The following are part of the public API. */ int (*api_convert_from_slice_object)(PyObject *slice, Py_ssize_t length, Py_ssize_t *start, Py_ssize_t *stop, Py_ssize_t *step, Py_ssize_t *slicelength); size_t (*api_long_as_size_t)(PyObject *o); void (*api_visit_wrappers)(sipWrapperVisitorFunc visitor, void *closure); int (*api_register_exit_notifier)(PyMethodDef *md); /* * The following are not part of the public API. */ PyObject *(*api_is_py_method_12_8)(sip_gilstate_t *gil, char *pymc, sipSimpleWrapper **sipSelfp, const char *cname, const char *mname); sipExceptionHandler (*api_next_exception_handler)(void **statep); /* * The following are part of the public API. */ PyObject *(*api_py_type_dict_ref)(PyTypeObject *); } sipAPIDef; const sipAPIDef *sip_init_library(PyObject *mod_dict); /* * The API implementing the optional Qt support. */ typedef struct _sipQtAPI { sipTypeDef **qt_qobject; void *(*qt_create_universal_signal)(void *, const char **); void *(*qt_find_universal_signal)(void *, const char **); void *(*qt_create_universal_slot)(struct _sipWrapper *, const char *, PyObject *, const char *, const char **, int); void (*qt_destroy_universal_slot)(void *); void *(*qt_find_slot)(void *, const char *, PyObject *, const char *, const char **); int (*qt_connect)(void *, const char *, void *, const char *, int); int (*qt_disconnect)(void *, const char *, void *, const char *); int (*qt_same_name)(const char *, const char *); sipSlot *(*qt_find_sipslot)(void *, void **); int (*qt_emit_signal)(PyObject *, const char *, PyObject *); int (*qt_connect_py_signal)(PyObject *, const char *, PyObject *, const char *); void (*qt_disconnect_py_signal)(PyObject *, const char *, PyObject *, const char *); } sipQtAPI; /* * These are flags that can be passed to sipCanConvertToType(), * sipConvertToType() and sipForceConvertToType(). */ #define SIP_NOT_NONE 0x01 /* Disallow None. */ #define SIP_NO_CONVERTORS 0x02 /* Disable any type convertors. */ /* * These are flags that can be passed to sipConvertToArray(). These are held * in sw_flags. */ #define SIP_READ_ONLY 0x01 /* The array is read-only. */ #define SIP_OWNS_MEMORY 0x02 /* The array owns its memory. */ /* * These are the state flags returned by %ConvertToTypeCode. Note that the * values share the same "flagspace" as the contents of sw_flags. */ #define SIP_TEMPORARY 0x01 /* A temporary instance. */ #define SIP_DERIVED_CLASS 0x02 /* The instance is derived. */ /* * These flags are specific to the Qt support API. */ #define SIP_SINGLE_SHOT 0x01 /* The connection is single shot. */ /* * Useful macros, not part of the public API. */ /* These are held in sw_flags. */ #define SIP_INDIRECT 0x0004 /* If there is a level of indirection. */ #define SIP_ACCFUNC 0x0008 /* If there is an access function. */ #define SIP_NOT_IN_MAP 0x0010 /* If Python object is not in the map. */ #if !defined(Py_LIMITED_API) #define SIP_PY_OWNED 0x0020 /* If owned by Python. */ #define SIP_SHARE_MAP 0x0040 /* If the map slot might be occupied. */ #define SIP_CPP_HAS_REF 0x0080 /* If C/C++ has a reference. */ #define SIP_POSSIBLE_PROXY 0x0100 /* If there might be a proxy slot. */ #define SIP_ALIAS 0x0200 /* If it is an alias. */ #define SIP_CREATED 0x0400 /* If the C/C++ object has been created. */ #define sipIsDerived(sw) ((sw)->sw_flags & SIP_DERIVED_CLASS) #define sipIsIndirect(sw) ((sw)->sw_flags & SIP_INDIRECT) #define sipIsAccessFunc(sw) ((sw)->sw_flags & SIP_ACCFUNC) #define sipNotInMap(sw) ((sw)->sw_flags & SIP_NOT_IN_MAP) #define sipSetNotInMap(sw) ((sw)->sw_flags |= SIP_NOT_IN_MAP) #define sipIsPyOwned(sw) ((sw)->sw_flags & SIP_PY_OWNED) #define sipSetPyOwned(sw) ((sw)->sw_flags |= SIP_PY_OWNED) #define sipResetPyOwned(sw) ((sw)->sw_flags &= ~SIP_PY_OWNED) #define sipCppHasRef(sw) ((sw)->sw_flags & SIP_CPP_HAS_REF) #define sipSetCppHasRef(sw) ((sw)->sw_flags |= SIP_CPP_HAS_REF) #define sipResetCppHasRef(sw) ((sw)->sw_flags &= ~SIP_CPP_HAS_REF) #define sipPossibleProxy(sw) ((sw)->sw_flags & SIP_POSSIBLE_PROXY) #define sipSetPossibleProxy(sw) ((sw)->sw_flags |= SIP_POSSIBLE_PROXY) #define sipIsAlias(sw) ((sw)->sw_flags & SIP_ALIAS) #define sipWasCreated(sw) ((sw)->sw_flags & SIP_CREATED) #endif #define SIP_TYPE_TYPE_MASK 0x0007 /* The type type mask. */ #define SIP_TYPE_CLASS 0x0000 /* If the type is a C++ class. */ #define SIP_TYPE_NAMESPACE 0x0001 /* If the type is a C++ namespace. */ #define SIP_TYPE_MAPPED 0x0002 /* If the type is a mapped type. */ #define SIP_TYPE_ENUM 0x0003 /* If the type is a named enum. */ #define SIP_TYPE_SCOPED_ENUM 0x0004 /* If the type is a scoped enum. */ #define SIP_TYPE_ABSTRACT 0x0008 /* If the type is abstract. */ #define SIP_TYPE_SCC 0x0010 /* If the type is subject to sub-class convertors. */ #define SIP_TYPE_ALLOW_NONE 0x0020 /* If the type can handle None. */ #define SIP_TYPE_STUB 0x0040 /* If the type is a stub. */ #define SIP_TYPE_NONLAZY 0x0080 /* If the type has a non-lazy method. */ #define SIP_TYPE_SUPER_INIT 0x0100 /* If the instance's super init should be called. */ #define SIP_TYPE_LIMITED_API 0x0200 /* Use the limited API. If this is more generally required it may need to be moved to the module definition. */ /* * The following are part of the public API. */ #define sipTypeIsClass(td) (((td)->td_flags & SIP_TYPE_TYPE_MASK) == SIP_TYPE_CLASS) #define sipTypeIsNamespace(td) (((td)->td_flags & SIP_TYPE_TYPE_MASK) == SIP_TYPE_NAMESPACE) #define sipTypeIsMapped(td) (((td)->td_flags & SIP_TYPE_TYPE_MASK) == SIP_TYPE_MAPPED) #define sipTypeIsEnum(td) (((td)->td_flags & SIP_TYPE_TYPE_MASK) == SIP_TYPE_ENUM) #define sipTypeIsScopedEnum(td) (((td)->td_flags & SIP_TYPE_TYPE_MASK) == SIP_TYPE_SCOPED_ENUM) #define sipTypeAsPyTypeObject(td) ((td)->td_py_type) #define sipTypeName(td) sipNameFromPool((td)->td_module, (td)->td_cname) #define sipTypePluginData(td) ((td)->td_plugin_data) /* * These are deprecated. */ #define sipClassName(w) PyString_FromString(Py_TYPE(w)->tp_name) #define sipIsExactWrappedType(wt) (sipTypeAsPyTypeObject((wt)->wt_td) == (PyTypeObject *)(wt)) /* * The following are not part of the public API. */ #define sipTypeIsAbstract(td) ((td)->td_flags & SIP_TYPE_ABSTRACT) #define sipTypeHasSCC(td) ((td)->td_flags & SIP_TYPE_SCC) #define sipTypeAllowNone(td) ((td)->td_flags & SIP_TYPE_ALLOW_NONE) #define sipTypeIsStub(td) ((td)->td_flags & SIP_TYPE_STUB) #define sipTypeSetStub(td) ((td)->td_flags |= SIP_TYPE_STUB) #define sipTypeHasNonlazyMethod(td) ((td)->td_flags & SIP_TYPE_NONLAZY) #define sipTypeCallSuperInit(td) ((td)->td_flags & SIP_TYPE_SUPER_INIT) #define sipTypeUseLimitedAPI(td) ((td)->td_flags & SIP_TYPE_LIMITED_API) /* * Get various names from the string pool for various data types. */ #define sipNameFromPool(em, mr) (&((em)->em_strings)[(mr)]) #define sipNameOfModule(em) sipNameFromPool((em), (em)->em_name) #define sipPyNameOfContainer(cod, td) sipNameFromPool((td)->td_module, (cod)->cod_name) #define sipPyNameOfEnum(etd) sipNameFromPool((etd)->etd_base.td_module, (etd)->etd_name) /* * The following are PyQt4-specific extensions. In SIP v5 they will be pushed * out to a plugin supplied by PyQt4. */ /* * The description of a Qt signal for PyQt4. */ typedef struct _pyqt4QtSignal { /* The C++ name and signature of the signal. */ const char *signature; /* The optional docstring. */ const char *docstring; /* * If the signal is an overload of regular methods then this points to the * code that implements those methods. */ PyMethodDef *non_signals; /* * The hack to apply when built against Qt5: * * 0 - no hack * 1 - add an optional None * 2 - add an optional [] * 3 - add an optional False */ int hack; } pyqt4QtSignal; /* * This is the PyQt4-specific extension to the generated class type structure. */ typedef struct _pyqt4ClassPluginDef { /* A pointer to the QObject sub-class's staticMetaObject class variable. */ const void *static_metaobject; /* * A set of flags. At the moment only bit 0 is used to say if the type is * derived from QFlags. */ unsigned flags; /* * The table of signals emitted by the type. These are grouped by signal * name. */ const pyqt4QtSignal *qt_signals; } pyqt4ClassPluginDef; /* * The following are PyQt5-specific extensions. In SIP v5 they will be pushed * out to a plugin supplied by PyQt5. */ /* * The description of a Qt signal for PyQt5. */ typedef int (*pyqt5EmitFunc)(void *, PyObject *); typedef struct _pyqt5QtSignal { /* The normalised C++ name and signature of the signal. */ const char *signature; /* The optional docstring. */ const char *docstring; /* * If the signal is an overload of regular methods then this points to the * code that implements those methods. */ PyMethodDef *non_signals; /* * If the signal has optional arguments then this function will implement * emit() for the signal. */ pyqt5EmitFunc emitter; } pyqt5QtSignal; /* * This is the PyQt5-specific extension to the generated class type structure. */ typedef struct _pyqt5ClassPluginDef { /* A pointer to the QObject sub-class's staticMetaObject class variable. */ const void *static_metaobject; /* * A set of flags. At the moment only bit 0 is used to say if the type is * derived from QFlags. */ unsigned flags; /* * The table of signals emitted by the type. These are grouped by signal * name. */ const pyqt5QtSignal *qt_signals; /* The name of the interface that the class defines. */ const char *qt_interface; } pyqt5ClassPluginDef; #ifdef __cplusplus } #endif #endif sip-6.8.6/sipbuild/module/source/12/sip.pyi000066400000000000000000000056171464421045000204770ustar00rootroot00000000000000# SPDX-License-Identifier: BSD-2-Clause # Copyright (c) 2024 Phil Thompson from typing import Any, Generic, Iterable, overload, Sequence, TypeVar, Union # PEP 484 has no explicit support for the buffer protocol so we just name types # we know that implement it. Buffer = Union[bytes, bytearray, memoryview, 'array', 'voidptr'] # Constants. SIP_VERSION = ... # type: int SIP_VERSION_STR = ... # type: str # The bases for SIP generated types. class wrappertype: def __init__(self, *args, **kwargs) -> None: ... class simplewrapper: def __init__(self, *args, **kwargs) -> None: ... class wrapper(simplewrapper): ... # The array type. _T = TypeVar('_T') class array(Sequence[_T], Generic[_T]): @overload def __getitem__(self, key: int) -> _T: ... @overload def __getitem__(self, key: slice) -> 'array[_T]': ... @overload def __setitem__(self, key: int, value: _T) -> None: ... @overload def __setitem__(self, key: slice, value: Iterable[_T]) -> None: ... @overload def __delitem__(self, key: int) -> None: ... @overload def __delitem__(self, key: slice) -> None: ... def __len__(self) -> int: ... # The voidptr type. class voidptr: def __init__(self, addr: Union[int, Buffer], size: int = -1, writeable: bool = True) -> None: ... def __int__(self) -> int: ... @overload def __getitem__(self, i: int) -> bytes: ... @overload def __getitem__(self, s: slice) -> 'voidptr': ... def __len__(self) -> int: ... def __setitem__(self, i: Union[int, slice], v: Buffer) -> None: ... def asarray(self, size: int = -1) -> array[int]: ... # Python doesn't expose the capsule type. def ascapsule(self) -> Any: ... def asstring(self, size: int = -1) -> bytes: ... def getsize(self) -> int: ... def getwriteable(self) -> bool: ... def setsize(self, size: int) -> None: ... def setwriteable(self, writeable: bool) -> None: ... # Remaining functions. def assign(obj: simplewrapper, other: simplewrapper) -> None: ... def cast(obj: simplewrapper, type: wrappertype) -> simplewrapper: ... def delete(obj: simplewrapper) -> None: ... def dump(obj: simplewrapper) -> None: ... def enableautoconversion(type: wrappertype, enable: bool) -> bool: ... def enableoverflowchecking(enable: bool) -> bool: ... def getapi(name: str) -> int: ... def isdeleted(obj: simplewrapper) -> bool: ... def ispycreated(obj: simplewrapper) -> bool: ... def ispyowned(obj: simplewrapper) -> bool: ... def setapi(name: str, version: int) -> None: ... def setdeleted(obj: simplewrapper) -> None: ... def setdestroyonexit(destroy: bool) -> None: ... def settracemask(mask: int) -> None: ... def transferback(obj: wrapper) -> None: ... def transferto(obj: wrapper, owner: wrapper) -> None: ... def unwrapinstance(obj: simplewrapper) -> None: ... def wrapinstance(addr: int, type: wrappertype) -> simplewrapper: ... sip-6.8.6/sipbuild/module/source/12/sip.rst.in000066400000000000000000000347261464421045000211160ustar00rootroot00000000000000The main purpose of the :py:mod:`~@SIP_MODULE_FQ_NAME@` module is to provide functionality common to all SIP generated bindings. It is loaded automatically and most of the time you will completely ignore it. However, it does expose some functionality that can be used by applications. .. py:class:: @SIP_MODULE_FQ_NAME@.array This is the type object for the type SIP uses to represent an array of wrapped C/C++ instances. (It can also present an array of a limited number of basic C/C++ types but such arrays cannot, at the moment, be created from Python.) Arrays can be indexed and elements can be modified in situ. Arrays cannot be resized. Arrays support the buffer protocol. .. py:method:: __init__(type, nr_elements) :param type: the type of an array element. :param nr_elements: the number of elements in the array. For a C++ class each element of the array is created by calling the class's argumentless constructor. For a C structure then the memory is simply allocated on the heap. .. py:method:: __getitem__(idx) This returns the element at a given index. It is not a copy of the element. If this is called a number of times for the same index then a different Python object will be returned each time but each will refer to the same C/C++ instance. :param idx: is the index which may either be an integer, an object that implements ``__index__()`` or a slice object. :return: the element. If the index is an integer then the item will be a single object of the type of the array. If the index is a slice object then the item will be a new :py:class:`~@SIP_MODULE_FQ_NAME@.array` object containing the chosen subset of the original array. .. py:method:: __len__() This returns the length of the array. :return: the number of elements in the array. .. py:method:: __setitem__(idx, item) This updates the array at a given index. :param idx: is the index which may either be an integer, an object that implements ``__index__()`` or a slice object. :param item: is the item that will be assigned to the element currently at the index. It must have the same type as the element it is being assigned to. .. py:function:: assign(obj, other) This does the Python equivalent of invoking the assignment operator of a C++ instance (i.e. ``*obj = other``). :param obj: the Python object being assigned to. :param other: the Python object being assigned. .. py:function:: cast(obj, type) This does the Python equivalent of casting a C++ instance to one of its sub or super-class types. :param obj: the Python object. :param type type: the type. :return: a new Python object is that wraps the same C++ instance as *obj*, but has the type *type*. .. py:function:: delete(obj) For C++ instances this calls the C++ destructor. For C structures it returns the structure's memory to the heap. :param obj: the Python object. .. py:function:: dump(obj) This displays various bits of useful information about the internal state of the Python object that wraps a C++ instance or C structure. Note that the reference count that is displayed has the same caveat as that of :func:`sys.getrefcount`. :param obj: the Python object. .. py:function:: enableautoconversion(type, enable) Instances of some classes may be automatically converted to other Python objects even though the class has been wrapped. This allows that behaviour to be suppressed so that an instances of the wrapped class is returned instead. By default it is enabled. :param type type: the Python type object. :param bool enable: is ``True`` if auto-conversion should be enabled for the type. :return: ``True`` or ``False`` depending on whether or not auto-conversion was previously enabled for the type. This allows the previous state to be restored later on. .. py:function:: enableoverflowchecking(enable) This enables or disables the checking for overflows when converting Python integer objects to C/C++ integer types. When it is enabled an exception is raised when the value of a Python integer object is too large to fit in the corresponding C/C++ type. By default it is disabled. :param bool enable: is ``True`` if overflow checking should be enabled. :return: ``True`` or ``False`` depending on whether or not overflow checking was previously enabled. This allows the previous state to be restored later on. .. py:function:: isdeleted(obj) This checks if the C++ instance or C structure has been deleted and returned to the heap. :param obj: the Python object. :return: ``True`` if the C/C++ instance has been deleted. .. py:function:: ispycreated(obj) This checks if the C++ instance or C structure was created by Python. If it was then it is possible to call a C++ instance's protected methods. :param obj: the Python object. :return: ``True`` if the C/C++ instance was created by Python. .. py:function:: ispyowned(obj) This checks if the C++ instance or C structure is owned by Python. :param obj: the Python object. :return: ``True`` if the C/C++ instance is owned by Python. .. py:function:: setdeleted(obj) This marks the C++ instance or C structure as having been deleted and returned to the heap so that future references to it raise an exception rather than cause a program crash. Normally SIP handles such things automatically, but there may be circumstances where this isn't possible. :param obj: the Python object. .. py:function:: setdestroyonexit(destroy) When the Python interpreter exits it garbage collects those objects that it can. This means that any corresponding C++ instances and C structures owned by Python are destroyed. Unfortunately this happens in an unpredictable order and so can cause memory faults within the wrapped library. Calling this function with a value of ``False`` disables the automatic destruction of C++ instances and C structures. :param bool destroy: ``True`` if all C++ instances and C structures owned by Python should be destroyed when the interpreter exits. This is the default. .. py:function:: settracemask(mask) If the bindings have been created with tracing enabled then the generated code will include debugging statements that trace the execution of the code. (It is particularly useful when trying to understand the operation of a C++ library's virtual function calls.) :param int mask: the mask that determines which debugging statements are enabled. Debugging statements are generated at the following points: - in a C++ virtual function (*mask* is ``0x0001``) - in a C++ constructor (*mask* is ``0x0002``) - in a C++ destructor (*mask* is ``0x0004``) - in a Python type's __init__ method (*mask* is ``0x0008``) - in a Python type's __del__ method (*mask* is ``0x0010``) - in a Python type's ordinary method (*mask* is ``0x0020``). By default the trace mask is zero and all debugging statements are disabled. .. py:class:: simplewrapper This is an alternative type object than can be used as the base type of an instance wrapped by SIP. Objects using this are smaller than those that use the default :py:class:`~@SIP_MODULE_FQ_NAME@.wrapper` type but do not support the concept of object ownership. .. py:method:: __dtor__ If the wrapped instance is a C++ class with a virtual destructor then this is called by the destructor. .. py:data:: SIP_VERSION This is a Python integer object that represents the SIP version number as a 3 part hexadecimal number (e.g. v5.0.0 is represented as ``0x050000``). Note that it is not the version number of the :py:mod:`~@SIP_MODULE_FQ_NAME@` module. .. py:data:: SIP_VERSION_STR This is a Python string object that defines the SIP version number as represented as a string. For development versions it will contain ``.dev``. Note that it is not the version number of the :py:mod:`~@SIP_MODULE_FQ_NAME@` module. .. py:function:: transferback(obj) This transfers ownership of a C++ instance or C structure to Python. :param obj: the Python object. .. py:function:: transferto(obj, owner) This transfers ownership of a C++ instance or C structure to C/C++. :param obj: the Python object. :param owner: an optional wrapped instance that *obj* becomes associated with with regard to the cyclic garbage collector. If *owner* is ``None`` then no such association is made. If *owner* is the same value as *obj* then any reference cycles involving *obj* can never be detected or broken by the cyclic garbage collector. Responsibility for destroying the C++ instance’s destructor or freeing the C structure is always transfered to C/C++. .. py:function:: unwrapinstance(obj) This returns the address, as an integer, of a wrapped C/C++ structure or class instance. :param obj: the Python object. :return: an integer that is the address of the C/C++ instance. .. py:class:: voidptr This is the type object for the type SIP uses to represent a C/C++ ``void *``. It may have a size associated with the address in which case the Python buffer interface is supported. The type has the following methods. .. py:method:: __init__(address, size=-1, writeable=True) :param address: the address, either another :py:class:`~@SIP_MODULE_FQ_NAME@.voidptr`, ``None``, a Python Capsule, an object that implements the buffer protocol or an integer. :param int size: the optional associated size of the block of memory and is negative if the size is not known. :param bool writeable: set if the memory is writeable. If it is not specified, and *address* is a :py:class:`~@SIP_MODULE_FQ_NAME@.voidptr` instance then its value will be used. .. py:method:: __getitem__(idx) This returns the item at a given index. An exception will be raised if the address does not have an associated size. In this way it behaves like a Python ``memoryview`` object. :param idx: is the index which may either be an integer, an object that implements ``__index__()`` or a slice object. :return: the item. If the index is an integer then the item will be a bytes object containing the single byte at that index. If the index is a slice object then the item will be a new :py:class:`~@SIP_MODULE_FQ_NAME@.voidptr` object defining the subset of the memory corresponding to the slice. .. py:method:: __int__() This returns the address as an integer. :return: the integer address. .. py:method:: __len__() This returns the size associated with the address. :return: the associated size. An exception will be raised if there is none. .. py:method:: __setitem__(idx, item) This updates the memory at a given index. An exception will be raised if the address does not have an associated size or is not writable. In this way it behaves like a Python ``memoryview`` object. :param idx: is the index which may either be an integer, an object that implements ``__index__()`` or a slice object. :param item: is the data that will update the memory defined by the index. It must implement the buffer interface and be the same size as the data that is being updated. .. py:method:: asarray(size=-1) This returns the block of memory as an :py:class:`~@SIP_MODULE_FQ_NAME@.array` object. The memory is **not** copied. :param int size: the size of the array. If it is negative then the size associated with the address is used. If there is no associated size then an exception is raised. :return: the :py:class:`~@SIP_MODULE_FQ_NAME@.array` object. .. py:method:: ascapsule() This returns the address as an unnamed Python Capsule. :return: the Capsule. .. py:method:: asstring(size=-1) This returns a copy of the block of memory as a bytes object. :param int size: the number of bytes to copy. If it is negative then the size associated with the address is used. If there is no associated size then an exception is raised. :return: the bytes object. .. py:method:: getsize() This returns the size associated with the address. :return: the associated size which will be negative if there is none. .. py:method:: setsize(size) This sets the size associated with the address. :param int size: the size to associate. If it is negative then no size is associated. .. py:method:: getwriteable() This returns the writeable state of the memory. :return: ``True`` if the memory is writeable. .. py:method:: setwriteable(writeable) This sets the writeable state of the memory. :param bool writeable: the writeable state to set. .. py:function:: wrapinstance(addr, type) This wraps a C structure or C++ class instance in a Python object. If the instance has already been wrapped then a new reference to the existing object is returned. :param int addr: the address of the instance as a number. :param type type: the Python type of the instance. :return: the Python object that wraps the instance. .. py:class:: wrapper This is the type object of the default base type of all instances wrapped by SIP. .. py:class:: wrappertype This is the type object of the metatype of the :py:class:`~@SIP_MODULE_FQ_NAME@.wrapper` type. sip-6.8.6/sipbuild/module/source/12/sip_array.c000066400000000000000000000503671464421045000213200ustar00rootroot00000000000000/* SPDX-License-Identifier: BSD-2-Clause */ /* * This file implements the API for the array type. * * Copyright (c) 2024 Phil Thompson */ /* Remove when Python v3.12 is no longer supported. */ #define PY_SSIZE_T_CLEAN #include #include #include #include "sipint.h" #include "sip_array.h" /* The object data structure. */ typedef struct { PyObject_HEAD void *data; const sipTypeDef *td; const char *format; size_t stride; Py_ssize_t len; int flags; PyObject *owner; } sipArrayObject; static void bad_key(PyObject *key); static int check_index(sipArrayObject *array, Py_ssize_t idx); static int check_writable(sipArrayObject *array); static PyObject *create_array(void *data, const sipTypeDef *td, const char *format, size_t stride, Py_ssize_t len, int flags, PyObject *owner); static void *element(sipArrayObject *array, Py_ssize_t idx); static void *get_slice(sipArrayObject *array, PyObject *value, Py_ssize_t len); static const char *get_type_name(sipArrayObject *array); static void *get_value(sipArrayObject *array, PyObject *value); static void init_array(sipArrayObject *array, void *data, const sipTypeDef *td, const char *format, size_t stride, Py_ssize_t len, int flags, PyObject *owner); /* * Implement len() for the type. */ static Py_ssize_t sipArray_length(PyObject *self) { return ((sipArrayObject *)self)->len; } /* * Implement sequence item sub-script for the type. */ static PyObject *sipArray_item(PyObject *self, Py_ssize_t idx) { sipArrayObject *array = (sipArrayObject *)self; PyObject *py_item; void *data; if (check_index(array, idx) < 0) return NULL; data = element(array, idx); if (array->td != NULL) { py_item = sip_api_convert_from_type(data, array->td, NULL); } else { switch (*array->format) { case 'b': py_item = PyLong_FromLong(*(char *)data); break; case 'B': py_item = PyLong_FromUnsignedLong(*(unsigned char *)data); break; case 'h': py_item = PyLong_FromLong(*(short *)data); break; case 'H': py_item = PyLong_FromUnsignedLong(*(unsigned short *)data); break; case 'i': py_item = PyLong_FromLong(*(int *)data); break; case 'I': py_item = PyLong_FromUnsignedLong(*(unsigned int *)data); break; case 'f': py_item = PyFloat_FromDouble(*(float *)data); break; case 'd': py_item = PyFloat_FromDouble(*(double *)data); break; default: py_item = NULL; } } return py_item; } /* The sequence methods data structure. */ static PySequenceMethods sipArray_SequenceMethods = { sipArray_length, /* sq_length */ 0, /* sq_concat */ 0, /* sq_repeat */ sipArray_item, /* sq_item */ 0, /* sq_slice */ 0, /* sq_ass_item */ 0, /* sq_ass_slice */ 0, /* sq_contains */ 0, /* sq_inplace_concat */ 0, /* sq_inplace_repeat */ }; /* * Implement mapping sub-script for the type. */ static PyObject *sipArray_subscript(PyObject *self, PyObject *key) { sipArrayObject *array = (sipArrayObject *)self; if (PyIndex_Check(key)) { Py_ssize_t idx = PyNumber_AsSsize_t(key, PyExc_IndexError); if (idx == -1 && PyErr_Occurred()) return NULL; if (idx < 0) idx += array->len; return sipArray_item(self, idx); } if (PySlice_Check(key)) { Py_ssize_t start, stop, step, slicelength; if (sip_api_convert_from_slice_object(key, array->len, &start, &stop, &step, &slicelength) < 0) return NULL; if (step != 1) { PyErr_SetNone(PyExc_NotImplementedError); return NULL; } return create_array(element(array, start), array->td, array->format, array->stride, slicelength, (array->flags & ~SIP_OWNS_MEMORY), array->owner); } bad_key(key); return NULL; } /* * Implement mapping assignment sub-script for the type. */ static int sipArray_ass_subscript(PyObject *self, PyObject *key, PyObject *value) { sipArrayObject *array = (sipArrayObject *)self; Py_ssize_t start, len; void *value_data; if (check_writable(array) < 0) return -1; if (PyIndex_Check(key)) { start = PyNumber_AsSsize_t(key, PyExc_IndexError); if (start == -1 && PyErr_Occurred()) return -1; if (start < 0) start += array->len; if (check_index(array, start) < 0) return -1; if ((value_data = get_value(array, value)) == NULL) return -1; len = 1; } else if (PySlice_Check(key)) { Py_ssize_t stop, step; if (sip_api_convert_from_slice_object(key, array->len, &start, &stop, &step, &len) < 0) return -1; if (step != 1) { PyErr_SetNone(PyExc_NotImplementedError); return -1; } if ((value_data = get_slice(array, value, len)) == NULL) return -1; } else { bad_key(key); return -1; } if (array->td != NULL) { const sipClassTypeDef *ctd = (const sipClassTypeDef *)(array->td); sipAssignFunc assign; Py_ssize_t i; if ((assign = ctd->ctd_assign) == NULL) { PyErr_Format(PyExc_TypeError, "a sip.array cannot copy '%s'", Py_TYPE(self)->tp_name); return -1; } for (i = 0; i < len; ++i) { assign(array->data, start + i, value_data); value_data = (char *)value_data + array->stride; } } else { memmove(element(array, start), value_data, len * array->stride); } return 0; } /* The mapping methods data structure. */ static PyMappingMethods sipArray_MappingMethods = { sipArray_length, /* mp_length */ sipArray_subscript, /* mp_subscript */ sipArray_ass_subscript, /* mp_ass_subscript */ }; /* * The buffer implementation. */ static int sipArray_getbuffer(PyObject *self, Py_buffer *view, int flags) { sipArrayObject *array = (sipArrayObject *)self; const char *format; Py_ssize_t itemsize; if (view == NULL) return 0; if (((flags & PyBUF_WRITABLE) == PyBUF_WRITABLE) && (array->flags & SIP_READ_ONLY)) { PyErr_SetString(PyExc_BufferError, "object is not writable"); return -1; } view->obj = self; Py_INCREF(self); /* * If there is no format, ie. it is a wrapped type, then present it as * bytes. */ if ((format = array->format) == NULL) { format = "B"; itemsize = sizeof (unsigned char); } else { itemsize = array->stride; } view->buf = array->data; view->len = array->len * array->stride; view->readonly = (array->flags & SIP_READ_ONLY); view->itemsize = itemsize; view->format = NULL; if ((flags & PyBUF_FORMAT) == PyBUF_FORMAT) /* Note that the need for a cast is probably a Python bug. */ view->format = (char *)format; view->ndim = 1; view->shape = NULL; if ((flags & PyBUF_ND) == PyBUF_ND) view->shape = &view->len; view->strides = NULL; if ((flags & PyBUF_STRIDES) == PyBUF_STRIDES) view->strides = &view->itemsize; view->suboffsets = NULL; view->internal = NULL; return 0; } /* The buffer methods data structure. */ static PyBufferProcs sipArray_BufferProcs = { sipArray_getbuffer, /* bf_getbuffer */ 0 /* bf_releasebuffer */ }; /* * The instance deallocation function. */ static void sipArray_dealloc(PyObject *self) { sipArrayObject *array = (sipArrayObject *)self; if (array->flags & SIP_OWNS_MEMORY) { if (array->td != NULL) ((const sipClassTypeDef *)(array->td))->ctd_array_delete(array->data); else PyMem_Free(array->data); } else { Py_XDECREF(array->owner); } } /* * Implement __repr__ for the type. */ static PyObject *sipArray_repr(PyObject *self) { sipArrayObject *array = (sipArrayObject *)self; return PyUnicode_FromFormat("sip.array(%s, %zd)", get_type_name(array), array->len); } /* * Implement __new__ for the type. */ static PyObject *sipArray_new(PyTypeObject *cls, PyObject *args, PyObject *kw) { #if PY_VERSION_HEX >= 0x030d0000 static char * const kwlist[] = {"", "", NULL}; #else static char *kwlist[] = {"", "", NULL}; #endif Py_ssize_t length; PyObject *array, *type; const sipClassTypeDef *ctd; if (!PyArg_ParseTupleAndKeywords(args, kw, "O!n:array", kwlist, &sipWrapperType_Type, &type, &length)) return NULL; ctd = (const sipClassTypeDef *)((sipWrapperType *)type)->wt_td; /* We require the array delete helper which was added in ABI v12.11. */ if (ctd->ctd_base.td_module->em_api_minor < 11) { PyErr_SetString(PyExc_TypeError, "a sip.array can only be created for types using ABI v12.11 or later"); return NULL; } if (ctd->ctd_array == NULL || ctd->ctd_sizeof == 0) { PyErr_Format(PyExc_TypeError, "a sip.array cannot be created for '%s'", Py_TYPE(type)->tp_name); return NULL; } if (length < 0) { PyErr_SetString(PyExc_ValueError, "a sip.array length cannot be negative"); return NULL; } /* Create the instance. */ if ((array = cls->tp_alloc(cls, 0)) == NULL) return NULL; init_array((sipArrayObject *)array, ctd->ctd_array(length), &ctd->ctd_base, NULL, ctd->ctd_sizeof, length, SIP_OWNS_MEMORY, NULL); return array; } /* The type data structure. */ PyTypeObject sipArray_Type = { PyVarObject_HEAD_INIT(NULL, 0) "sip.array", /* tp_name */ sizeof (sipArrayObject), /* tp_basicsize */ 0, /* tp_itemsize */ sipArray_dealloc, /* tp_dealloc */ 0, /* tp_print */ 0, /* tp_getattr */ 0, /* tp_setattr */ 0, /* tp_reserved */ sipArray_repr, /* tp_repr */ 0, /* tp_as_number */ &sipArray_SequenceMethods, /* tp_as_sequence */ &sipArray_MappingMethods, /* tp_as_mapping */ 0, /* tp_hash */ 0, /* tp_call */ 0, /* tp_str */ 0, /* tp_getattro */ 0, /* tp_setattro */ &sipArray_BufferProcs, /* tp_as_buffer */ #if defined(Py_TPFLAGS_HAVE_NEWBUFFER) Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_NEWBUFFER, /* tp_flags */ #else Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /* tp_flags */ #endif 0, /* tp_doc */ 0, /* tp_traverse */ 0, /* tp_clear */ 0, /* tp_richcompare */ 0, /* tp_weaklistoffset */ 0, /* tp_iter */ 0, /* tp_iternext */ 0, /* tp_methods */ 0, /* tp_members */ 0, /* tp_getset */ 0, /* tp_base */ 0, /* tp_dict */ 0, /* tp_descr_get */ 0, /* tp_descr_set */ 0, /* tp_dictoffset */ 0, /* tp_init */ 0, /* tp_alloc */ sipArray_new, /* tp_new */ 0, /* tp_free */ 0, /* tp_is_gc */ 0, /* tp_bases */ 0, /* tp_mro */ 0, /* tp_cache */ 0, /* tp_subclasses */ 0, /* tp_weaklist */ 0, /* tp_del */ 0, /* tp_version_tag */ 0, /* tp_finalize */ 0, /* tp_vectorcall */ }; /* * Return TRUE if an object is a sip.array with elements of a given type. */ int sip_array_can_convert(PyObject *obj, const sipTypeDef *td) { if (!PyObject_TypeCheck(obj, &sipArray_Type)) return FALSE; return (((sipArrayObject *)obj)->td == td); } /* * Return the address and number of elements of a sip.array for which * sip_array_can_convert has already returned TRUE. */ void sip_array_convert(PyObject *obj, void **data, Py_ssize_t *size) { sipArrayObject *array = (sipArrayObject *)obj; *data = array->data; *size = array->len; } /* * Check that an array is writable. */ static int check_writable(sipArrayObject *array) { if (array->flags & SIP_READ_ONLY) { PyErr_SetString(PyExc_TypeError, "sip.array object is read-only"); return -1; } return 0; } /* * Check that an index is valid for an array. */ static int check_index(sipArrayObject *array, Py_ssize_t idx) { if (idx >= 0 && idx < array->len) return 0; PyErr_SetString(PyExc_IndexError, "index out of bounds"); return -1; } /* * Raise an exception about a bad sub-script key. */ static void bad_key(PyObject *key) { PyErr_Format(PyExc_TypeError, "cannot index a sip.array object using '%s'", Py_TYPE(key)->tp_name); } /* * Get the address of an element of an array. */ static void *element(sipArrayObject *array, Py_ssize_t idx) { return (unsigned char *)(array->data) + idx * array->stride; } /* * Get the address of a value that will be copied to an array. */ static void *get_value(sipArrayObject *array, PyObject *value) { static union { signed char s_char_t; unsigned char u_char_t; signed short s_short_t; unsigned short u_short_t; signed int s_int_t; unsigned int u_int_t; float float_t; double double_t; } static_data; void *data; if (array->td != NULL) { int iserr = FALSE; data = sip_api_force_convert_to_type(value, array->td, NULL, SIP_NOT_NONE|SIP_NO_CONVERTORS, NULL, &iserr); } else { PyErr_Clear(); switch (*array->format) { case 'b': static_data.s_char_t = sip_api_long_as_char(value); data = &static_data.s_char_t; break; case 'B': static_data.u_char_t = sip_api_long_as_unsigned_char(value); data = &static_data.u_char_t; break; case 'h': static_data.s_short_t = sip_api_long_as_short(value); data = &static_data.s_short_t; break; case 'H': static_data.u_short_t = sip_api_long_as_unsigned_short(value); data = &static_data.u_short_t; break; case 'i': static_data.s_int_t = sip_api_long_as_int(value); data = &static_data.s_int_t; break; case 'I': static_data.u_int_t = sip_api_long_as_unsigned_int(value); data = &static_data.u_int_t; break; case 'f': static_data.float_t = (float)PyFloat_AsDouble(value); data = &static_data.float_t; break; case 'd': static_data.double_t = PyFloat_AsDouble(value); data = &static_data.double_t; break; default: data = NULL; } if (PyErr_Occurred()) data = NULL; } return data; } /* * Get the address of an value that will be copied to an array slice. */ static void *get_slice(sipArrayObject *array, PyObject *value, Py_ssize_t len) { sipArrayObject *other = (sipArrayObject *)value; if (!PyObject_IsInstance(value, (PyObject *)&sipArray_Type) || array->td != other->td || strcmp(array->format, other->format) != 0) { PyErr_Format(PyExc_TypeError, "can only assign another array of %s to the slice", get_type_name(array)); return NULL; } if (other->len != len) { PyErr_Format(PyExc_TypeError, "the array being assigned must have length %zd", len); return NULL; } if (other->stride == array->stride) { PyErr_Format(PyExc_TypeError, "the array being assigned must have stride %zu", array->stride); return NULL; } return other->data; } /* * Get the name of the type of an element of an array. */ static const char *get_type_name(sipArrayObject *array) { const char *type_name; if (array->td != NULL) { type_name = sipTypeName(array->td); } else { switch (*array->format) { case 'b': type_name = "char"; break; case 'B': type_name = "unsigned char"; break; case 'h': type_name = "short"; break; case 'H': type_name = "unsigned short"; break; case 'i': type_name = "int"; break; case 'I': type_name = "unsigned int"; break; case 'f': type_name = "float"; break; case 'd': type_name = "double"; break; default: type_name = ""; } } return type_name; } /* * Create an array for the C API. */ static PyObject *create_array(void *data, const sipTypeDef *td, const char *format, size_t stride, Py_ssize_t len, int flags, PyObject *owner) { sipArrayObject *array; if ((array = PyObject_NEW(sipArrayObject, &sipArray_Type)) == NULL) return NULL; init_array(array, data, td, format, stride, len, flags, owner); return (PyObject *)array; } /* * Initialise an array. */ static void init_array(sipArrayObject *array, void *data, const sipTypeDef *td, const char *format, size_t stride, Py_ssize_t len, int flags, PyObject *owner) { array->data = data; array->td = td; array->format = format; array->stride = stride; array->len = len; array->flags = flags; if (flags & SIP_OWNS_MEMORY) { /* This is a borrowed reference to itself. */ array->owner = (PyObject *)array; } else { Py_XINCREF(owner); array->owner = owner; } } /* * Wrap an array of instances of a fundamental type. At the moment format must * be either "b" (char), "B" (unsigned char), "h" (short), "H" (unsigned * short), "i" (int), "I" (unsigned int), "f" (float) or "d" (double). */ PyObject *sip_api_convert_to_array(void *data, const char *format, Py_ssize_t len, int flags) { size_t stride; assert(len >= 0); if (data == NULL) { Py_INCREF(Py_None); return Py_None; } switch (*format) { case 'b': stride = sizeof (char); break; case 'B': stride = sizeof (unsigned char); break; case 'h': stride = sizeof (short); break; case 'H': stride = sizeof (unsigned short); break; case 'i': stride = sizeof (int); break; case 'I': stride = sizeof (unsigned int); break; case 'f': stride = sizeof (float); break; case 'd': stride = sizeof (double); break; default: PyErr_Format(PyExc_ValueError, "'%c' is not a supported format", format); return NULL; } return create_array(data, NULL, format, stride, len, flags, NULL); } /* * Wrap an array of instances of a defined type. */ PyObject *sip_api_convert_to_typed_array(void *data, const sipTypeDef *td, const char *format, size_t stride, Py_ssize_t len, int flags) { if (data == NULL) { Py_INCREF(Py_None); return Py_None; } assert(stride > 0); assert(len >= 0); return create_array(data, td, format, stride, len, flags, NULL); } sip-6.8.6/sipbuild/module/source/12/sip_array.h000066400000000000000000000015031464421045000213110ustar00rootroot00000000000000/* SPDX-License-Identifier: BSD-2-Clause */ /* * This file defines the API for the array type. * * Copyright (c) 2024 Phil Thompson */ #ifndef _SIP_ARRAY_H #define _SIP_ARRAY_H /* Remove when Python v3.12 is no longer supported. */ #define PY_SSIZE_T_CLEAN #include #include "sip.h" #ifdef __cplusplus extern "C" { #endif extern PyTypeObject sipArray_Type; PyObject *sip_api_convert_to_array(void *data, const char *format, Py_ssize_t len, int flags); PyObject *sip_api_convert_to_typed_array(void *data, const sipTypeDef *td, const char *format, size_t stride, Py_ssize_t len, int flags); int sip_array_can_convert(PyObject *obj, const sipTypeDef *td); void sip_array_convert(PyObject *obj, void **data, Py_ssize_t *size); #ifdef __cplusplus } #endif #endif sip-6.8.6/sipbuild/module/source/12/sipint.h000066400000000000000000000140311464421045000206260ustar00rootroot00000000000000/* SPDX-License-Identifier: BSD-2-Clause */ /* * This file defines the SIP library internal interfaces. * * Copyright (c) 2024 Phil Thompson */ #ifndef _SIPINT_H #define _SIPINT_H /* Remove when Python v3.12 is no longer supported. */ #define PY_SSIZE_T_CLEAN #include #include #include #include "sip.h" #ifdef __cplusplus extern "C" { #endif #undef TRUE #define TRUE 1 #undef FALSE #define FALSE 0 /* * This defines a single entry in an object map's hash table. */ typedef struct { void *key; /* The C/C++ address. */ sipSimpleWrapper *first; /* The first object at this address. */ } sipHashEntry; /* * This defines the interface to a hash table class for mapping C/C++ addresses * to the corresponding wrapped Python object. */ typedef struct { int primeIdx; /* Index into table sizes. */ uintptr_t size; /* Size of hash table. */ uintptr_t unused; /* Nr. unused in hash table. */ uintptr_t stale; /* Nr. stale in hash table. */ sipHashEntry *hash_array; /* Current hash table. */ } sipObjectMap; /* * Support for the descriptors. */ extern PyTypeObject sipMethodDescr_Type; PyObject *sipMethodDescr_New(PyMethodDef *pmd); PyObject *sipMethodDescr_Copy(PyObject *orig, PyObject *mixin_name); extern PyTypeObject sipVariableDescr_Type; PyObject *sipVariableDescr_New(sipVariableDef *vd, const sipTypeDef *td, const sipContainerDef *cod); PyObject *sipVariableDescr_Copy(PyObject *orig, PyObject *mixin_name); /* * Support for API versions. */ PyObject *sipGetAPI(PyObject *self, PyObject *args); PyObject *sipSetAPI(PyObject *self, PyObject *args); int sip_api_is_api_enabled(const char *name, int from, int to); int sipIsRangeEnabled(sipExportedModuleDef *em, int range_index); int sipInitAPI(sipExportedModuleDef *em, PyObject *mod_dict); /* * Support for void pointers. */ extern PyTypeObject sipVoidPtr_Type; void *sip_api_convert_to_void_ptr(PyObject *obj); PyObject *sip_api_convert_from_void_ptr(void *val); PyObject *sip_api_convert_from_const_void_ptr(const void *val); PyObject *sip_api_convert_from_void_ptr_and_size(void *val, Py_ssize_t size); PyObject *sip_api_convert_from_const_void_ptr_and_size(const void *val, Py_ssize_t size); /* * Support for int convertors. */ PyObject *sipEnableOverflowChecking(PyObject *self, PyObject *args); int sip_api_enable_overflow_checking(int enable); int sip_api_convert_to_bool(PyObject *o); char sip_api_long_as_char(PyObject *o); signed char sip_api_long_as_signed_char(PyObject *o); unsigned char sip_api_long_as_unsigned_char(PyObject *o); short sip_api_long_as_short(PyObject *o); unsigned short sip_api_long_as_unsigned_short(PyObject *o); int sip_api_long_as_int(PyObject *o); unsigned int sip_api_long_as_unsigned_int(PyObject *o); long sip_api_long_as_long(PyObject *o); unsigned long sip_api_long_as_unsigned_long(PyObject *o); #if defined(HAVE_LONG_LONG) PY_LONG_LONG sip_api_long_as_long_long(PyObject *o); unsigned PY_LONG_LONG sip_api_long_as_unsigned_long_long(PyObject *o); #endif size_t sip_api_long_as_size_t(PyObject *o); extern sipQtAPI *sipQtSupport; /* The Qt support API. */ extern PyTypeObject sipWrapperType_Type; /* The wrapper type type. */ extern sipWrapperType sipSimpleWrapper_Type; /* The simple wrapper type. */ extern sipTypeDef *sipQObjectType; /* The QObject type. */ void *sipGetRx(sipSimpleWrapper *txSelf, const char *sigargs, PyObject *rxObj, const char *slot, const char **memberp); PyObject *sip_api_connect_rx(PyObject *txObj, const char *sig, PyObject *rxObj, const char *slot, int type); PyObject *sip_api_disconnect_rx(PyObject *txObj, const char *sig, PyObject *rxObj,const char *slot); /* * These are part of the SIP API but are also used within the SIP module. */ void *sip_api_malloc(size_t nbytes); void sip_api_free(void *mem); void *sip_api_get_address(sipSimpleWrapper *w); void *sip_api_get_cpp_ptr(sipSimpleWrapper *w, const sipTypeDef *td); PyObject *sip_api_convert_from_type(void *cppPtr, const sipTypeDef *td, PyObject *transferObj); void sip_api_instance_destroyed(sipSimpleWrapper *sipSelf); void sip_api_end_thread(void); void *sip_api_force_convert_to_type(PyObject *pyObj, const sipTypeDef *td, PyObject *transferObj, int flags, int *statep, int *iserrp); void sip_api_free_sipslot(sipSlot *slot); int sip_api_same_slot(const sipSlot *sp, PyObject *rxObj, const char *slot); PyObject *sip_api_invoke_slot(const sipSlot *slot, PyObject *sigargs); PyObject *sip_api_invoke_slot_ex(const sipSlot *slot, PyObject *sigargs, int no_receiver_check); void *sip_api_convert_rx(sipWrapper *txSelf, const char *sigargs, PyObject *rxObj, const char *slot, const char **memberp, int flags); int sip_api_save_slot(sipSlot *sp, PyObject *rxObj, const char *slot); int sip_api_convert_from_slice_object(PyObject *slice, Py_ssize_t length, Py_ssize_t *start, Py_ssize_t *stop, Py_ssize_t *step, Py_ssize_t *slicelength); int sip_api_deprecated(const char *classname, const char *method); /* * These are not part of the SIP API but are used within the SIP module. */ sipClassTypeDef *sipGetGeneratedClassType(const sipEncodedTypeDef *enc, const sipClassTypeDef *ctd); void sipSaveMethod(sipPyMethod *pm,PyObject *meth); int sipGetPending(void **pp, sipWrapper **op, int *fp); int sipIsPending(void); PyObject *sipWrapInstance(void *cpp, PyTypeObject *py_type, PyObject *args, sipWrapper *owner, int flags); void *sipConvertRxEx(sipWrapper *txSelf, const char *sigargs, PyObject *rxObj, const char *slot, const char **memberp, int flags); void sipOMInit(sipObjectMap *om); void sipOMFinalise(sipObjectMap *om); sipSimpleWrapper *sipOMFindObject(sipObjectMap *om, void *key, const sipTypeDef *td); void sipOMAddObject(sipObjectMap *om, sipSimpleWrapper *val); int sipOMRemoveObject(sipObjectMap *om, sipSimpleWrapper *val); #define sipSetBool(p, v) (*(_Bool *)(p) = (v)) #ifdef __cplusplus } #endif #endif sip-6.8.6/sipbuild/module/source/12/siplib.c000066400000000000000000012635511464421045000206130ustar00rootroot00000000000000/* SPDX-License-Identifier: BSD-2-Clause */ /* * SIP library code. * * Copyright (c) 2024 Phil Thompson */ /* Remove when Python v3.12 is no longer supported. */ #define PY_SSIZE_T_CLEAN #include #include #include #include #include #include #include #include #include "sip.h" #include "sipint.h" #include "sip_array.h" /* * The Python metatype for a C++ wrapper type. We inherit everything from the * standard Python metatype except the init and getattro methods and the size * of the type object created is increased to accomodate the extra information * we associate with a wrapped type. */ static PyObject *sipWrapperType_alloc(PyTypeObject *self, Py_ssize_t nitems); static PyObject *sipWrapperType_getattro(PyObject *self, PyObject *name); static int sipWrapperType_init(sipWrapperType *self, PyObject *args, PyObject *kwds); static int sipWrapperType_setattro(PyObject *self, PyObject *name, PyObject *value); PyTypeObject sipWrapperType_Type = { PyVarObject_HEAD_INIT(NULL, 0) "sip.wrappertype", /* tp_name */ sizeof (sipWrapperType), /* tp_basicsize */ 0, /* tp_itemsize */ 0, /* tp_dealloc */ 0, /* tp_print */ 0, /* tp_getattr */ 0, /* tp_setattr */ 0, /* tp_as_async (Python v3.5), tp_compare (Python v2) */ 0, /* tp_repr */ 0, /* tp_as_number */ 0, /* tp_as_sequence */ 0, /* tp_as_mapping */ 0, /* tp_hash */ 0, /* tp_call */ 0, /* tp_str */ sipWrapperType_getattro, /* tp_getattro */ sipWrapperType_setattro, /* tp_setattro */ 0, /* tp_as_buffer */ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /* tp_flags */ 0, /* tp_doc */ 0, /* tp_traverse */ 0, /* tp_clear */ 0, /* tp_richcompare */ 0, /* tp_weaklistoffset */ 0, /* tp_iter */ 0, /* tp_iternext */ 0, /* tp_methods */ 0, /* tp_members */ 0, /* tp_getset */ 0, /* tp_base */ 0, /* tp_dict */ 0, /* tp_descr_get */ 0, /* tp_descr_set */ 0, /* tp_dictoffset */ (initproc)sipWrapperType_init, /* tp_init */ sipWrapperType_alloc, /* tp_alloc */ 0, /* tp_new */ 0, /* tp_free */ 0, /* tp_is_gc */ 0, /* tp_bases */ 0, /* tp_mro */ 0, /* tp_cache */ 0, /* tp_subclasses */ 0, /* tp_weaklist */ 0, /* tp_del */ 0, /* tp_version_tag */ 0, /* tp_finalize */ 0, /* tp_vectorcall */ }; /* * The Python type that is the super-type for all C++ wrapper types that * support parent/child relationships. */ static int sipWrapper_clear(sipWrapper *self); static void sipWrapper_dealloc(sipWrapper *self); static int sipWrapper_traverse(sipWrapper *self, visitproc visit, void *arg); static sipWrapperType sipWrapper_Type = { #if !defined(STACKLESS) { #endif { PyVarObject_HEAD_INIT(&sipWrapperType_Type, 0) "sip.wrapper", /* tp_name */ sizeof (sipWrapper), /* tp_basicsize */ 0, /* tp_itemsize */ (destructor)sipWrapper_dealloc, /* tp_dealloc */ 0, /* tp_print */ 0, /* tp_getattr */ 0, /* tp_setattr */ 0, /* tp_as_async (Python v3.5), tp_compare (Python v2) */ 0, /* tp_repr */ 0, /* tp_as_number */ 0, /* tp_as_sequence */ 0, /* tp_as_mapping */ 0, /* tp_hash */ 0, /* tp_call */ 0, /* tp_str */ 0, /* tp_getattro */ 0, /* tp_setattro */ 0, /* tp_as_buffer */ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_GC, /* tp_flags */ 0, /* tp_doc */ (traverseproc)sipWrapper_traverse, /* tp_traverse */ (inquiry)sipWrapper_clear, /* tp_clear */ 0, /* tp_richcompare */ 0, /* tp_weaklistoffset */ 0, /* tp_iter */ 0, /* tp_iternext */ 0, /* tp_methods */ 0, /* tp_members */ 0, /* tp_getset */ 0, /* tp_base */ 0, /* tp_dict */ 0, /* tp_descr_get */ 0, /* tp_descr_set */ 0, /* tp_dictoffset */ 0, /* tp_init */ 0, /* tp_alloc */ 0, /* tp_new */ 0, /* tp_free */ 0, /* tp_is_gc */ 0, /* tp_bases */ 0, /* tp_mro */ 0, /* tp_cache */ 0, /* tp_subclasses */ 0, /* tp_weaklist */ 0, /* tp_del */ 0, /* tp_version_tag */ 0, /* tp_finalize */ 0, /* tp_vectorcall */ }, { 0, /* am_await */ 0, /* am_aiter */ 0, /* am_anext */ }, { 0, /* nb_add */ 0, /* nb_subtract */ 0, /* nb_multiply */ 0, /* nb_remainder */ 0, /* nb_divmod */ 0, /* nb_power */ 0, /* nb_negative */ 0, /* nb_positive */ 0, /* nb_absolute */ 0, /* nb_bool */ 0, /* nb_invert */ 0, /* nb_lshift */ 0, /* nb_rshift */ 0, /* nb_and */ 0, /* nb_xor */ 0, /* nb_or */ 0, /* nb_int */ 0, /* nb_reserved */ 0, /* nb_float */ 0, /* nb_inplace_add */ 0, /* nb_inplace_subtract */ 0, /* nb_inplace_multiply */ 0, /* nb_inplace_remainder */ 0, /* nb_inplace_power */ 0, /* nb_inplace_lshift */ 0, /* nb_inplace_rshift */ 0, /* nb_inplace_and */ 0, /* nb_inplace_xor */ 0, /* nb_inplace_or */ 0, /* nb_floor_divide */ 0, /* nb_true_divide */ 0, /* nb_inplace_floor_divide */ 0, /* nb_inplace_true_divide */ 0, /* nb_index */ 0, /* nb_matrix_multiply */ 0, /* nb_inplace_matrix_multiply */ }, { 0, /* mp_length */ 0, /* mp_subscript */ 0, /* mp_ass_subscript */ }, { 0, /* sq_length */ 0, /* sq_concat */ 0, /* sq_repeat */ 0, /* sq_item */ 0, /* was_sq_slice */ 0, /* sq_ass_item */ 0, /* was_sq_ass_slice */ 0, /* sq_contains */ 0, /* sq_inplace_concat */ 0, /* sq_inplace_repeat */ }, { 0, /* bf_getbuffer */ 0, /* bf_releasebuffer */ }, 0, /* ht_name */ 0, /* ht_slots */ 0, /* ht_qualname */ 0, /* ht_cached_keys */ #if PY_VERSION_HEX >= 0x03090000 0, /* ht_module */ #endif #if !defined(STACKLESS) }, #endif 0, /* wt_user_type */ 0, /* wt_dict_complete */ 0, /* wt_unused */ 0, /* wt_td */ 0, /* wt_iextend */ 0, /* wt_new_user_type_handler */ 0, /* wt_user_data */ }; static void sip_api_bad_catcher_result(PyObject *method); static void sip_api_bad_length_for_slice(Py_ssize_t seqlen, Py_ssize_t slicelen); static PyObject *sip_api_build_result(int *isErr, const char *fmt, ...); static PyObject *sip_api_call_method(int *isErr, PyObject *method, const char *fmt, ...); static void sip_api_call_procedure_method(sip_gilstate_t gil_state, sipVirtErrorHandlerFunc error_handler, sipSimpleWrapper *py_self, PyObject *method, const char *fmt, ...); static Py_ssize_t sip_api_convert_from_sequence_index(Py_ssize_t idx, Py_ssize_t len); static int sip_api_can_convert_to_type(PyObject *pyObj, const sipTypeDef *td, int flags); static void *sip_api_convert_to_type(PyObject *pyObj, const sipTypeDef *td, PyObject *transferObj, int flags, int *statep, int *iserrp); static int sip_api_can_convert_to_enum(PyObject *pyObj, const sipTypeDef *td); static int sip_api_convert_to_enum(PyObject *pyObj, const sipTypeDef *td); static void sip_api_release_type(void *cpp, const sipTypeDef *td, int state); static PyObject *sip_api_convert_from_new_type(void *cpp, const sipTypeDef *td, PyObject *transferObj); static PyObject *sip_api_convert_from_new_pytype(void *cpp, PyTypeObject *py_type, sipWrapper *owner, sipSimpleWrapper **selfp, const char *fmt, ...); static int sip_api_get_state(PyObject *transferObj); static PyObject *sip_api_get_pyobject(void *cppPtr, const sipTypeDef *td); static sipWrapperType *sip_api_map_int_to_class(int typeInt, const sipIntTypeClassMap *map, int maplen); static sipWrapperType *sip_api_map_string_to_class(const char *typeString, const sipStringTypeClassMap *map, int maplen); static int sip_api_parse_result_ex(sip_gilstate_t gil_state, sipVirtErrorHandlerFunc error_handler, sipSimpleWrapper *py_self, PyObject *method, PyObject *res, const char *fmt, ...); static int sip_api_parse_result(int *isErr, PyObject *method, PyObject *res, const char *fmt, ...); static void sip_api_call_error_handler(sipVirtErrorHandlerFunc error_handler, sipSimpleWrapper *py_self, sip_gilstate_t gil_state); static void sip_api_trace(unsigned mask,const char *fmt,...); static void sip_api_transfer_back(PyObject *self); static void sip_api_transfer_to(PyObject *self, PyObject *owner); static int sip_api_export_module(sipExportedModuleDef *client, unsigned api_major, unsigned api_minor, void *unused); static int sip_api_init_module(sipExportedModuleDef *client, PyObject *mod_dict); static int sip_api_parse_args(PyObject **parseErrp, PyObject *sipArgs, const char *fmt, ...); static int sip_api_parse_kwd_args(PyObject **parseErrp, PyObject *sipArgs, PyObject *sipKwdArgs, const char **kwdlist, PyObject **unused, const char *fmt, ...); static int sip_api_parse_pair(PyObject **parseErrp, PyObject *sipArg0, PyObject *sipArg1, const char *fmt, ...); static void sip_api_no_function(PyObject *parseErr, const char *func, const char *doc); static void sip_api_no_method(PyObject *parseErr, const char *scope, const char *method, const char *doc); static void sip_api_abstract_method(const char *classname, const char *method); static void sip_api_bad_class(const char *classname); static void *sip_api_get_complex_cpp_ptr(sipSimpleWrapper *sw); static PyObject *sip_api_is_py_method(sip_gilstate_t *gil, char *pymc, sipSimpleWrapper *sipSelf, const char *cname, const char *mname); static PyObject *sip_api_is_py_method_12_8(sip_gilstate_t *gil, char *pymc, sipSimpleWrapper **sipSelfp, const char *cname, const char *mname); static void sip_api_call_hook(const char *hookname); static void sip_api_raise_unknown_exception(void); static void sip_api_raise_type_exception(const sipTypeDef *td, void *ptr); static int sip_api_add_type_instance(PyObject *dict, const char *name, void *cppPtr, const sipTypeDef *td); static sipErrorState sip_api_bad_callable_arg(int arg_nr, PyObject *arg); static void sip_api_bad_operator_arg(PyObject *self, PyObject *arg, sipPySlotType st); static PyObject *sip_api_pyslot_extend(sipExportedModuleDef *mod, sipPySlotType st, const sipTypeDef *td, PyObject *arg0, PyObject *arg1); static void sip_api_add_delayed_dtor(sipSimpleWrapper *w); static int sip_api_export_symbol(const char *name, void *sym); static void *sip_api_import_symbol(const char *name); static const sipTypeDef *sip_api_find_type(const char *type); static sipWrapperType *sip_api_find_class(const char *type); static const sipMappedType *sip_api_find_mapped_type(const char *type); static PyTypeObject *sip_api_find_named_enum(const char *type); static char sip_api_bytes_as_char(PyObject *obj); static const char *sip_api_bytes_as_string(PyObject *obj); static char sip_api_string_as_ascii_char(PyObject *obj); static const char *sip_api_string_as_ascii_string(PyObject **obj); static char sip_api_string_as_latin1_char(PyObject *obj); static const char *sip_api_string_as_latin1_string(PyObject **obj); static char sip_api_string_as_utf8_char(PyObject *obj); static const char *sip_api_string_as_utf8_string(PyObject **obj); #if defined(HAVE_WCHAR_H) static wchar_t sip_api_unicode_as_wchar(PyObject *obj); static wchar_t *sip_api_unicode_as_wstring(PyObject *obj); #else static int sip_api_unicode_as_wchar(PyObject *obj); static int *sip_api_unicode_as_wstring(PyObject *obj); #endif static void sip_api_transfer_break(PyObject *self); static int sip_api_register_py_type(PyTypeObject *supertype); static PyObject *sip_api_convert_from_enum(int eval, const sipTypeDef *td); static const sipTypeDef *sip_api_type_from_py_type_object(PyTypeObject *py_type); static const sipTypeDef *sip_api_type_scope(const sipTypeDef *td); static const char *sip_api_resolve_typedef(const char *name); static int sip_api_register_attribute_getter(const sipTypeDef *td, sipAttrGetterFunc getter); static void sip_api_clear_any_slot_reference(sipSlot *slot); static int sip_api_visit_slot(sipSlot *slot, visitproc visit, void *arg); static void sip_api_keep_reference(PyObject *self, int key, PyObject *obj); static PyObject *sip_api_get_reference(PyObject *self, int key); static int sip_api_is_owned_by_python(sipSimpleWrapper *sw); static int sip_api_is_derived_class(sipSimpleWrapper *sw); static void sip_api_add_exception(sipErrorState es, PyObject **parseErrp); static void sip_api_set_destroy_on_exit(int value); static int sip_api_enable_autoconversion(const sipTypeDef *td, int enable); static int sip_api_init_mixin(PyObject *self, PyObject *args, PyObject *kwds, const sipClassTypeDef *ctd); static void *sip_api_get_mixin_address(sipSimpleWrapper *w, const sipTypeDef *td); static int sip_api_register_proxy_resolver(const sipTypeDef *td, sipProxyResolverFunc resolver); static PyInterpreterState *sip_api_get_interpreter(void); static sipNewUserTypeFunc sip_api_set_new_user_type_handler( const sipTypeDef *td, sipNewUserTypeFunc handler); static void sip_api_set_type_user_data(sipWrapperType *wt, void *data); static void *sip_api_get_type_user_data(const sipWrapperType *wt); static PyObject *sip_api_py_type_dict(const PyTypeObject *py_type); static PyObject *sip_api_py_type_dict_ref(PyTypeObject *py_type); static const char *sip_api_py_type_name(const PyTypeObject *py_type); static int sip_api_get_method(PyObject *obj, sipMethodDef *method); static PyObject *sip_api_from_method(const sipMethodDef *method); static int sip_api_get_c_function(PyObject *obj, sipCFunctionDef *c_function); static int sip_api_get_date(PyObject *obj, sipDateDef *date); static PyObject *sip_api_from_date(const sipDateDef *date); static int sip_api_get_datetime(PyObject *obj, sipDateDef *date, sipTimeDef *time); static PyObject *sip_api_from_datetime(const sipDateDef *date, const sipTimeDef *time); static int sip_api_get_time(PyObject *obj, sipTimeDef *time); static PyObject *sip_api_from_time(const sipTimeDef *time); static int sip_api_is_user_type(const sipWrapperType *wt); static struct _frame *sip_api_get_frame(int); static int sip_api_check_plugin_for_type(const sipTypeDef *td, const char *name); static PyObject *sip_api_unicode_new(Py_ssize_t len, unsigned maxchar, int *kind, void **data); static void sip_api_unicode_write(int kind, void *data, int index, unsigned value); static void *sip_api_unicode_data(PyObject *obj, int *char_size, Py_ssize_t *len); static int sip_api_get_buffer_info(PyObject *obj, sipBufferInfoDef *bi); static void sip_api_release_buffer_info(sipBufferInfoDef *bi); static PyObject *sip_api_get_user_object(const sipSimpleWrapper *sw); static void sip_api_set_user_object(sipSimpleWrapper *sw, PyObject *user); static int sip_api_enable_gc(int enable); static void sip_api_print_object(PyObject *o); static int sip_api_register_event_handler(sipEventType type, const sipTypeDef *td, void *handler); static void sip_api_instance_destroyed_ex(sipSimpleWrapper **sipSelfp); static void sip_api_visit_wrappers(sipWrapperVisitorFunc visitor, void *closure); static int sip_api_register_exit_notifier(PyMethodDef *md); static sipExceptionHandler sip_api_next_exception_handler(void **statep); /* * The data structure that represents the SIP API. */ static const sipAPIDef sip_api = { /* This must be first. */ sip_api_export_module, /* * The following are part of the public API. */ (PyTypeObject *)&sipSimpleWrapper_Type, (PyTypeObject *)&sipWrapper_Type, &sipWrapperType_Type, &sipVoidPtr_Type, sip_api_bad_catcher_result, sip_api_bad_length_for_slice, sip_api_build_result, sip_api_call_method, sip_api_call_procedure_method, sip_api_connect_rx, sip_api_convert_from_sequence_index, sip_api_can_convert_to_type, sip_api_convert_to_type, sip_api_force_convert_to_type, sip_api_can_convert_to_enum, sip_api_release_type, sip_api_convert_from_type, sip_api_convert_from_new_type, sip_api_convert_from_enum, sip_api_get_state, sip_api_disconnect_rx, sip_api_free, sip_api_get_pyobject, sip_api_malloc, sip_api_parse_result, sip_api_trace, sip_api_transfer_back, sip_api_transfer_to, sip_api_transfer_break, sip_api_long_as_unsigned_long, sip_api_convert_from_void_ptr, sip_api_convert_from_const_void_ptr, sip_api_convert_from_void_ptr_and_size, sip_api_convert_from_const_void_ptr_and_size, sip_api_convert_to_void_ptr, sip_api_export_symbol, sip_api_import_symbol, sip_api_find_type, sip_api_register_py_type, sip_api_type_from_py_type_object, sip_api_type_scope, sip_api_resolve_typedef, sip_api_register_attribute_getter, sip_api_is_api_enabled, sip_api_bad_callable_arg, sip_api_get_address, sip_api_set_destroy_on_exit, sip_api_enable_autoconversion, sip_api_get_mixin_address, sip_api_convert_from_new_pytype, sip_api_convert_to_typed_array, sip_api_convert_to_array, sip_api_register_proxy_resolver, sip_api_get_interpreter, sip_api_set_new_user_type_handler, sip_api_set_type_user_data, sip_api_get_type_user_data, sip_api_py_type_dict, sip_api_py_type_name, sip_api_get_method, sip_api_from_method, sip_api_get_c_function, sip_api_get_date, sip_api_from_date, sip_api_get_datetime, sip_api_from_datetime, sip_api_get_time, sip_api_from_time, sip_api_is_user_type, sip_api_get_frame, sip_api_check_plugin_for_type, sip_api_unicode_new, sip_api_unicode_write, sip_api_unicode_data, sip_api_get_buffer_info, sip_api_release_buffer_info, sip_api_get_user_object, sip_api_set_user_object, /* * The following are not part of the public API. */ sip_api_init_module, sip_api_parse_args, sip_api_parse_pair, /* * The following are part of the public API. */ sip_api_instance_destroyed, /* * The following are not part of the public API. */ sip_api_no_function, sip_api_no_method, sip_api_abstract_method, sip_api_bad_class, sip_api_get_cpp_ptr, sip_api_get_complex_cpp_ptr, sip_api_is_py_method, sip_api_call_hook, sip_api_end_thread, sip_api_raise_unknown_exception, sip_api_raise_type_exception, sip_api_add_type_instance, sip_api_bad_operator_arg, sip_api_pyslot_extend, sip_api_add_delayed_dtor, sip_api_bytes_as_char, sip_api_bytes_as_string, sip_api_string_as_ascii_char, sip_api_string_as_ascii_string, sip_api_string_as_latin1_char, sip_api_string_as_latin1_string, sip_api_string_as_utf8_char, sip_api_string_as_utf8_string, sip_api_unicode_as_wchar, sip_api_unicode_as_wstring, sip_api_deprecated, sip_api_keep_reference, sip_api_parse_kwd_args, sip_api_add_exception, sip_api_parse_result_ex, sip_api_call_error_handler, sip_api_init_mixin, sip_api_get_reference, /* * The following are part of the public API. */ sip_api_is_owned_by_python, /* * The following are not part of the public API. */ sip_api_is_derived_class, /* * The following may be used by Qt support code but by no other handwritten * code. */ sip_api_free_sipslot, sip_api_same_slot, sip_api_convert_rx, sip_api_invoke_slot, sip_api_invoke_slot_ex, sip_api_save_slot, sip_api_clear_any_slot_reference, sip_api_visit_slot, /* * The following are deprecated parts of the public API. */ sip_api_find_named_enum, sip_api_find_mapped_type, sip_api_find_class, sip_api_map_int_to_class, sip_api_map_string_to_class, /* * The following are part of the public API. */ sip_api_enable_gc, sip_api_print_object, sip_api_register_event_handler, sip_api_convert_to_enum, sip_api_convert_to_bool, sip_api_enable_overflow_checking, sip_api_long_as_char, sip_api_long_as_signed_char, sip_api_long_as_unsigned_char, sip_api_long_as_short, sip_api_long_as_unsigned_short, sip_api_long_as_int, sip_api_long_as_unsigned_int, sip_api_long_as_long, #if defined(HAVE_LONG_LONG) sip_api_long_as_long_long, sip_api_long_as_unsigned_long_long, #else 0, 0, #endif /* * The following are not part of the public API. */ sip_api_instance_destroyed_ex, /* * The following are part of the public API. */ sip_api_convert_from_slice_object, sip_api_long_as_size_t, sip_api_visit_wrappers, sip_api_register_exit_notifier, /* * The following are not part of the public API. */ sip_api_is_py_method_12_8, sip_api_next_exception_handler, /* * The following are part of the public API. */ sip_api_py_type_dict_ref, }; #define AUTO_DOCSTRING '\1' /* Marks an auto class docstring. */ /* * These are the format flags supported by argument parsers. */ #define FMT_AP_DEREF 0x01 /* The pointer will be dereferenced. */ #define FMT_AP_TRANSFER 0x02 /* Implement /Transfer/. */ #define FMT_AP_TRANSFER_BACK 0x04 /* Implement /TransferBack/. */ #define FMT_AP_NO_CONVERTORS 0x08 /* Suppress any convertors. */ #define FMT_AP_TRANSFER_THIS 0x10 /* Support for /TransferThis/. */ /* * These are the format flags supported by result parsers. Deprecated values * have a _DEPR suffix. */ #define FMT_RP_DEREF 0x01 /* The pointer will be dereferenced. */ #define FMT_RP_FACTORY 0x02 /* /Factory/ or /TransferBack/. */ #define FMT_RP_MAKE_COPY 0x04 /* Return a copy of the value. */ #define FMT_RP_NO_STATE_DEPR 0x04 /* Don't return the C/C++ state. */ /* * The different reasons for failing to parse an overload. These include * internal (i.e. non-user) errors. */ typedef enum { Ok, Unbound, TooFew, TooMany, UnknownKeyword, Duplicate, WrongType, Raised, KeywordNotString, Exception, Overflow } sipParseFailureReason; /* * The description of a failure to parse an overload because of a user error. */ typedef struct _sipParseFailure { sipParseFailureReason reason; /* The reason for the failure. */ const char *detail_str; /* The detail if a string. */ PyObject *detail_obj; /* The detail if a Python object. */ int arg_nr; /* The wrong positional argument. */ const char *arg_name; /* The wrong keyword argument. */ int overflow_arg_nr; /* The overflowed positional argument. */ const char *overflow_arg_name; /* The overflowed keyword argument. */ } sipParseFailure; /* * An entry in a linked list of name/symbol pairs. */ typedef struct _sipSymbol { const char *name; /* The name. */ void *symbol; /* The symbol. */ struct _sipSymbol *next; /* The next in the list. */ } sipSymbol; /* * An entry in a linked list of Python objects. */ typedef struct _sipPyObject { PyObject *object; /* The Python object. */ struct _sipPyObject *next; /* The next in the list. */ } sipPyObject; /* * An entry in the linked list of attribute getters. */ typedef struct _sipAttrGetter { PyTypeObject *type; /* The Python type being handled. */ sipAttrGetterFunc getter; /* The getter. */ struct _sipAttrGetter *next; /* The next in the list. */ } sipAttrGetter; /* * An entry in the linked list of proxy resolvers. */ typedef struct _sipProxyResolver { const sipTypeDef *td; /* The type the resolver handles. */ sipProxyResolverFunc resolver; /* The resolver. */ struct _sipProxyResolver *next; /* The next in the list. */ } sipProxyResolver; /* * An entry in the linked list of event handlers. */ typedef struct _sipEventHandler { const sipClassTypeDef *ctd; /* The type the handler handles. */ void *handler; /* The handler. */ struct _sipEventHandler *next; /* The next in the list. */ } sipEventHandler; /***************************************************************************** * The structures to support a Python type to hold a named enum. *****************************************************************************/ static PyObject *sipEnumType_alloc(PyTypeObject *self, Py_ssize_t nitems); static PyObject *sipEnumType_getattro(PyObject *self, PyObject *name); /* * The type data structure. We inherit everything from the standard Python * metatype and the size of the type object created is increased to accomodate * the extra information we associate with a named enum type. */ static PyTypeObject sipEnumType_Type = { PyVarObject_HEAD_INIT(NULL, 0) "sip.enumtype", /* tp_name */ sizeof (sipEnumTypeObject), /* tp_basicsize */ 0, /* tp_itemsize */ 0, /* tp_dealloc */ 0, /* tp_print */ 0, /* tp_getattr */ 0, /* tp_setattr */ 0, /* tp_as_async (Python v3.5), tp_compare (Python v2) */ 0, /* tp_repr */ 0, /* tp_as_number */ 0, /* tp_as_sequence */ 0, /* tp_as_mapping */ 0, /* tp_hash */ 0, /* tp_call */ 0, /* tp_str */ sipEnumType_getattro, /* tp_getattro */ 0, /* tp_setattro */ 0, /* tp_as_buffer */ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /* tp_flags */ 0, /* tp_doc */ 0, /* tp_traverse */ 0, /* tp_clear */ 0, /* tp_richcompare */ 0, /* tp_weaklistoffset */ 0, /* tp_iter */ 0, /* tp_iternext */ 0, /* tp_methods */ 0, /* tp_members */ 0, /* tp_getset */ 0, /* tp_base */ 0, /* tp_dict */ 0, /* tp_descr_get */ 0, /* tp_descr_set */ 0, /* tp_dictoffset */ 0, /* tp_init */ sipEnumType_alloc, /* tp_alloc */ 0, /* tp_new */ 0, /* tp_free */ 0, /* tp_is_gc */ 0, /* tp_bases */ 0, /* tp_mro */ 0, /* tp_cache */ 0, /* tp_subclasses */ 0, /* tp_weaklist */ 0, /* tp_del */ 0, /* tp_version_tag */ 0, /* tp_finalize */ 0, /* tp_vectorcall */ }; sipQtAPI *sipQtSupport = NULL; sipTypeDef *sipQObjectType; static int got_kw_handler = FALSE; static int (*kw_handler)(PyObject *, void *, PyObject *); /* * Various strings as Python objects created as and when needed. */ static PyObject *licenseName = NULL; static PyObject *licenseeName = NULL; static PyObject *typeName = NULL; static PyObject *timestampName = NULL; static PyObject *signatureName = NULL; static sipObjectMap cppPyMap; /* The C/C++ to Python map. */ static sipExportedModuleDef *moduleList = NULL; /* List of registered modules. */ static unsigned traceMask = 0; /* The current trace mask. */ static sipTypeDef *currentType = NULL; /* The type being created. */ static PyObject **unused_backdoor = NULL; /* For passing dict of unused arguments. */ static PyObject *init_name = NULL; /* '__init__'. */ static PyObject *empty_tuple; /* The empty tuple. */ static PyObject *type_unpickler; /* The type unpickler function. */ static PyObject *enum_unpickler; /* The enum unpickler function. */ static sipSymbol *sipSymbolList = NULL; /* The list of published symbols. */ static sipAttrGetter *sipAttrGetters = NULL; /* The list of attribute getters. */ static sipProxyResolver *proxyResolvers = NULL; /* The list of proxy resolvers. */ static sipPyObject *sipRegisteredPyTypes = NULL; /* Registered Python types. */ static sipPyObject *sipDisabledAutoconversions = NULL; /* Python types whose auto-conversion is disabled. */ static PyInterpreterState *sipInterpreter = NULL; /* The interpreter. */ static int destroy_on_exit = TRUE; /* Destroy owned objects on exit. */ static sipEventHandler *event_handlers[sipEventNrEvents]; /* The event handler lists. */ static void addClassSlots(sipWrapperType *wt, const sipClassTypeDef *ctd); static void addTypeSlots(PyHeapTypeObject *heap_to, sipPySlotDef *slots); static void *findSlot(PyObject *self, sipPySlotType st); static void *findSlotInClass(const sipClassTypeDef *psd, sipPySlotType st); static void *findSlotInSlotList(sipPySlotDef *psd, sipPySlotType st); static int objobjargprocSlot(PyObject *self, PyObject *arg1, PyObject *arg2, sipPySlotType st); static int ssizeobjargprocSlot(PyObject *self, Py_ssize_t arg1, PyObject *arg2, sipPySlotType st); static PyObject *buildObject(PyObject *tup, const char *fmt, va_list va); static int parseKwdArgs(PyObject **parseErrp, PyObject *sipArgs, PyObject *sipKwdArgs, const char **kwdlist, PyObject **unused, const char *fmt, va_list va_orig); static int parsePass1(PyObject **parseErrp, sipSimpleWrapper **selfp, int *selfargp, PyObject *sipArgs, PyObject *sipKwdArgs, const char **kwdlist, PyObject **unused, const char *fmt, va_list va); static int parsePass2(sipSimpleWrapper *self, int selfarg, PyObject *sipArgs, PyObject *sipKwdArgs, const char **kwdlist, const char *fmt, va_list va); static int parseResult(PyObject *method, PyObject *res, sipSimpleWrapper *py_self, const char *fmt, va_list va); static PyObject *signature_FromDocstring(const char *doc, Py_ssize_t line); static PyObject *detail_FromFailure(PyObject *failure_obj); static int isQObject(PyObject *obj); static int canConvertFromSequence(PyObject *seq, const sipTypeDef *td); static int convertFromSequence(PyObject *seq, const sipTypeDef *td, void **array, Py_ssize_t *nr_elem); static PyObject *convertToSequence(void *array, Py_ssize_t nr_elem, const sipTypeDef *td); static int getSelfFromArgs(sipTypeDef *td, PyObject *args, int argnr, sipSimpleWrapper **selfp); static int compareTypedefName(const void *key, const void *el); static int checkPointer(void *ptr, sipSimpleWrapper *sw); static void *cast_cpp_ptr(void *ptr, PyTypeObject *src_type, const sipTypeDef *dst_type); static void finalise(void); static PyObject *getDefaultBase(void); static PyObject *getDefaultSimpleBase(void); static PyObject *getScopeDict(sipTypeDef *td, PyObject *mod_dict, sipExportedModuleDef *client); static PyObject *createContainerType(sipContainerDef *cod, sipTypeDef *td, PyObject *bases, PyObject *metatype, PyObject *mod_dict, PyObject *type_dict, sipExportedModuleDef *client); static int createClassType(sipExportedModuleDef *client, sipClassTypeDef *ctd, PyObject *mod_dict); static int createMappedType(sipExportedModuleDef *client, sipMappedTypeDef *mtd, PyObject *mod_dict); static sipExportedModuleDef *getModule(PyObject *mname_obj); static PyObject *pickle_type(PyObject *obj, PyObject *args); static PyObject *unpickle_type(PyObject *obj, PyObject *args); static PyObject *pickle_enum(PyObject *obj, PyObject *args); static PyObject *unpickle_enum(PyObject *obj, PyObject *args); static int setReduce(PyTypeObject *type, PyMethodDef *pickler); static int createEnum(sipExportedModuleDef *client, sipEnumTypeDef *etd, int enum_nr, PyObject *mod_dict); static PyObject *createUnscopedEnum(sipExportedModuleDef *client, sipEnumTypeDef *etd, PyObject *name); static PyObject *createScopedEnum(sipExportedModuleDef *client, sipEnumTypeDef *etd, int enum_nr, PyObject *name); static PyObject *createTypeDict(sipExportedModuleDef *em); static sipTypeDef *getGeneratedType(const sipEncodedTypeDef *enc, sipExportedModuleDef *em); static const sipTypeDef *convertSubClass(const sipTypeDef *td, void **cppPtr); static int convertPass(const sipTypeDef **tdp, void **cppPtr); static void *getPtrTypeDef(sipSimpleWrapper *self, const sipClassTypeDef **ctd); static int addInstances(PyObject *dict, sipInstancesDef *id); static int addVoidPtrInstances(PyObject *dict, sipVoidPtrInstanceDef *vi); static int addCharInstances(PyObject *dict, sipCharInstanceDef *ci); static int addStringInstances(PyObject *dict, sipStringInstanceDef *si); static int addIntInstances(PyObject *dict, sipIntInstanceDef *ii); static int addLongInstances(PyObject *dict, sipLongInstanceDef *li); static int addUnsignedLongInstances(PyObject *dict, sipUnsignedLongInstanceDef *uli); static int addLongLongInstances(PyObject *dict, sipLongLongInstanceDef *lli); static int addUnsignedLongLongInstances(PyObject *dict, sipUnsignedLongLongInstanceDef *ulli); static int addDoubleInstances(PyObject *dict, sipDoubleInstanceDef *di); static int addTypeInstances(PyObject *dict, sipTypeInstanceDef *ti); static int addSingleTypeInstance(PyObject *dict, const char *name, void *cppPtr, const sipTypeDef *td, int initflags); static int addLicense(PyObject *dict, sipLicenseDef *lc); static PyObject *assign(PyObject *self, PyObject *args); static PyObject *cast(PyObject *self, PyObject *args); static PyObject *callDtor(PyObject *self, PyObject *args); static PyObject *dumpWrapper(PyObject *self, PyObject *arg); static PyObject *enableAutoconversion(PyObject *self, PyObject *args); static PyObject *isDeleted(PyObject *self, PyObject *args); static PyObject *isPyCreated(PyObject *self, PyObject *args); static PyObject *isPyOwned(PyObject *self, PyObject *args); static PyObject *setDeleted(PyObject *self, PyObject *args); static PyObject *setTraceMask(PyObject *self, PyObject *args); static PyObject *wrapInstance(PyObject *self, PyObject *args); static PyObject *unwrapInstance(PyObject *self, PyObject *args); static PyObject *transferBack(PyObject *self, PyObject *args); static PyObject *transferTo(PyObject *self, PyObject *args); static PyObject *setDestroyOnExit(PyObject *self, PyObject *args); static void clear_wrapper(sipSimpleWrapper *sw); static void print_object(const char *label, PyObject *obj); static void addToParent(sipWrapper *self, sipWrapper *owner); static void removeFromParent(sipWrapper *self); static void detachChildren(sipWrapper *self); static void release(void *addr, const sipTypeDef *td, int state); static void callPyDtor(sipSimpleWrapper *self); static int parseBytes_AsCharArray(PyObject *obj, const char **ap, Py_ssize_t *aszp); static int parseBytes_AsChar(PyObject *obj, char *ap); static int parseBytes_AsString(PyObject *obj, const char **ap); static int parseString_AsASCIIChar(PyObject *obj, char *ap); static PyObject *parseString_AsASCIIString(PyObject *obj, const char **ap); static int parseString_AsLatin1Char(PyObject *obj, char *ap); static PyObject *parseString_AsLatin1String(PyObject *obj, const char **ap); static int parseString_AsUTF8Char(PyObject *obj, char *ap); static PyObject *parseString_AsUTF8String(PyObject *obj, const char **ap); static int parseString_AsEncodedChar(PyObject *bytes, PyObject *obj, char *ap); static PyObject *parseString_AsEncodedString(PyObject *bytes, PyObject *obj, const char **ap); #if defined(HAVE_WCHAR_H) static int parseWCharArray(PyObject *obj, wchar_t **ap, Py_ssize_t *aszp); static int convertToWCharArray(PyObject *obj, wchar_t **ap, Py_ssize_t *aszp); static int parseWChar(PyObject *obj, wchar_t *ap); static int convertToWChar(PyObject *obj, wchar_t *ap); static int parseWCharString(PyObject *obj, wchar_t **ap); static int convertToWCharString(PyObject *obj, wchar_t **ap); #else static void raiseNoWChar(); #endif static void *getComplexCppPtr(sipSimpleWrapper *w, const sipTypeDef *td); static PyObject *findPyType(const char *name); static int addPyObjectToList(sipPyObject **head, PyObject *object); static PyObject *getDictFromObject(PyObject *obj); static void forgetObject(sipSimpleWrapper *sw); static int add_lazy_container_attrs(sipTypeDef *td, sipContainerDef *cod, PyObject *dict); static int add_lazy_attrs(sipTypeDef *td); static int add_all_lazy_attrs(sipTypeDef *td); static int objectify(const char *s, PyObject **objp); static void add_failure(PyObject **parseErrp, sipParseFailure *failure); static PyObject *bad_type_str(int arg_nr, PyObject *arg); static void *explicit_access_func(sipSimpleWrapper *sw, AccessFuncOp op); static void *indirect_access_func(sipSimpleWrapper *sw, AccessFuncOp op); static void clear_access_func(sipSimpleWrapper *sw); static int check_encoded_string(PyObject *obj); static int isNonlazyMethod(PyMethodDef *pmd); static int addMethod(PyObject *dict, PyMethodDef *pmd); static PyObject *create_property(sipVariableDef *vd); static PyObject *create_function(PyMethodDef *ml); static PyObject *sip_exit(PyObject *self, PyObject *args); static sipConvertFromFunc get_from_convertor(const sipTypeDef *td); static sipPyObject **autoconversion_disabled(const sipTypeDef *td); static void fix_slots(PyTypeObject *py_type, sipPySlotDef *psd); static sipFinalFunc find_finalisation(sipClassTypeDef *ctd); static sipNewUserTypeFunc find_new_user_type_handler(sipWrapperType *wt); static PyObject *next_in_mro(PyObject *self, PyObject *after); static int super_init(PyObject *self, PyObject *args, PyObject *kwds, PyObject *type); static sipSimpleWrapper *deref_mixin(sipSimpleWrapper *w); static PyObject *wrap_simple_instance(void *cpp, const sipTypeDef *td, sipWrapper *owner, int flags); static void *resolve_proxy(const sipTypeDef *td, void *proxy); static PyObject *call_method(PyObject *method, const char *fmt, va_list va); static int importTypes(sipExportedModuleDef *client, sipImportedModuleDef *im, sipExportedModuleDef *em); static int importErrorHandlers(sipExportedModuleDef *client, sipImportedModuleDef *im, sipExportedModuleDef *em); static int importExceptions(sipExportedModuleDef *client, sipImportedModuleDef *im, sipExportedModuleDef *em); static int is_subtype(const sipClassTypeDef *ctd, const sipClassTypeDef *base_ctd); static PyObject *import_module_attr(const char *module, const char *attr); static const sipContainerDef *get_container(const sipTypeDef *td); static PyObject *get_qualname(const sipTypeDef *td, PyObject *name); static int convert_to_enum(PyObject *obj, const sipTypeDef *td, int allow_int); static void handle_failed_int_conversion(sipParseFailure *pf, PyObject *arg); static void enum_expected(PyObject *obj, const sipTypeDef *td); static int long_as_nonoverflow_int(PyObject *val_obj); static int dict_set_and_discard(PyObject *dict, const char *name, PyObject *obj); /* * Initialise the module as a library. */ const sipAPIDef *sip_init_library(PyObject *mod_dict) { static PyMethodDef methods[] = { /* This must be first. */ {"_unpickle_enum", unpickle_enum, METH_VARARGS, NULL}, /* This must be second. */ {"_unpickle_type", unpickle_type, METH_VARARGS, NULL}, {"assign", assign, METH_VARARGS, NULL}, {"cast", cast, METH_VARARGS, NULL}, {"delete", callDtor, METH_VARARGS, NULL}, {"dump", dumpWrapper, METH_O, NULL}, {"enableautoconversion", enableAutoconversion, METH_VARARGS, NULL}, {"enableoverflowchecking", sipEnableOverflowChecking, METH_VARARGS, NULL}, {"getapi", sipGetAPI, METH_VARARGS, NULL}, {"isdeleted", isDeleted, METH_VARARGS, NULL}, {"ispycreated", isPyCreated, METH_VARARGS, NULL}, {"ispyowned", isPyOwned, METH_VARARGS, NULL}, {"setapi", sipSetAPI, METH_VARARGS, NULL}, {"setdeleted", setDeleted, METH_VARARGS, NULL}, {"setdestroyonexit", setDestroyOnExit, METH_VARARGS, NULL}, {"settracemask", setTraceMask, METH_VARARGS, NULL}, {"transferback", transferBack, METH_VARARGS, NULL}, {"transferto", transferTo, METH_VARARGS, NULL}, {"wrapinstance", wrapInstance, METH_VARARGS, NULL}, {"unwrapinstance", unwrapInstance, METH_VARARGS, NULL}, {NULL, NULL, 0, NULL} }; static PyMethodDef sip_exit_md = { "_sip_exit", sip_exit, METH_NOARGS, NULL }; PyObject *obj; PyMethodDef *md; /* Add the SIP version number. */ obj = PyLong_FromLong(SIP_VERSION); if (dict_set_and_discard(mod_dict, "SIP_VERSION", obj) < 0) return NULL; obj = PyUnicode_FromString(SIP_VERSION_STR); if (dict_set_and_discard(mod_dict, "SIP_VERSION_STR", obj) < 0) return NULL; /* Add the methods. */ for (md = methods; md->ml_name != NULL; ++md) { PyObject *meth = PyCFunction_New(md, NULL); if (dict_set_and_discard(mod_dict, md->ml_name, meth) < 0) return NULL; if (md == &methods[0]) { Py_INCREF(meth); enum_unpickler = meth; } else if (md == &methods[1]) { Py_INCREF(meth); type_unpickler = meth; } } /* Initialise the types. */ sipWrapperType_Type.tp_base = &PyType_Type; if (PyType_Ready(&sipWrapperType_Type) < 0) return NULL; if (PyType_Ready((PyTypeObject *)&sipSimpleWrapper_Type) < 0) return NULL; if (sip_api_register_py_type((PyTypeObject *)&sipSimpleWrapper_Type) < 0) return NULL; #if defined(STACKLESS) sipWrapper_Type.super.tp_base = (PyTypeObject *)&sipSimpleWrapper_Type; #else sipWrapper_Type.super.ht_type.tp_base = (PyTypeObject *)&sipSimpleWrapper_Type; #endif if (PyType_Ready((PyTypeObject *)&sipWrapper_Type) < 0) return NULL; if (PyType_Ready(&sipMethodDescr_Type) < 0) return NULL; if (PyType_Ready(&sipVariableDescr_Type) < 0) return NULL; sipEnumType_Type.tp_base = &PyType_Type; if (PyType_Ready(&sipEnumType_Type) < 0) return NULL; if (PyType_Ready(&sipVoidPtr_Type) < 0) return NULL; if (PyType_Ready(&sipArray_Type) < 0) return NULL; /* Add the public types. */ if (PyDict_SetItemString(mod_dict, "wrappertype", (PyObject *)&sipWrapperType_Type) < 0) return NULL; if (PyDict_SetItemString(mod_dict, "simplewrapper", (PyObject *)&sipSimpleWrapper_Type) < 0) return NULL; if (PyDict_SetItemString(mod_dict, "wrapper", (PyObject *)&sipWrapper_Type) < 0) return NULL; if (PyDict_SetItemString(mod_dict, "voidptr", (PyObject *)&sipVoidPtr_Type) < 0) return NULL; if (PyDict_SetItemString(mod_dict, "array", (PyObject *)&sipArray_Type) < 0) return NULL; /* These will always be needed. */ if (objectify("__init__", &init_name) < 0) return NULL; if ((empty_tuple = PyTuple_New(0)) == NULL) return NULL; /* Initialise the object map. */ sipOMInit(&cppPyMap); /* Make sure we are notified at the end of the exit process. */ if (Py_AtExit(finalise) < 0) return NULL; /* Make sure we are notified at the start of the exit process. */ if (sip_api_register_exit_notifier(&sip_exit_md) < 0) return NULL; /* * Get the current interpreter. This will be shared between all threads. */ sipInterpreter = PyThreadState_Get()->interp; return &sip_api; } /* * Set a dictionary item and discard the reference to the item even if there * was an error. */ static int dict_set_and_discard(PyObject *dict, const char *name, PyObject *obj) { int rc; if (obj == NULL) return -1; rc = PyDict_SetItemString(dict, name, obj); Py_DECREF(obj); return rc; } #if _SIP_MODULE_SHARED /* * The Python module initialisation function. */ #if defined(SIP_STATIC_MODULE) PyObject *_SIP_MODULE_ENTRY(void) #else PyMODINIT_FUNC _SIP_MODULE_ENTRY(void) #endif { static PyModuleDef module_def = { PyModuleDef_HEAD_INIT, _SIP_MODULE_FQ_NAME, /* m_name */ NULL, /* m_doc */ -1, /* m_size */ NULL, /* m_methods */ NULL, /* m_reload */ NULL, /* m_traverse */ NULL, /* m_clear */ NULL, /* m_free */ }; const sipAPIDef *api; PyObject *mod, *mod_dict, *api_obj; /* Create the module. */ if ((mod = PyModule_Create(&module_def)) == NULL) return NULL; mod_dict = PyModule_GetDict(mod); /* Initialise the module dictionary and static variables. */ if ((api = sip_init_library(mod_dict)) == NULL) return NULL; /* Publish the SIP API. */ api_obj = PyCapsule_New((void *)api, _SIP_MODULE_FQ_NAME "._C_API", NULL); if (dict_set_and_discard(mod_dict, "_C_API", api_obj) < 0) { Py_DECREF(mod); return NULL; } #if _SIP_MODULE_LEGACY { /* * Also install the package-specific module at the top level for * backwards compatibility. */ PyObject *modules = PySys_GetObject("modules"); if (modules != NULL) PyDict_SetItemString(modules, "sip", mod); } #endif return mod; } #endif /* * Return the current interpreter, if there is one. */ static PyInterpreterState *sip_api_get_interpreter(void) { return sipInterpreter; } /* * Display a printf() style message to stderr according to the current trace * mask. */ static void sip_api_trace(unsigned mask, const char *fmt, ...) { va_list ap; va_start(ap,fmt); if (mask & traceMask) vfprintf(stderr, fmt, ap); va_end(ap); } /* * Set the trace mask. */ static PyObject *setTraceMask(PyObject *self, PyObject *args) { unsigned new_mask; (void)self; if (PyArg_ParseTuple(args, "I:settracemask", &new_mask)) { traceMask = new_mask; Py_INCREF(Py_None); return Py_None; } return NULL; } /* * Dump various bits of potentially useful information to stdout. Note that we * use the same calling convention as sys.getrefcount() so that it has the * same caveat regarding the reference count. */ static PyObject *dumpWrapper(PyObject *self, PyObject *arg) { sipSimpleWrapper *sw; (void)self; if (!PyObject_TypeCheck(arg, (PyTypeObject *)&sipSimpleWrapper_Type)) { PyErr_Format(PyExc_TypeError, "dump() argument 1 must be sip.simplewrapper, not %s", Py_TYPE(arg)->tp_name); return NULL; } sw = (sipSimpleWrapper *)arg; print_object(NULL, (PyObject *)sw); printf(" Reference count: %" PY_FORMAT_SIZE_T "d\n", Py_REFCNT(sw)); printf(" Address of wrapped object: %p\n", sip_api_get_address(sw)); printf(" Created by: %s\n", (sipIsDerived(sw) ? "Python" : "C/C++")); printf(" To be destroyed by: %s\n", (sipIsPyOwned(sw) ? "Python" : "C/C++")); if (PyObject_TypeCheck((PyObject *)sw, (PyTypeObject *)&sipWrapper_Type)) { sipWrapper *w = (sipWrapper *)sw; print_object("Parent wrapper", (PyObject *)w->parent); print_object("Next sibling wrapper", (PyObject *)w->sibling_next); print_object("Previous sibling wrapper", (PyObject *)w->sibling_prev); print_object("First child wrapper", (PyObject *)w->first_child); } Py_INCREF(Py_None); return Py_None; } /* * Write a reference to a wrapper to stdout. */ static void print_object(const char *label, PyObject *obj) { if (label != NULL) printf(" %s: ", label); if (obj != NULL) PyObject_Print(obj, stdout, 0); else printf("NULL"); printf("\n"); } /* * Transfer the ownership of an instance to C/C++. */ static PyObject *transferTo(PyObject *self, PyObject *args) { PyObject *w, *owner; (void)self; if (PyArg_ParseTuple(args, "O!O:transferto", &sipWrapper_Type, &w, &owner)) { if (owner == Py_None) { /* * Note that the Python API is different to the C API when the * owner is None. */ owner = NULL; } else if (!PyObject_TypeCheck(owner, (PyTypeObject *)&sipWrapper_Type)) { PyErr_Format(PyExc_TypeError, "transferto() argument 2 must be sip.wrapper, not %s", Py_TYPE(owner)->tp_name); return NULL; } sip_api_transfer_to(w, owner); Py_INCREF(Py_None); return Py_None; } return NULL; } /* * Transfer the ownership of an instance to Python. */ static PyObject *transferBack(PyObject *self, PyObject *args) { PyObject *w; (void)self; if (PyArg_ParseTuple(args, "O!:transferback", &sipWrapper_Type, &w)) { sip_api_transfer_back(w); Py_INCREF(Py_None); return Py_None; } return NULL; } /* * Invoke the assignment operator for a C++ instance. */ static PyObject *assign(PyObject *self, PyObject *args) { sipSimpleWrapper *dst, *src; PyTypeObject *dst_type, *src_type; const sipTypeDef *td, *super_td; sipAssignFunc assign_helper; void *dst_addr, *src_addr; (void)self; if (!PyArg_ParseTuple(args, "O!O!:assign", &sipSimpleWrapper_Type, &dst, &sipSimpleWrapper_Type, &src)) return NULL; /* Get the assignment helper. */ dst_type = Py_TYPE(dst); td = ((sipWrapperType *)dst_type)->wt_td; if (sipTypeIsMapped(td)) assign_helper = ((const sipMappedTypeDef *)td)->mtd_assign; else assign_helper = ((const sipClassTypeDef *)td)->ctd_assign; if (assign_helper == NULL) { PyErr_SetString(PyExc_TypeError, "argument 1 of assign() does not support assignment"); return NULL; } /* Check the types are compatible. */ src_type = Py_TYPE(src); if (src_type == dst_type) { super_td = NULL; } else if (PyType_IsSubtype(src_type, dst_type)) { super_td = td; } else { PyErr_SetString(PyExc_TypeError, "type of argument 1 of assign() must be a super-type of type of argument 2"); return NULL; } /* Get the addresses. */ if ((dst_addr = sip_api_get_cpp_ptr(dst, NULL)) == NULL) return NULL; if ((src_addr = sip_api_get_cpp_ptr(src, super_td)) == NULL) return NULL; /* Do the assignment. */ assign_helper(dst_addr, 0, src_addr); Py_INCREF(Py_None); return Py_None; } /* * Cast an instance to one of it's sub or super-classes by returning a new * Python object with the superclass type wrapping the same C++ instance. */ static PyObject *cast(PyObject *self, PyObject *args) { sipSimpleWrapper *sw; sipWrapperType *wt; const sipTypeDef *td; void *addr; PyTypeObject *ft, *tt; (void)self; if (!PyArg_ParseTuple(args, "O!O!:cast", &sipSimpleWrapper_Type, &sw, &sipWrapperType_Type, &wt)) return NULL; ft = Py_TYPE(sw); tt = (PyTypeObject *)wt; if (ft == tt || PyType_IsSubtype(tt, ft)) td = NULL; else if (PyType_IsSubtype(ft, tt)) td = wt->wt_td; else { PyErr_SetString(PyExc_TypeError, "argument 1 of cast() must be an instance of a sub or super-type of argument 2"); return NULL; } if ((addr = sip_api_get_cpp_ptr(sw, td)) == NULL) return NULL; /* * We don't put this new object into the map so that the original object is * always found. It would also totally confuse the map logic. */ return wrap_simple_instance(addr, wt->wt_td, NULL, (sw->sw_flags | SIP_NOT_IN_MAP) & ~SIP_PY_OWNED); } /* * Call an instance's dtor. */ static PyObject *callDtor(PyObject *self, PyObject *args) { sipSimpleWrapper *sw; void *addr; const sipClassTypeDef *ctd; (void)self; if (!PyArg_ParseTuple(args, "O!:delete", &sipSimpleWrapper_Type, &sw)) return NULL; addr = getPtrTypeDef(sw, &ctd); if (checkPointer(addr, sw) < 0) return NULL; clear_wrapper(sw); release(addr, (const sipTypeDef *)ctd, sw->sw_flags); Py_INCREF(Py_None); return Py_None; } /* * Check if an instance still exists without raising an exception. */ static PyObject *isDeleted(PyObject *self, PyObject *args) { sipSimpleWrapper *sw; PyObject *res; (void)self; if (!PyArg_ParseTuple(args, "O!:isdeleted", &sipSimpleWrapper_Type, &sw)) return NULL; res = (sip_api_get_address(sw) == NULL ? Py_True : Py_False); Py_INCREF(res); return res; } /* * Check if an instance was created by Python. */ static PyObject *isPyCreated(PyObject *self, PyObject *args) { sipSimpleWrapper *sw; PyObject *res; (void)self; if (!PyArg_ParseTuple(args, "O!:ispycreated", &sipSimpleWrapper_Type, &sw)) return NULL; /* sipIsDerived() is a misnomer. */ res = (sipIsDerived(sw) ? Py_True : Py_False); Py_INCREF(res); return res; } /* * Check if an instance is owned by Python or C/C++. */ static PyObject *isPyOwned(PyObject *self, PyObject *args) { sipSimpleWrapper *sw; PyObject *res; (void)self; if (!PyArg_ParseTuple(args, "O!:ispyowned", &sipSimpleWrapper_Type, &sw)) return NULL; res = (sipIsPyOwned(sw) ? Py_True : Py_False); Py_INCREF(res); return res; } /* * Mark an instance as having been deleted. */ static PyObject *setDeleted(PyObject *self, PyObject *args) { sipSimpleWrapper *sw; (void)self; if (!PyArg_ParseTuple(args, "O!:setdeleted", &sipSimpleWrapper_Type, &sw)) return NULL; clear_wrapper(sw); Py_INCREF(Py_None); return Py_None; } /* * Unwrap an instance. */ static PyObject *unwrapInstance(PyObject *self, PyObject *args) { sipSimpleWrapper *sw; (void)self; if (PyArg_ParseTuple(args, "O!:unwrapinstance", &sipSimpleWrapper_Type, &sw)) { void *addr; /* * We just get the pointer but don't try and cast it (which isn't * needed and wouldn't work with the way casts are currently * implemented if we are unwrapping something derived from a wrapped * class). */ if ((addr = sip_api_get_cpp_ptr(sw, NULL)) == NULL) return NULL; return PyLong_FromVoidPtr(addr); } return NULL; } /* * Wrap an instance. */ static PyObject *wrapInstance(PyObject *self, PyObject *args) { unsigned PY_LONG_LONG addr; sipWrapperType *wt; (void)self; if (PyArg_ParseTuple(args, "KO!:wrapinstance", &addr, &sipWrapperType_Type, &wt)) return sip_api_convert_from_type((void *)addr, wt->wt_td, NULL); return NULL; } /* * Set the destroy on exit flag from Python code. */ static PyObject *setDestroyOnExit(PyObject *self, PyObject *args) { (void)self; if (PyArg_ParseTuple(args, "i:setdestroyonexit", &destroy_on_exit)) { Py_INCREF(Py_None); return Py_None; } return NULL; } /* * Set the destroy on exit flag from C++ code. */ static void sip_api_set_destroy_on_exit(int value) { destroy_on_exit = value; } /* * Register a client module. A negative value is returned and an exception * raised if there was an error. */ static int sip_api_export_module(sipExportedModuleDef *client, unsigned api_major, unsigned api_minor, void *unused) { sipExportedModuleDef *em; const char *full_name = sipNameOfModule(client); (void)unused; /* Check that we can support it. */ if (api_major != SIP_API_MAJOR_NR || api_minor > SIP_API_MINOR_NR) { #if SIP_API_MINOR_NR > 0 PyErr_Format(PyExc_RuntimeError, "the sip module implements API v%d.0 to v%d.%d but the %s module requires API v%d.%d", SIP_API_MAJOR_NR, SIP_API_MAJOR_NR, SIP_API_MINOR_NR, full_name, api_major, api_minor); #else PyErr_Format(PyExc_RuntimeError, "the sip module implements API v%d.0 but the %s module requires API v%d.%d", SIP_API_MAJOR_NR, full_name, api_major, api_minor); #endif return -1; } /* Import any required modules. */ if (client->em_imports != NULL) { sipImportedModuleDef *im = client->em_imports; while (im->im_name != NULL) { PyObject *mod; if ((mod = PyImport_ImportModule(im->im_name)) == NULL) return -1; for (em = moduleList; em != NULL; em = em->em_next) if (strcmp(sipNameOfModule(em), im->im_name) == 0) break; if (em == NULL) { PyErr_Format(PyExc_RuntimeError, "the %s module failed to register with the sip module", im->im_name); return -1; } if (im->im_imported_types != NULL && importTypes(client, im, em) < 0) return -1; if (im->im_imported_veh != NULL && importErrorHandlers(client, im, em) < 0) return -1; if (im->im_imported_exceptions != NULL && importExceptions(client, im, em) < 0) return -1; ++im; } } for (em = moduleList; em != NULL; em = em->em_next) { /* SIP clients must have unique names. */ if (strcmp(sipNameOfModule(em), full_name) == 0) { PyErr_Format(PyExc_RuntimeError, "the sip module has already registered a module called %s", full_name); return -1; } /* Only one module can claim to wrap QObject. */ if (em->em_qt_api != NULL && client->em_qt_api != NULL) { PyErr_Format(PyExc_RuntimeError, "the %s and %s modules both wrap the QObject class", full_name, sipNameOfModule(em)); return -1; } } /* Convert the module name to an object. */ if ((client->em_nameobj = PyUnicode_FromString(full_name)) == NULL) return -1; /* Add it to the list of client modules. */ client->em_next = moduleList; moduleList = client; /* Get any keyword handler. */ if (!got_kw_handler) { kw_handler = sip_api_import_symbol("pyqt_kw_handler"); got_kw_handler = TRUE; } return 0; } /* * Initialise the contents of a client module. By this time anything that * this depends on should have been initialised. A negative value is returned * and an exception raised if there was an error. */ static int sip_api_init_module(sipExportedModuleDef *client, PyObject *mod_dict) { sipExportedModuleDef *em; sipEnumMemberDef *emd; int i; /* Handle any API. */ if (sipInitAPI(client, mod_dict) < 0) return -1; /* Create the module's types. */ for (i = 0; i < client->em_nrtypes; ++i) { sipTypeDef *td = client->em_types[i]; /* Skip external classes. */ if (td == NULL) continue; /* Skip if already initialised. */ if (td->td_module != NULL) continue; /* If it is a stub then just set the module so we can get its name. */ if (sipTypeIsStub(td)) { td->td_module = client; continue; } if (sipTypeIsEnum(td) || sipTypeIsScopedEnum(td)) { sipEnumTypeDef *etd = (sipEnumTypeDef *)td; if (td->td_version < 0 || sipIsRangeEnabled(client, td->td_version)) if (createEnum(client, etd, i, mod_dict) < 0) return -1; /* * Register the enum pickler for nested enums (non-nested enums * don't need special treatment). */ if (sipTypeIsEnum(td) && etd->etd_scope >= 0) { static PyMethodDef md = { "_pickle_enum", pickle_enum, METH_NOARGS, NULL }; if (setReduce(sipTypeAsPyTypeObject(td), &md) < 0) return -1; } } else if (sipTypeIsMapped(td)) { sipMappedTypeDef *mtd = (sipMappedTypeDef *)td; /* If there is a name then we need a namespace. */ if (mtd->mtd_container.cod_name >= 0) { if (createMappedType(client, mtd, mod_dict) < 0) return -1; } else { td->td_module = client; } } else { sipClassTypeDef *ctd = (sipClassTypeDef *)td; /* See if this is a namespace extender. */ if (ctd->ctd_container.cod_name < 0) { sipTypeDef *real_nspace; sipClassTypeDef **last; ctd->ctd_base.td_module = client; real_nspace = getGeneratedType(&ctd->ctd_container.cod_scope, client); /* Append this type to the real one. */ last = &((sipClassTypeDef *)real_nspace)->ctd_nsextender; while (*last != NULL) last = &(*last)->ctd_nsextender; *last = ctd; /* * Save the real namespace type so that it is the correct scope * for any enums or classes defined in this module. */ client->em_types[i] = real_nspace; } else if (createClassType(client, ctd, mod_dict) < 0) return -1; } } /* Set any Qt support API. */ if (client->em_qt_api != NULL) { sipQtSupport = client->em_qt_api; sipQObjectType = *sipQtSupport->qt_qobject; } /* Append any initialiser extenders to the relevant classes. */ if (client->em_initextend != NULL) { sipInitExtenderDef *ie = client->em_initextend; while (ie->ie_extender != NULL) { sipTypeDef *td = getGeneratedType(&ie->ie_class, client); int enabled; if (ie->ie_api_range < 0) enabled = TRUE; else enabled = sipIsRangeEnabled(td->td_module, ie->ie_api_range); if (enabled) { sipWrapperType *wt = (sipWrapperType *)sipTypeAsPyTypeObject(td); ie->ie_next = wt->wt_iextend; wt->wt_iextend = ie; } ++ie; } } /* Set the base class object for any sub-class convertors. */ if (client->em_convertors != NULL) { sipSubClassConvertorDef *scc = client->em_convertors; while (scc->scc_convertor != NULL) { scc->scc_basetype = getGeneratedType(&scc->scc_base, client); ++scc; } } /* Create the module's enum members. */ for (emd = client->em_enummembers, i = 0; i < client->em_nrenummembers; ++i, ++emd) { sipTypeDef *etd = client->em_types[emd->em_enum]; PyObject *mo; if (sipTypeIsScopedEnum(etd)) continue; mo = sip_api_convert_from_enum(emd->em_val, etd); if (dict_set_and_discard(mod_dict, emd->em_name, mo) < 0) return -1; } /* * Add any class static instances. We need to do this once all types are * fully formed because of potential interdependencies. */ for (i = 0; i < client->em_nrtypes; ++i) { sipTypeDef *td = client->em_types[i]; if (td != NULL && !sipTypeIsStub(td) && sipTypeIsClass(td)) if (addInstances((sipTypeAsPyTypeObject(td))->tp_dict, &((sipClassTypeDef *)td)->ctd_container.cod_instances) < 0) return -1; } /* Add any global static instances. */ if (addInstances(mod_dict, &client->em_instances) < 0) return -1; /* Add any license. */ if (client->em_license != NULL && addLicense(mod_dict, client->em_license) < 0) return -1; /* See if the new module satisfies any outstanding external types. */ for (em = moduleList; em != NULL; em = em->em_next) { sipExternalTypeDef *etd; if (em == client || em->em_external == NULL) continue; for (etd = em->em_external; etd->et_nr >= 0; ++etd) { if (etd->et_name == NULL) continue; for (i = 0; i < client->em_nrtypes; ++i) { sipTypeDef *td = client->em_types[i]; if (td != NULL && !sipTypeIsStub(td) && sipTypeIsClass(td)) { const char *pyname = sipPyNameOfContainer( &((sipClassTypeDef *)td)->ctd_container, td); if (strcmp(etd->et_name, pyname) == 0) { em->em_types[etd->et_nr] = td; etd->et_name = NULL; break; } } } } } return 0; } /* * Called by the interpreter to do any final clearing up, just in case the * interpreter will re-start. */ static void finalise(void) { sipExportedModuleDef *em; /* * Mark the Python API as unavailable. This should already have been done, * but just in case... */ sipInterpreter = NULL; /* Handle any delayed dtors. */ for (em = moduleList; em != NULL; em = em->em_next) if (em->em_ddlist != NULL) { em->em_delayeddtors(em->em_ddlist); /* Free the list. */ do { sipDelayedDtor *dd = em->em_ddlist; em->em_ddlist = dd->dd_next; sip_api_free(dd); } while (em->em_ddlist != NULL); } licenseName = NULL; licenseeName = NULL; typeName = NULL; timestampName = NULL; signatureName = NULL; /* Release all memory we've allocated directly. */ sipOMFinalise(&cppPyMap); /* Re-initialise those globals that (might) need it. */ moduleList = NULL; } /* * Register the given Python type. */ static int sip_api_register_py_type(PyTypeObject *type) { return addPyObjectToList(&sipRegisteredPyTypes, (PyObject *)type); } /* * Find the registered type with the given name. Raise an exception if it * couldn't be found. */ static PyObject *findPyType(const char *name) { sipPyObject *po; for (po = sipRegisteredPyTypes; po != NULL; po = po->next) { PyObject *type = po->object; if (strcmp(((PyTypeObject *)type)->tp_name, name) == 0) return type; } PyErr_Format(PyExc_RuntimeError, "%s is not a registered type", name); return NULL; } /* * Add a wrapped C/C++ pointer to the list of delayed dtors. */ static void sip_api_add_delayed_dtor(sipSimpleWrapper *sw) { void *ptr; const sipClassTypeDef *ctd; sipExportedModuleDef *em; if ((ptr = getPtrTypeDef(sw, &ctd)) == NULL) return; /* Find the defining module. */ for (em = moduleList; em != NULL; em = em->em_next) { int i; for (i = 0; i < em->em_nrtypes; ++i) if (em->em_types[i] == (const sipTypeDef *)ctd) { sipDelayedDtor *dd; if ((dd = sip_api_malloc(sizeof (sipDelayedDtor))) == NULL) return; /* Add to the list. */ dd->dd_ptr = ptr; dd->dd_name = sipPyNameOfContainer(&ctd->ctd_container, (sipTypeDef *)ctd); dd->dd_isderived = sipIsDerived(sw); dd->dd_next = em->em_ddlist; em->em_ddlist = dd; return; } } } /* * A wrapper around the Python memory allocater that will raise an exception if * if the allocation fails. */ void *sip_api_malloc(size_t nbytes) { void *mem; if ((mem = PyMem_RawMalloc(nbytes)) == NULL) PyErr_NoMemory(); return mem; } /* * A wrapper around the Python memory de-allocater. */ void sip_api_free(void *mem) { PyMem_RawFree(mem); } /* * Extend a Python slot by looking in other modules to see if there is an * extender function that can handle the arguments. */ static PyObject *sip_api_pyslot_extend(sipExportedModuleDef *mod, sipPySlotType st, const sipTypeDef *td, PyObject *arg0, PyObject *arg1) { sipExportedModuleDef *em; /* Go through each module. */ for (em = moduleList; em != NULL; em = em->em_next) { sipPySlotExtenderDef *ex; /* Skip the module that couldn't handle the arguments. */ if (em == mod) continue; /* Skip if the module doesn't have any extenders. */ if (em->em_slotextend == NULL) continue; /* Go through each extender. */ for (ex = em->em_slotextend; ex->pse_func != NULL; ++ex) { PyObject *res; /* Skip if not the right slot type. */ if (ex->pse_type != st) continue; /* Check against the type if one was given. */ if (td != NULL && td != getGeneratedType(&ex->pse_class, NULL)) continue; PyErr_Clear(); res = ((binaryfunc)ex->pse_func)(arg0, arg1); if (res != Py_NotImplemented) return res; } } /* The arguments couldn't handled anywhere. */ PyErr_Clear(); Py_INCREF(Py_NotImplemented); return Py_NotImplemented; } /* * Convert a new C/C++ instance to a Python instance of a specific Python type.. */ static PyObject *sip_api_convert_from_new_pytype(void *cpp, PyTypeObject *py_type, sipWrapper *owner, sipSimpleWrapper **selfp, const char *fmt, ...) { PyObject *args, *res; va_list va; va_start(va, fmt); if ((args = PyTuple_New(strlen(fmt))) != NULL && buildObject(args, fmt, va) != NULL) { res = sipWrapInstance(cpp, py_type, args, owner, (selfp != NULL ? SIP_DERIVED_CLASS : 0)); /* Initialise the rest of an instance of a derived class. */ if (selfp != NULL) *selfp = (sipSimpleWrapper *)res; } else { res = NULL; } Py_XDECREF(args); va_end(va); return res; } /* * Call a method and return the result. */ static PyObject *call_method(PyObject *method, const char *fmt, va_list va) { PyObject *args, *res; if ((args = PyTuple_New(strlen(fmt))) == NULL) return NULL; if (buildObject(args, fmt, va) != NULL) res = PyObject_CallObject(method, args); else res = NULL; Py_DECREF(args); return res; } /* * Call the Python re-implementation of a C++ virtual that does not return a * value and handle the result.. */ static void sip_api_call_procedure_method(sip_gilstate_t gil_state, sipVirtErrorHandlerFunc error_handler, sipSimpleWrapper *py_self, PyObject *method, const char *fmt, ...) { PyObject *res; va_list va; va_start(va, fmt); res = call_method(method, fmt, va); va_end(va); if (res != NULL) { Py_DECREF(res); if (res != Py_None) { sip_api_bad_catcher_result(method); res = NULL; } } Py_DECREF(method); if (res == NULL) sip_api_call_error_handler(error_handler, py_self, gil_state); SIP_RELEASE_GIL(gil_state); } /* * Call the Python re-implementation of a C++ virtual. */ static PyObject *sip_api_call_method(int *isErr, PyObject *method, const char *fmt, ...) { PyObject *res; va_list va; va_start(va, fmt); res = call_method(method, fmt, va); va_end(va); if (res == NULL && isErr != NULL) *isErr = TRUE; return res; } /* * Build a result object based on a format string. */ static PyObject *sip_api_build_result(int *isErr, const char *fmt, ...) { PyObject *res = NULL; int badfmt, tupsz; va_list va; va_start(va,fmt); /* Basic validation of the format string. */ badfmt = FALSE; if (*fmt == '(') { char *ep; if ((ep = strchr(fmt,')')) == NULL || ep[1] != '\0') badfmt = TRUE; else tupsz = (int)(ep - fmt - 1); } else if (strlen(fmt) == 1) tupsz = -1; else badfmt = TRUE; if (badfmt) PyErr_Format(PyExc_SystemError,"sipBuildResult(): invalid format string \"%s\"",fmt); else if (tupsz < 0 || (res = PyTuple_New(tupsz)) != NULL) res = buildObject(res,fmt,va); va_end(va); if (res == NULL && isErr != NULL) *isErr = TRUE; return res; } /* * Get the values off the stack and put them into an object. */ static PyObject *buildObject(PyObject *obj, const char *fmt, va_list va) { char ch, termch; int i; /* * The format string has already been checked that it is properly formed if * it is enclosed in parenthesis. */ if (*fmt == '(') { termch = ')'; ++fmt; } else termch = '\0'; i = 0; while ((ch = *fmt++) != termch) { PyObject *el; switch (ch) { case 'g': { char *s; Py_ssize_t l; s = va_arg(va, char *); l = va_arg(va, Py_ssize_t); if (s != NULL) { el = PyBytes_FromStringAndSize(s, l); } else { Py_INCREF(Py_None); el = Py_None; } } break; case 'G': #if defined(HAVE_WCHAR_H) { wchar_t *s; Py_ssize_t l; s = va_arg(va, wchar_t *); l = va_arg(va, Py_ssize_t); if (s != NULL) el = PyUnicode_FromWideChar(s, l); else { Py_INCREF(Py_None); el = Py_None; } } #else raiseNoWChar(); el = NULL; #endif break; case 'b': el = PyBool_FromLong(va_arg(va,int)); break; case 'c': { char c = va_arg(va, int); el = PyBytes_FromStringAndSize(&c, 1); } break; case 'a': { char c = va_arg(va, int); el = PyUnicode_FromStringAndSize(&c, 1); } break; case 'w': #if defined(HAVE_WCHAR_H) { wchar_t c = va_arg(va, int); el = PyUnicode_FromWideChar(&c, 1); } #else raiseNoWChar(); el = NULL; #endif break; case 'E': { int ev = va_arg(va, int); PyTypeObject *et = va_arg(va, PyTypeObject *); el = sip_api_convert_from_enum(ev, ((const sipEnumTypeObject *)et)->type); } break; case 'F': { int ev = va_arg(va, int); const sipTypeDef *td = va_arg(va, const sipTypeDef *); el = sip_api_convert_from_enum(ev, td); } break; case 'd': case 'f': el = PyFloat_FromDouble(va_arg(va, double)); break; case 'e': case 'h': case 'i': case 'L': el = PyLong_FromLong(va_arg(va, int)); break; case 'l': el = PyLong_FromLong(va_arg(va, long)); break; case 'm': el = PyLong_FromUnsignedLong(va_arg(va, unsigned long)); break; case 'n': #if defined(HAVE_LONG_LONG) el = PyLong_FromLongLong(va_arg(va, PY_LONG_LONG)); #else el = PyLong_FromLong(va_arg(va, long)); #endif break; case 'o': #if defined(HAVE_LONG_LONG) el = PyLong_FromUnsignedLongLong(va_arg(va, unsigned PY_LONG_LONG)); #else el = PyLong_FromUnsignedLong(va_arg(va, unsigned long)); #endif break; case 's': { char *s = va_arg(va, char *); if (s != NULL) { el = PyBytes_FromString(s); } else { Py_INCREF(Py_None); el = Py_None; } } break; case 'A': { char *s = va_arg(va, char *); if (s != NULL) { el = PyUnicode_FromString(s); } else { Py_INCREF(Py_None); el = Py_None; } } break; case 'x': #if defined(HAVE_WCHAR_H) { wchar_t *s = va_arg(va, wchar_t *); if (s != NULL) el = PyUnicode_FromWideChar(s, (Py_ssize_t)wcslen(s)); else { Py_INCREF(Py_None); el = Py_None; } } #else raiseNoWChar(); el = NULL; #endif break; case 't': case 'u': case 'M': el = PyLong_FromUnsignedLong(va_arg(va, unsigned)); break; case '=': el = PyLong_FromSize_t(va_arg(va, size_t)); break; case 'B': { void *p = va_arg(va,void *); sipWrapperType *wt = va_arg(va, sipWrapperType *); PyObject *xfer = va_arg(va, PyObject *); el = sip_api_convert_from_new_type(p, wt->wt_td, xfer); } break; case 'N': { void *p = va_arg(va, void *); const sipTypeDef *td = va_arg(va, const sipTypeDef *); PyObject *xfer = va_arg(va, PyObject *); el = sip_api_convert_from_new_type(p, td, xfer); } break; case 'C': { void *p = va_arg(va,void *); sipWrapperType *wt = va_arg(va, sipWrapperType *); PyObject *xfer = va_arg(va, PyObject *); el = sip_api_convert_from_type(p, wt->wt_td, xfer); } break; case 'D': { void *p = va_arg(va, void *); const sipTypeDef *td = va_arg(va, const sipTypeDef *); PyObject *xfer = va_arg(va, PyObject *); el = sip_api_convert_from_type(p, td, xfer); } break; case 'r': { void *p = va_arg(va, void *); Py_ssize_t l = va_arg(va, Py_ssize_t); const sipTypeDef *td = va_arg(va, const sipTypeDef *); el = convertToSequence(p, l, td); } break; case 'R': el = va_arg(va,PyObject *); break; case 'S': el = va_arg(va,PyObject *); Py_INCREF(el); break; case 'V': el = sip_api_convert_from_void_ptr(va_arg(va, void *)); break; case 'z': { const char *name = va_arg(va, const char *); void *p = va_arg(va, void *); if (p == NULL) { el = Py_None; Py_INCREF(el); } else { el = PyCapsule_New(p, name, NULL); } } break; default: PyErr_Format(PyExc_SystemError,"buildObject(): invalid format character '%c'",ch); el = NULL; } if (el == NULL) { Py_XDECREF(obj); return NULL; } if (obj == NULL) return el; PyTuple_SET_ITEM(obj,i,el); ++i; } return obj; } /* * Parse a result object based on a format string. As of v9.0 of the API this * is only ever called by handwritten code. */ static int sip_api_parse_result(int *isErr, PyObject *method, PyObject *res, const char *fmt, ...) { int rc; va_list va; va_start(va, fmt); rc = parseResult(method, res, NULL, fmt, va); va_end(va); if (isErr != NULL && rc < 0) *isErr = TRUE; return rc; } /* * Parse a result object based on a format string. */ static int sip_api_parse_result_ex(sip_gilstate_t gil_state, sipVirtErrorHandlerFunc error_handler, sipSimpleWrapper *py_self, PyObject *method, PyObject *res, const char *fmt, ...) { int rc; if (res != NULL) { va_list va; va_start(va, fmt); rc = parseResult(method, res, deref_mixin(py_self), fmt, va); va_end(va); Py_DECREF(res); } else { rc = -1; } Py_DECREF(method); if (rc < 0) sip_api_call_error_handler(error_handler, py_self, gil_state); SIP_RELEASE_GIL(gil_state); return rc; } /* * Call a virtual error handler. This is called with the GIL and from the * thread that raised the error. */ static void sip_api_call_error_handler(sipVirtErrorHandlerFunc error_handler, sipSimpleWrapper *py_self, sip_gilstate_t sipGILState) { if (error_handler != NULL) error_handler(deref_mixin(py_self), sipGILState); else PyErr_Print(); } /* * Do the main work of parsing a result object based on a format string. */ static int parseResult(PyObject *method, PyObject *res, sipSimpleWrapper *py_self, const char *fmt, va_list va) { int tupsz, rc = 0; /* We rely on PyErr_Occurred(). */ PyErr_Clear(); /* Get self if it is provided as an argument. */ if (*fmt == 'S') { py_self = va_arg(va, sipSimpleWrapper *); ++fmt; } /* Basic validation of the format string. */ if (*fmt == '(') { char ch; const char *cp = ++fmt; int sub_format = FALSE; tupsz = 0; while ((ch = *cp++) != ')') { if (ch == '\0') { PyErr_Format(PyExc_SystemError, "sipParseResult(): invalid format string \"%s\"", fmt - 1); rc = -1; break; } if (sub_format) { sub_format = FALSE; } else { ++tupsz; /* Some format characters have a sub-format. */ if (strchr("aAHDC", ch) != NULL) sub_format = TRUE; } } if (rc == 0) if (!PyTuple_Check(res) || PyTuple_GET_SIZE(res) != tupsz) { sip_api_bad_catcher_result(method); rc = -1; } } else tupsz = -1; if (rc == 0) { char ch; int i = 0; while ((ch = *fmt++) != '\0' && ch != ')' && rc == 0) { PyObject *arg; int invalid = FALSE; if (tupsz > 0) { arg = PyTuple_GET_ITEM(res,i); ++i; } else arg = res; switch (ch) { case 'g': { const char **p = va_arg(va, const char **); Py_ssize_t *szp = va_arg(va, Py_ssize_t *); if (parseBytes_AsCharArray(arg, p, szp) < 0) invalid = TRUE; } break; case 'G': #if defined(HAVE_WCHAR_H) { wchar_t **p = va_arg(va, wchar_t **); Py_ssize_t *szp = va_arg(va, Py_ssize_t *); if (parseWCharArray(arg, p, szp) < 0) invalid = TRUE; } #else raiseNoWChar(); invalid = TRUE; #endif break; case 'b': { char *p = va_arg(va, void *); int v = sip_api_convert_to_bool(arg); if (v < 0) invalid = TRUE; else if (p != NULL) sipSetBool(p, v); } break; case 'c': { char *p = va_arg(va, char *); if (parseBytes_AsChar(arg, p) < 0) invalid = TRUE; } break; case 'a': { char *p = va_arg(va, char *); int enc; switch (*fmt++) { case 'A': enc = parseString_AsASCIIChar(arg, p); break; case 'L': enc = parseString_AsLatin1Char(arg, p); break; case '8': enc = parseString_AsUTF8Char(arg, p); break; default: enc = -1; } if (enc < 0) invalid = TRUE; } break; case 'w': #if defined(HAVE_WCHAR_H) { wchar_t *p = va_arg(va, wchar_t *); if (parseWChar(arg, p) < 0) invalid = TRUE; } #else raiseNoWChar(); invalid = TRUE; #endif break; case 'd': { double *p = va_arg(va, double *); double v = PyFloat_AsDouble(arg); if (PyErr_Occurred()) invalid = TRUE; else if (p != NULL) *p = v; } break; case 'E': { PyTypeObject *et = va_arg(va, PyTypeObject *); int *p = va_arg(va, int *); int v = sip_api_convert_to_enum(arg, ((sipEnumTypeObject *)et)->type); if (PyErr_Occurred()) invalid = TRUE; else if (p != NULL) *p = v; } break; case 'F': { sipTypeDef *td = va_arg(va, sipTypeDef *); int *p = va_arg(va, int *); int v = sip_api_convert_to_enum(arg, td); if (PyErr_Occurred()) invalid = TRUE; else if (p != NULL) *p = v; } break; case 'f': { float *p = va_arg(va, float *); float v = (float)PyFloat_AsDouble(arg); if (PyErr_Occurred()) invalid = TRUE; else if (p != NULL) *p = v; } break; case 'I': { char *p = va_arg(va, char *); char v = sip_api_long_as_char(arg); if (PyErr_Occurred()) invalid = TRUE; else if (p != NULL) *p = v; } break; case 'L': { signed char *p = va_arg(va, signed char *); signed char v = sip_api_long_as_signed_char(arg); if (PyErr_Occurred()) invalid = TRUE; else if (p != NULL) *p = v; } break; case 'M': { unsigned char *p = va_arg(va, unsigned char *); unsigned char v = sip_api_long_as_unsigned_char(arg); if (PyErr_Occurred()) invalid = TRUE; else if (p != NULL) *p = v; } break; case 'h': { signed short *p = va_arg(va, signed short *); signed short v = sip_api_long_as_short(arg); if (PyErr_Occurred()) invalid = TRUE; else if (p != NULL) *p = v; } break; case 't': { unsigned short *p = va_arg(va, unsigned short *); unsigned short v = sip_api_long_as_unsigned_short(arg); if (PyErr_Occurred()) invalid = TRUE; else if (p != NULL) *p = v; } break; case 'e': { int *p = va_arg(va, int *); int v = long_as_nonoverflow_int(arg); if (PyErr_Occurred()) invalid = TRUE; else if (p != NULL) *p = v; } break; case 'i': { int *p = va_arg(va, int *); int v = sip_api_long_as_int(arg); if (PyErr_Occurred()) invalid = TRUE; else if (p != NULL) *p = v; } break; case 'u': { unsigned *p = va_arg(va, unsigned *); unsigned v = sip_api_long_as_unsigned_int(arg); if (PyErr_Occurred()) invalid = TRUE; else if (p != NULL) *p = v; } break; case '=': { size_t *p = va_arg(va, size_t *); size_t v = sip_api_long_as_size_t(arg); if (PyErr_Occurred()) invalid = TRUE; else if (p != NULL) *p = v; } break; case 'l': { long *p = va_arg(va, long *); long v = sip_api_long_as_long(arg); if (PyErr_Occurred()) invalid = TRUE; else if (p != NULL) *p = v; } break; case 'm': { unsigned long *p = va_arg(va, unsigned long *); unsigned long v = sip_api_long_as_unsigned_long(arg); if (PyErr_Occurred()) invalid = TRUE; else if (p != NULL) *p = v; } break; case 'n': { #if defined(HAVE_LONG_LONG) PY_LONG_LONG *p = va_arg(va, PY_LONG_LONG *); PY_LONG_LONG v = sip_api_long_as_long_long(arg); #else long *p = va_arg(va, long *); long v = sip_api_long_as_long(arg); #endif if (PyErr_Occurred()) invalid = TRUE; else if (p != NULL) *p = v; } break; case 'o': { #if defined(HAVE_LONG_LONG) unsigned PY_LONG_LONG *p = va_arg(va, unsigned PY_LONG_LONG *); unsigned PY_LONG_LONG v = sip_api_long_as_unsigned_long_long(arg); #else unsigned long *p = va_arg(va, unsigned long *); unsigned long v = sip_api_long_as_unsigned_long(arg); #endif if (PyErr_Occurred()) invalid = TRUE; else if (p != NULL) *p = v; } break; case 's': { const char **p = va_arg(va, const char **); if (parseBytes_AsString(arg, p) < 0) invalid = TRUE; } break; case 'A': { int key = va_arg(va, int); const char **p = va_arg(va, const char **); PyObject *keep; switch (*fmt++) { case 'A': keep = parseString_AsASCIIString(arg, p); break; case 'L': keep = parseString_AsLatin1String(arg, p); break; case '8': keep = parseString_AsUTF8String(arg, p); break; default: keep = NULL; } if (keep == NULL) invalid = TRUE; else sip_api_keep_reference((PyObject *)py_self, key, keep); } break; case 'B': { int key = va_arg(va, int); const char **p = va_arg(va, const char **); if (parseBytes_AsString(arg, p) < 0) invalid = TRUE; else sip_api_keep_reference((PyObject *)py_self, key, arg); } break; case 'x': #if defined(HAVE_WCHAR_H) { wchar_t **p = va_arg(va, wchar_t **); if (parseWCharString(arg, p) < 0) invalid = TRUE; } #else raiseNoWChar(); invalid = TRUE; #endif break; case 'C': { if (*fmt == '\0') { invalid = TRUE; } else { int flags = *fmt++ - '0'; int iserr = FALSE; sipWrapperType *type; void **cpp; int *state; type = va_arg(va, sipWrapperType *); if (flags & FMT_RP_NO_STATE_DEPR) state = NULL; else state = va_arg(va, int *); cpp = va_arg(va, void **); *cpp = sip_api_force_convert_to_type(arg, type->wt_td, (flags & FMT_RP_FACTORY ? arg : NULL), (flags & FMT_RP_DEREF ? SIP_NOT_NONE : 0), state, &iserr); if (iserr) invalid = TRUE; } } break; case 'D': { if (*fmt == '\0') { invalid = TRUE; } else { int flags = *fmt++ - '0'; int iserr = FALSE; const sipTypeDef *td; void **cpp; int *state; td = va_arg(va, const sipTypeDef *); if (flags & FMT_RP_NO_STATE_DEPR) state = NULL; else state = va_arg(va, int *); cpp = va_arg(va, void **); *cpp = sip_api_force_convert_to_type(arg, td, (flags & FMT_RP_FACTORY ? arg : NULL), (flags & FMT_RP_DEREF ? SIP_NOT_NONE : 0), state, &iserr); if (iserr) invalid = TRUE; } } break; case 'H': { if (*fmt == '\0') { invalid = TRUE; } else { int flags = *fmt++ - '0'; int iserr = FALSE, state; const sipTypeDef *td; void *cpp, *val; td = va_arg(va, const sipTypeDef *); cpp = va_arg(va, void **); val = sip_api_force_convert_to_type(arg, td, (flags & FMT_RP_FACTORY ? arg : NULL), (flags & FMT_RP_DEREF ? SIP_NOT_NONE : 0), &state, &iserr); if (iserr) { invalid = TRUE; } else if (flags & FMT_RP_MAKE_COPY) { sipAssignFunc assign_helper; if (sipTypeIsMapped(td)) assign_helper = ((const sipMappedTypeDef *)td)->mtd_assign; else assign_helper = ((const sipClassTypeDef *)td)->ctd_assign; assert(assign_helper != NULL); if (cpp != NULL) assign_helper(cpp, 0, val); sip_api_release_type(val, td, state); } else if (cpp != NULL) { *(void **)cpp = val; } } } break; case 'N': { PyTypeObject *type = va_arg(va, PyTypeObject *); PyObject **p = va_arg(va, PyObject **); if (arg == Py_None || PyObject_TypeCheck(arg, type)) { if (p != NULL) { Py_INCREF(arg); *p = arg; } } else { invalid = TRUE; } } break; case 'O': { PyObject **p = va_arg(va, PyObject **); if (p != NULL) { Py_INCREF(arg); *p = arg; } } break; case 'T': { PyTypeObject *type = va_arg(va, PyTypeObject *); PyObject **p = va_arg(va, PyObject **); if (PyObject_TypeCheck(arg, type)) { if (p != NULL) { Py_INCREF(arg); *p = arg; } } else { invalid = TRUE; } } break; case 'V': { void *v = sip_api_convert_to_void_ptr(arg); void **p = va_arg(va, void **); if (PyErr_Occurred()) invalid = TRUE; else if (p != NULL) *p = v; } break; case 'z': { const char *name = va_arg(va, const char *); void **p = va_arg(va, void **); if (arg == Py_None) { if (p != NULL) *p = NULL; } else { #if defined(SIP_USE_CAPSULE) void *v = PyCapsule_GetPointer(arg, name); #else void *v = sip_api_convert_to_void_ptr(arg); (void)name; #endif if (PyErr_Occurred()) invalid = TRUE; else if (p != NULL) *p = v; } } break; case 'Z': if (arg != Py_None) invalid = TRUE; break; case '!': { PyObject **p = va_arg(va, PyObject **); if (PyObject_CheckBuffer(arg)) { if (p != NULL) { Py_INCREF(arg); *p = arg; } } else { invalid = TRUE; } } break; case '$': { PyObject **p = va_arg(va, PyObject **); if (arg == Py_None || PyObject_CheckBuffer(arg)) { if (p != NULL) { Py_INCREF(arg); *p = arg; } } else { invalid = TRUE; } } break; default: PyErr_Format(PyExc_SystemError,"sipParseResult(): invalid format character '%c'",ch); rc = -1; } if (invalid) { sip_api_bad_catcher_result(method); rc = -1; break; } } } return rc; } /* * Parse the arguments to a C/C++ function without any side effects. */ static int sip_api_parse_args(PyObject **parseErrp, PyObject *sipArgs, const char *fmt, ...) { int ok; va_list va; va_start(va, fmt); ok = parseKwdArgs(parseErrp, sipArgs, NULL, NULL, NULL, fmt, va); va_end(va); return ok; } /* * Parse the positional and/or keyword arguments to a C/C++ function without * any side effects. */ static int sip_api_parse_kwd_args(PyObject **parseErrp, PyObject *sipArgs, PyObject *sipKwdArgs, const char **kwdlist, PyObject **unused, const char *fmt, ...) { int ok; va_list va; if (unused != NULL) { /* * Initialise the return of any unused keyword arguments. This is * used by any ctor overload. */ *unused = NULL; } va_start(va, fmt); ok = parseKwdArgs(parseErrp, sipArgs, sipKwdArgs, kwdlist, unused, fmt, va); va_end(va); /* Release any unused arguments if the parse failed. */ if (!ok && unused != NULL) { Py_XDECREF(*unused); } return ok; } /* * Parse the arguments to a C/C++ function without any side effects. */ static int parseKwdArgs(PyObject **parseErrp, PyObject *sipArgs, PyObject *sipKwdArgs, const char **kwdlist, PyObject **unused, const char *fmt, va_list va_orig) { int no_tmp_tuple, ok, selfarg; sipSimpleWrapper *self; PyObject *single_arg; va_list va; /* Previous second pass errors stop subsequent parses. */ if (*parseErrp != NULL && !PyList_Check(*parseErrp)) return FALSE; /* * See if we are parsing a single argument. In current versions we are * told explicitly by the first character of the format string. In earlier * versions we guessed (sometimes wrongly). */ if (*fmt == '1') { ++fmt; no_tmp_tuple = FALSE; } else no_tmp_tuple = PyTuple_Check(sipArgs); if (no_tmp_tuple) { Py_INCREF(sipArgs); } else if ((single_arg = PyTuple_New(1)) != NULL) { Py_INCREF(sipArgs); PyTuple_SET_ITEM(single_arg, 0, sipArgs); sipArgs = single_arg; } else { /* Stop all parsing and indicate an exception has been raised. */ Py_XDECREF(*parseErrp); *parseErrp = Py_None; Py_INCREF(Py_None); return FALSE; } /* * The first pass checks all the types and does conversions that are cheap * and have no side effects. */ va_copy(va, va_orig); ok = parsePass1(parseErrp, &self, &selfarg, sipArgs, sipKwdArgs, kwdlist, unused, fmt, va); va_end(va); if (ok) { /* * The second pass does any remaining conversions now that we know we * have the right signature. */ va_copy(va, va_orig); ok = parsePass2(self, selfarg, sipArgs, sipKwdArgs, kwdlist, fmt, va); va_end(va); /* Remove any previous failed parses. */ Py_XDECREF(*parseErrp); if (ok) { *parseErrp = NULL; } else { /* Indicate that an exception has been raised. */ *parseErrp = Py_None; Py_INCREF(Py_None); } } Py_DECREF(sipArgs); return ok; } /* * Return a string as a Python object that describes an argument with an * unexpected type. */ static PyObject *bad_type_str(int arg_nr, PyObject *arg) { return PyUnicode_FromFormat("argument %d has unexpected type '%s'", arg_nr, Py_TYPE(arg)->tp_name); } /* * Adds a failure about an argument with an incorrect type to the current list * of exceptions. */ static sipErrorState sip_api_bad_callable_arg(int arg_nr, PyObject *arg) { PyObject *detail = bad_type_str(arg_nr + 1, arg); if (detail == NULL) return sipErrorFail; PyErr_SetObject(PyExc_TypeError, detail); Py_DECREF(detail); return sipErrorContinue; } /* * Adds the current exception to the current list of exceptions (if it is a * user exception) or replace the current list of exceptions. */ static void sip_api_add_exception(sipErrorState es, PyObject **parseErrp) { assert(*parseErrp == NULL); if (es == sipErrorContinue) { sipParseFailure failure; PyObject *e_type, *e_traceback; /* Get the value of the exception. */ PyErr_Fetch(&e_type, &failure.detail_obj, &e_traceback); Py_XDECREF(e_type); Py_XDECREF(e_traceback); failure.reason = Exception; add_failure(parseErrp, &failure); if (failure.reason == Raised) { Py_XDECREF(failure.detail_obj); es = sipErrorFail; } } if (es == sipErrorFail) { Py_XDECREF(*parseErrp); *parseErrp = Py_None; Py_INCREF(Py_None); } } /* * The dtor for parse failure wrapped in a Python object. */ static void failure_dtor(PyObject *capsule) { sipParseFailure *failure = (sipParseFailure *)PyCapsule_GetPointer(capsule, NULL); Py_XDECREF(failure->detail_obj); sip_api_free(failure); } /* * Add a parse failure to the current list of exceptions. */ static void add_failure(PyObject **parseErrp, sipParseFailure *failure) { sipParseFailure *failure_copy; PyObject *failure_obj; /* Create the list if necessary. */ if (*parseErrp == NULL && (*parseErrp = PyList_New(0)) == NULL) { failure->reason = Raised; return; } /* * Make a copy of the failure, convert it to a Python object and add it to * the list. We do it this way to make it as lightweight as possible. */ if ((failure_copy = sip_api_malloc(sizeof (sipParseFailure))) == NULL) { failure->reason = Raised; return; } *failure_copy = *failure; if ((failure_obj = PyCapsule_New(failure_copy, NULL, failure_dtor)) == NULL) { sip_api_free(failure_copy); failure->reason = Raised; return; } /* Ownership of any detail object is now with the wrapped failure. */ failure->detail_obj = NULL; if (PyList_Append(*parseErrp, failure_obj) < 0) { Py_DECREF(failure_obj); failure->reason = Raised; return; } Py_DECREF(failure_obj); } /* * Parse one or a pair of arguments to a C/C++ function without any side * effects. */ static int sip_api_parse_pair(PyObject **parseErrp, PyObject *sipArg0, PyObject *sipArg1, const char *fmt, ...) { int ok, selfarg; sipSimpleWrapper *self; PyObject *args; va_list va; /* Previous second pass errors stop subsequent parses. */ if (*parseErrp != NULL && !PyList_Check(*parseErrp)) return FALSE; if ((args = PyTuple_New(sipArg1 != NULL ? 2 : 1)) == NULL) { /* Stop all parsing and indicate an exception has been raised. */ Py_XDECREF(*parseErrp); *parseErrp = Py_None; Py_INCREF(Py_None); return FALSE; } Py_INCREF(sipArg0); PyTuple_SET_ITEM(args, 0, sipArg0); if (sipArg1 != NULL) { Py_INCREF(sipArg1); PyTuple_SET_ITEM(args, 1, sipArg1); } /* * The first pass checks all the types and does conversions that are cheap * and have no side effects. */ va_start(va, fmt); ok = parsePass1(parseErrp, &self, &selfarg, args, NULL, NULL, NULL, fmt, va); va_end(va); if (ok) { /* * The second pass does any remaining conversions now that we know we * have the right signature. */ va_start(va, fmt); ok = parsePass2(self, selfarg, args, NULL, NULL, fmt, va); va_end(va); /* Remove any previous failed parses. */ Py_XDECREF(*parseErrp); if (ok) { *parseErrp = NULL; } else { /* Indicate that an exception has been raised. */ *parseErrp = Py_None; Py_INCREF(Py_None); } } Py_DECREF(args); return ok; } /* * First pass of the argument parse, converting those that can be done so * without any side effects. Return TRUE if the arguments matched. */ static int parsePass1(PyObject **parseErrp, sipSimpleWrapper **selfp, int *selfargp, PyObject *sipArgs, PyObject *sipKwdArgs, const char **kwdlist, PyObject **unused, const char *fmt, va_list va) { int compulsory, argnr, nr_args; Py_ssize_t nr_pos_args, nr_kwd_args, nr_kwd_args_used; sipParseFailure failure; failure.reason = Ok; failure.detail_obj = NULL; compulsory = TRUE; argnr = 0; nr_args = 0; nr_pos_args = PyTuple_GET_SIZE(sipArgs); nr_kwd_args = nr_kwd_args_used = 0; if (sipKwdArgs != NULL) { assert(PyDict_Check(sipKwdArgs)); nr_kwd_args = PyDict_Size(sipKwdArgs); } /* * Handle those format characters that deal with the "self" argument. They * will always be the first one. */ *selfp = NULL; *selfargp = FALSE; switch (*fmt++) { case '#': /* A ctor has an argument with the /Transfer/ annotation. */ *selfp = (sipSimpleWrapper *)va_arg(va, PyObject *); break; case 'B': case 'p': { PyObject *self; sipTypeDef *td; self = *va_arg(va, PyObject **); td = va_arg(va, sipTypeDef *); va_arg(va, void **); if (self == NULL) { if (!getSelfFromArgs(td, sipArgs, argnr, selfp)) { failure.reason = Unbound; failure.detail_str = sipPyNameOfContainer( &((sipClassTypeDef *)td)->ctd_container, td); break; } *selfargp = TRUE; ++argnr; } else *selfp = (sipSimpleWrapper *)self; break; } case 'C': *selfp = (sipSimpleWrapper *)va_arg(va,PyObject *); break; default: --fmt; } /* * Now handle the remaining arguments. We continue to parse if we get an * overflow because that is, strictly speaking, a second pass error. */ while (failure.reason == Ok || failure.reason == Overflow) { char ch; PyObject *arg; PyErr_Clear(); /* See if the following arguments are optional. */ if ((ch = *fmt++) == '|') { compulsory = FALSE; ch = *fmt++; } /* See if we don't expect anything else. */ if (ch == '\0') { if (argnr < nr_pos_args) { /* There are still positional arguments. */ failure.reason = TooMany; } else if (nr_kwd_args_used != nr_kwd_args) { /* * Take a shortcut if no keyword arguments were used and we are * interested in them. */ if (nr_kwd_args_used == 0 && unused != NULL) { Py_INCREF(sipKwdArgs); *unused = sipKwdArgs; } else { PyObject *key, *value, *unused_dict = NULL; Py_ssize_t pos = 0; /* * Go through the keyword arguments to find any that were * duplicates of positional arguments. For the remaining * ones remember the unused ones if we are interested. */ while (PyDict_Next(sipKwdArgs, &pos, &key, &value)) { int a; if (!PyUnicode_Check(key)) { failure.reason = KeywordNotString; failure.detail_obj = key; Py_INCREF(key); break; } if (kwdlist != NULL) { /* Get the argument's index if it is one. */ for (a = 0; a < nr_args; ++a) { const char *name = kwdlist[a]; if (name == NULL) continue; if (PyUnicode_CompareWithASCIIString(key, name) == 0) break; } } else { a = nr_args; } if (a == nr_args) { /* * The name doesn't correspond to a keyword * argument. */ if (unused == NULL) { /* * It may correspond to a keyword argument of a * different overload. */ failure.reason = UnknownKeyword; failure.detail_obj = key; Py_INCREF(key); break; } /* * Add it to the dictionary of unused arguments * creating it if necessary. Note that if the * unused arguments are actually used by a later * overload then the parse will incorrectly * succeed. This should be picked up (perhaps with * a misleading exception) so long as the code that * handles the unused arguments checks that it can * handle them all. */ if (unused_dict == NULL && (*unused = unused_dict = PyDict_New()) == NULL) { failure.reason = Raised; break; } if (PyDict_SetItem(unused_dict, key, value) < 0) { failure.reason = Raised; break; } } else if (a < nr_pos_args - *selfargp) { /* * The argument has been given positionally and as * a keyword. */ failure.reason = Duplicate; failure.detail_obj = key; Py_INCREF(key); break; } } } } break; } /* Get the next argument. */ arg = NULL; failure.arg_nr = -1; failure.arg_name = NULL; if (argnr < nr_pos_args) { arg = PyTuple_GET_ITEM(sipArgs, argnr); failure.arg_nr = argnr + 1; } else if (nr_kwd_args != 0 && kwdlist != NULL) { const char *name = kwdlist[argnr - *selfargp]; if (name != NULL) { arg = PyDict_GetItemString(sipKwdArgs, name); if (arg != NULL) ++nr_kwd_args_used; failure.arg_name = name; } } ++argnr; ++nr_args; if (arg == NULL && compulsory) { if (ch == 'W') { /* * A variable number of arguments was allowed but none were * given. */ break; } /* An argument was required. */ failure.reason = TooFew; /* * Check if there were any unused keyword arguments so that we give * a (possibly) more accurate diagnostic in the case that a keyword * argument has been mis-spelled. */ if (unused == NULL && sipKwdArgs != NULL && nr_kwd_args_used != nr_kwd_args) { PyObject *key, *value; Py_ssize_t pos = 0; while (PyDict_Next(sipKwdArgs, &pos, &key, &value)) { int a; if (!PyUnicode_Check(key)) { failure.reason = KeywordNotString; failure.detail_obj = key; Py_INCREF(key); break; } if (kwdlist != NULL) { /* Get the argument's index if it is one. */ for (a = 0; a < nr_args; ++a) { const char *name = kwdlist[a]; if (name == NULL) continue; if (PyUnicode_CompareWithASCIIString(key, name) == 0) break; } } else { a = nr_args; } if (a == nr_args) { failure.reason = UnknownKeyword; failure.detail_obj = key; Py_INCREF(key); break; } } } break; } /* * Handle the format character even if we don't have an argument so * that we skip the right number of arguments. */ switch (ch) { case 'W': /* Ellipsis. */ break; case '@': { /* Implement /GetWrapper/. */ PyObject **p = va_arg(va, PyObject **); if (arg != NULL) *p = arg; /* Process the same argument next time round. */ --argnr; --nr_args; break; } case 's': { /* String from a Python bytes or None. */ const char **p = va_arg(va, const char **); if (arg != NULL && parseBytes_AsString(arg, p) < 0) { failure.reason = WrongType; failure.detail_obj = arg; Py_INCREF(arg); } break; } case 'A': { /* String from a Python string or None. */ va_arg(va, PyObject **); va_arg(va, const char **); fmt++; if (arg != NULL && check_encoded_string(arg) < 0) { failure.reason = WrongType; failure.detail_obj = arg; Py_INCREF(arg); } break; } case 'a': { /* Character from a Python string. */ va_arg(va, char *); fmt++; if (arg != NULL && check_encoded_string(arg) < 0) { failure.reason = WrongType; failure.detail_obj = arg; Py_INCREF(arg); } break; } case 'x': #if defined(HAVE_WCHAR_H) { /* Wide string or None. */ wchar_t **p = va_arg(va, wchar_t **); if (arg != NULL && parseWCharString(arg, p) < 0) { failure.reason = WrongType; failure.detail_obj = arg; Py_INCREF(arg); } break; } #else raiseNoWChar(); failure.reason = Raised; break; #endif case 'U': { /* Slot name or callable, return the name or callable. */ char **sname = va_arg(va, char **); PyObject **scall = va_arg(va, PyObject **); if (arg != NULL) { *sname = NULL; *scall = NULL; if (PyBytes_Check(arg)) { char *s = PyBytes_AS_STRING(arg); if (*s == '1' || *s == '2' || *s == '9') { *sname = s; } else { failure.reason = WrongType; failure.detail_obj = arg; Py_INCREF(arg); } } else if (PyCallable_Check(arg)) { *scall = arg; } else if (arg != Py_None) { failure.reason = WrongType; failure.detail_obj = arg; Py_INCREF(arg); } } break; } case 'S': { /* Slot name, return the name. */ char **p = va_arg(va, char **); if (arg != NULL) { if (PyBytes_Check(arg)) { char *s = PyBytes_AS_STRING(arg); if (*s == '1' || *s == '2' || *s == '9') { *p = s; } else { failure.reason = WrongType; failure.detail_obj = arg; Py_INCREF(arg); } } else { failure.reason = WrongType; failure.detail_obj = arg; Py_INCREF(arg); } } break; } case 'G': { /* Signal name, return the name. */ char **p = va_arg(va, char **); if (arg != NULL) { if (PyBytes_Check(arg)) { char *s = PyBytes_AS_STRING(arg); if (*s == '2' || *s == '9') { *p = s; } else { failure.reason = WrongType; failure.detail_obj = arg; Py_INCREF(arg); } } else { failure.reason = WrongType; failure.detail_obj = arg; Py_INCREF(arg); } } break; } case 'r': { /* * Sequence of mapped type instances. For ABI v12.10 and * earlier this is also used for class instances. */ const sipTypeDef *td; td = va_arg(va, const sipTypeDef *); va_arg(va, void **); va_arg(va, Py_ssize_t *); if (arg != NULL && !canConvertFromSequence(arg, td)) { failure.reason = WrongType; failure.detail_obj = arg; Py_INCREF(arg); } break; } case '>': { /* * Sequence or sip.array of class instances. This is only used * by ABI v12.11 and later. */ const sipTypeDef *td; td = va_arg(va, const sipTypeDef *); va_arg(va, void **); va_arg(va, Py_ssize_t *); va_arg(va, int *); if (arg != NULL && !sip_array_can_convert(arg, td) && !canConvertFromSequence(arg, td)) { failure.reason = WrongType; failure.detail_obj = arg; Py_INCREF(arg); } break; } case 'J': { /* Class or mapped type instance. */ char sub_fmt = *fmt++; const sipTypeDef *td; int flags = sub_fmt - '0'; int iflgs = 0; td = va_arg(va, const sipTypeDef *); va_arg(va, void **); if (flags & FMT_AP_DEREF) iflgs |= SIP_NOT_NONE; if (flags & FMT_AP_TRANSFER_THIS) va_arg(va, PyObject **); if (flags & FMT_AP_NO_CONVERTORS) iflgs |= SIP_NO_CONVERTORS; else va_arg(va, int *); if (arg != NULL && !sip_api_can_convert_to_type(arg, td, iflgs)) { failure.reason = WrongType; failure.detail_obj = arg; Py_INCREF(arg); } break; } case 'N': { /* Python object of given type or None. */ PyTypeObject *type = va_arg(va,PyTypeObject *); PyObject **p = va_arg(va,PyObject **); if (arg != NULL) { if (arg == Py_None || PyObject_TypeCheck(arg,type)) { *p = arg; } else { failure.reason = WrongType; failure.detail_obj = arg; Py_INCREF(arg); } } break; } case 'P': { /* Python object of any type with a sub-format. */ va_arg(va, PyObject **); /* Skip the sub-format. */ ++fmt; break; } case 'T': { /* Python object of given type. */ PyTypeObject *type = va_arg(va, PyTypeObject *); PyObject **p = va_arg(va, PyObject **); if (arg != NULL) { if (PyObject_TypeCheck(arg,type)) { *p = arg; } else { failure.reason = WrongType; failure.detail_obj = arg; Py_INCREF(arg); } } break; } case 'R': { /* Sub-class of QObject. */ PyObject **p = va_arg(va, PyObject **); if (arg != NULL) { if (isQObject(arg)) { *p = arg; } else { failure.reason = WrongType; failure.detail_obj = arg; Py_INCREF(arg); } } break; } case 'F': { /* Python callable object. */ PyObject **p = va_arg(va, PyObject **); if (arg != NULL) { if (PyCallable_Check(arg)) { *p = arg; } else { failure.reason = WrongType; failure.detail_obj = arg; Py_INCREF(arg); } } break; } case 'H': { /* Python callable object or None. */ PyObject **p = va_arg(va, PyObject **); if (arg != NULL) { if (arg == Py_None || PyCallable_Check(arg)) { *p = arg; } else { failure.reason = WrongType; failure.detail_obj = arg; Py_INCREF(arg); } } break; } case '!': { /* Python object that implements the buffer protocol. */ PyObject **p = va_arg(va, PyObject **); if (arg != NULL) { if (PyObject_CheckBuffer(arg)) { *p = arg; } else { failure.reason = WrongType; failure.detail_obj = arg; Py_INCREF(arg); } } break; } case '$': { /* * Python object that implements the buffer protocol or None. */ PyObject **p = va_arg(va, PyObject **); if (arg != NULL) { if (arg == Py_None || PyObject_CheckBuffer(arg)) { *p = arg; } else { failure.reason = WrongType; failure.detail_obj = arg; Py_INCREF(arg); } } break; } case 'q': { /* Qt receiver to connect. */ va_arg(va, char *); va_arg(va, void **); va_arg(va, const char **); if (arg != NULL && !isQObject(arg)) { failure.reason = WrongType; failure.detail_obj = arg; Py_INCREF(arg); } break; } case 'Q': { /* Qt receiver to disconnect. */ va_arg(va, char *); va_arg(va, void **); va_arg(va, const char **); if (arg != NULL && !isQObject(arg)) { failure.reason = WrongType; failure.detail_obj = arg; Py_INCREF(arg); } break; } case 'g': case 'y': { /* Python slot to connect. */ va_arg(va, char *); va_arg(va, void **); va_arg(va, const char **); if (arg != NULL && (sipQtSupport == NULL || !PyCallable_Check(arg))) { failure.reason = WrongType; failure.detail_obj = arg; Py_INCREF(arg); } break; } case 'Y': { /* Python slot to disconnect. */ va_arg(va, char *); va_arg(va, void **); va_arg(va, const char **); if (arg != NULL && (sipQtSupport == NULL || !PyCallable_Check(arg))) { failure.reason = WrongType; failure.detail_obj = arg; Py_INCREF(arg); } break; } case 'k': { /* Char array or None. */ const char **p = va_arg(va, const char **); Py_ssize_t *szp = va_arg(va, Py_ssize_t *); if (arg != NULL && parseBytes_AsCharArray(arg, p, szp) < 0) { failure.reason = WrongType; failure.detail_obj = arg; Py_INCREF(arg); } break; } case 'K': #if defined(HAVE_WCHAR_H) { /* Wide char array or None. */ wchar_t **p = va_arg(va, wchar_t **); Py_ssize_t *szp = va_arg(va, Py_ssize_t *); if (arg != NULL && parseWCharArray(arg, p, szp) < 0) { failure.reason = WrongType; failure.detail_obj = arg; Py_INCREF(arg); } break; } #else raiseNoWChar(); failure.reason = Raised; break #endif case 'c': { /* Character from a Python bytes. */ char *p = va_arg(va, char *); if (arg != NULL && parseBytes_AsChar(arg, p) < 0) { failure.reason = WrongType; failure.detail_obj = arg; Py_INCREF(arg); } break; } case 'w': #if defined(HAVE_WCHAR_H) { /* Wide character. */ wchar_t *p = va_arg(va, wchar_t *); if (arg != NULL && parseWChar(arg, p) < 0) { failure.reason = WrongType; failure.detail_obj = arg; Py_INCREF(arg); } break; } #else raiseNoWChar(); failure.reason = Raised; break #endif case 'b': { /* Bool. */ void *p = va_arg(va, void *); if (arg != NULL) { int v = sip_api_convert_to_bool(arg); if (v < 0) { failure.reason = WrongType; failure.detail_obj = arg; Py_INCREF(arg); } else { sipSetBool(p, v); } } break; } case 'E': { /* Named enum or integer. */ sipTypeDef *td = va_arg(va, sipTypeDef *); int *p = va_arg(va, int *); if (arg != NULL) { int v = sip_api_convert_to_enum(arg, td); if (PyErr_Occurred()) handle_failed_int_conversion(&failure, arg); else *p = v; } } break; case 'e': { /* Anonymous enum. */ int *p = va_arg(va, int *); if (arg != NULL) { int v = long_as_nonoverflow_int(arg); if (PyErr_Occurred()) handle_failed_int_conversion(&failure, arg); else *p = v; } } break; case 'i': { /* Integer. */ int *p = va_arg(va, int *); if (arg != NULL) { int v = sip_api_long_as_int(arg); if (PyErr_Occurred()) handle_failed_int_conversion(&failure, arg); else *p = v; } break; } case 'u': { /* Unsigned integer. */ unsigned *p = va_arg(va, unsigned *); if (arg != NULL) { unsigned v = sip_api_long_as_unsigned_int(arg); if (PyErr_Occurred()) handle_failed_int_conversion(&failure, arg); else *p = v; } break; } case '=': { /* size_t integer. */ size_t *p = va_arg(va, size_t *); if (arg != NULL) { size_t v = sip_api_long_as_size_t(arg); if (PyErr_Occurred()) handle_failed_int_conversion(&failure, arg); else *p = v; } break; } case 'I': { /* Char as an integer. */ char *p = va_arg(va, char *); if (arg != NULL) { char v = sip_api_long_as_char(arg); if (PyErr_Occurred()) handle_failed_int_conversion(&failure, arg); else *p = v; } break; } case 'L': { /* Signed char as an integer. */ signed char *p = va_arg(va, signed char *); if (arg != NULL) { signed char v = sip_api_long_as_signed_char(arg); if (PyErr_Occurred()) handle_failed_int_conversion(&failure, arg); else *p = v; } break; } case 'M': { /* Unsigned char as an integer. */ unsigned char *p = va_arg(va, unsigned char *); if (arg != NULL) { unsigned char v = sip_api_long_as_unsigned_char(arg); if (PyErr_Occurred()) handle_failed_int_conversion(&failure, arg); else *p = v; } break; } case 'h': { /* Short integer. */ signed short *p = va_arg(va, signed short *); if (arg != NULL) { signed short v = sip_api_long_as_short(arg); if (PyErr_Occurred()) handle_failed_int_conversion(&failure, arg); else *p = v; } break; } case 't': { /* Unsigned short integer. */ unsigned short *p = va_arg(va, unsigned short *); if (arg != NULL) { unsigned short v = sip_api_long_as_unsigned_short(arg); if (PyErr_Occurred()) handle_failed_int_conversion(&failure, arg); else *p = v; } break; } case 'l': { /* Long integer. */ long *p = va_arg(va, long *); if (arg != NULL) { long v = sip_api_long_as_long(arg); if (PyErr_Occurred()) handle_failed_int_conversion(&failure, arg); else *p = v; } break; } case 'm': { /* Unsigned long integer. */ unsigned long *p = va_arg(va, unsigned long *); if (arg != NULL) { unsigned long v = sip_api_long_as_unsigned_long(arg); if (PyErr_Occurred()) handle_failed_int_conversion(&failure, arg); else *p = v; } break; } case 'n': { /* Long long integer. */ #if defined(HAVE_LONG_LONG) PY_LONG_LONG *p = va_arg(va, PY_LONG_LONG *); #else long *p = va_arg(va, long *); #endif if (arg != NULL) { #if defined(HAVE_LONG_LONG) PY_LONG_LONG v = sip_api_long_as_long_long(arg); #else long v = sip_api_long_as_long(arg); #endif if (PyErr_Occurred()) handle_failed_int_conversion(&failure, arg); else *p = v; } break; } case 'o': { /* Unsigned long long integer. */ #if defined(HAVE_LONG_LONG) unsigned PY_LONG_LONG *p = va_arg(va, unsigned PY_LONG_LONG *); #else unsigned long *p = va_arg(va, unsigned long *); #endif if (arg != NULL) { #if defined(HAVE_LONG_LONG) unsigned PY_LONG_LONG v = sip_api_long_as_unsigned_long_long(arg); #else unsigned long v = sip_api_long_as_unsigned_long(arg); #endif if (PyErr_Occurred()) handle_failed_int_conversion(&failure, arg); else *p = v; } break; } case 'f': { /* Float. */ float *p = va_arg(va, float *); if (arg != NULL) { double v = PyFloat_AsDouble(arg); if (PyErr_Occurred()) { failure.reason = WrongType; failure.detail_obj = arg; Py_INCREF(arg); } else { *p = (float)v; } } break; } case 'X': { /* Constrained types. */ char sub_fmt = *fmt++; if (sub_fmt == 'E') { /* Named enum. */ sipTypeDef *td = va_arg(va, sipTypeDef *); int *p = va_arg(va, int *); if (arg != NULL) { *p = convert_to_enum(arg, td, FALSE); if (PyErr_Occurred()) handle_failed_int_conversion(&failure, arg); } } else { void *p = va_arg(va, void *); if (arg != NULL) { switch (sub_fmt) { case 'b': { /* Boolean. */ if (PyBool_Check(arg)) { sipSetBool(p, (arg == Py_True)); } else { failure.reason = WrongType; failure.detail_obj = arg; Py_INCREF(arg); } break; } case 'd': { /* Double float. */ if (PyFloat_Check(arg)) { *(double *)p = PyFloat_AS_DOUBLE(arg); } else { failure.reason = WrongType; failure.detail_obj = arg; Py_INCREF(arg); } break; } case 'f': { /* Float. */ if (PyFloat_Check(arg)) { *(float *)p = (float)PyFloat_AS_DOUBLE(arg); } else { failure.reason = WrongType; failure.detail_obj = arg; Py_INCREF(arg); } break; } case 'i': { /* Integer. */ if (PyLong_Check(arg)) { *(int *)p = sip_api_long_as_int(arg); if (PyErr_Occurred()) handle_failed_int_conversion(&failure, arg); } else { failure.reason = WrongType; failure.detail_obj = arg; Py_INCREF(arg); } break; } } } } break; } case 'd': { /* Double float. */ double *p = va_arg(va,double *); if (arg != NULL) { double v = PyFloat_AsDouble(arg); if (PyErr_Occurred()) { failure.reason = WrongType; failure.detail_obj = arg; Py_INCREF(arg); } else { *p = v; } } break; } case 'v': { /* Void pointer. */ void **p = va_arg(va, void **); if (arg != NULL) { void *v = sip_api_convert_to_void_ptr(arg); if (PyErr_Occurred()) { failure.reason = WrongType; failure.detail_obj = arg; Py_INCREF(arg); } else { *p = v; } } break; } case 'z': { /* Void pointer as a capsule. */ const char *name = va_arg(va, const char *); void **p = va_arg(va, void **); if (arg == Py_None) { *p = NULL; } else if (arg != NULL) { void *v = PyCapsule_GetPointer(arg, name); if (PyErr_Occurred()) { failure.reason = WrongType; failure.detail_obj = arg; Py_INCREF(arg); } else { *p = v; } } break; } } if ((failure.reason == Ok || failure.reason == Overflow) && ch == 'W') { /* An ellipsis matches everything and ends the parse. */ break; } } /* Handle parse failures appropriately. */ if (failure.reason == Ok) return TRUE; if (failure.reason == Overflow) { /* * We have successfully parsed the signature but one of the arguments * has been found to overflow. Raise an appropriate exception and * ensure we don't parse any subsequent overloads. */ if (failure.overflow_arg_nr >= 0) { PyErr_Format(PyExc_OverflowError, "argument %d overflowed: %S", failure.overflow_arg_nr, failure.detail_obj); } else { PyErr_Format(PyExc_OverflowError, "argument '%s' overflowed: %S", failure.overflow_arg_name, failure.detail_obj); } /* The overflow exception has now been raised. */ failure.reason = Raised; } if (failure.reason != Raised) add_failure(parseErrp, &failure); if (failure.reason == Raised) { Py_XDECREF(failure.detail_obj); /* * Discard any previous errors and flag that the exception we want the * user to see has been raised. */ Py_XDECREF(*parseErrp); *parseErrp = Py_None; Py_INCREF(Py_None); } return FALSE; } /* * Called after a failed conversion of an integer. */ static void handle_failed_int_conversion(sipParseFailure *pf, PyObject *arg) { PyObject *xtype, *xvalue, *xtb; assert(pf->reason == Ok || pf->reason == Overflow); PyErr_Fetch(&xtype, &xvalue, &xtb); if (PyErr_GivenExceptionMatches(xtype, PyExc_OverflowError) && xvalue != NULL) { /* Remove any previous overflow exception. */ Py_XDECREF(pf->detail_obj); pf->reason = Overflow; pf->overflow_arg_nr = pf->arg_nr; pf->overflow_arg_name = pf->arg_name; pf->detail_obj = xvalue; Py_INCREF(xvalue); } else { pf->reason = WrongType; pf->detail_obj = arg; Py_INCREF(arg); } PyErr_Restore(xtype, xvalue, xtb); } /* * Second pass of the argument parse, converting the remaining ones that might * have side effects. Return TRUE if there was no error. */ static int parsePass2(sipSimpleWrapper *self, int selfarg, PyObject *sipArgs, PyObject *sipKwdArgs, const char **kwdlist, const char *fmt, va_list va) { int a, ok; Py_ssize_t nr_pos_args; /* Handle the converions of "self" first. */ switch (*fmt++) { case '#': va_arg(va, PyObject *); break; case 'B': { /* * The address of a C++ instance when calling one of its public * methods. */ const sipTypeDef *td; void **p; *va_arg(va, PyObject **) = (PyObject *)self; td = va_arg(va, const sipTypeDef *); p = va_arg(va, void **); if ((*p = sip_api_get_cpp_ptr(self, td)) == NULL) return FALSE; break; } case 'p': { /* * The address of a C++ instance when calling one of its protected * methods. */ const sipTypeDef *td; void **p; *va_arg(va, PyObject **) = (PyObject *)self; td = va_arg(va, const sipTypeDef *); p = va_arg(va, void **); if ((*p = getComplexCppPtr(self, td)) == NULL) return FALSE; break; } case 'C': va_arg(va, PyObject *); break; default: --fmt; } ok = TRUE; nr_pos_args = PyTuple_GET_SIZE(sipArgs); for (a = (selfarg ? 1 : 0); *fmt != '\0' && *fmt != 'W' && ok; ++a) { char ch; PyObject *arg; /* Skip the optional character. */ if ((ch = *fmt++) == '|') ch = *fmt++; /* Get the next argument. */ arg = NULL; if (a < nr_pos_args) { arg = PyTuple_GET_ITEM(sipArgs, a); } else if (sipKwdArgs != NULL) { const char *name = kwdlist[a - selfarg]; if (name != NULL) arg = PyDict_GetItemString(sipKwdArgs, name); } /* * Do the outstanding conversions. For most types it has already been * done, so we are just skipping the parameters. */ switch (ch) { case '@': /* Implement /GetWrapper/. */ va_arg(va, PyObject **); /* Process the same argument next time round. */ --a; break; case 'q': { /* Qt receiver to connect. */ char *sig = va_arg(va, char *); void **rx = va_arg(va, void **); const char **slot = va_arg(va, const char **); if (arg != NULL) { *rx = sip_api_convert_rx((sipWrapper *)self, sig, arg, *slot, slot, 0); if (*rx == NULL) return FALSE; } break; } case 'Q': { /* Qt receiver to disconnect. */ char *sig = va_arg(va, char *); void **rx = va_arg(va, void **); const char **slot = va_arg(va, const char **); if (arg != NULL) *rx = sipGetRx(self, sig, arg, *slot, slot); break; } case 'g': { /* Python single shot slot to connect. */ char *sig = va_arg(va, char *); void **rx = va_arg(va, void **); const char **slot = va_arg(va, const char **); if (arg != NULL) { *rx = sip_api_convert_rx((sipWrapper *)self, sig, arg, NULL, slot, SIP_SINGLE_SHOT); if (*rx == NULL) return FALSE; } break; } case 'y': { /* Python slot to connect. */ char *sig = va_arg(va, char *); void **rx = va_arg(va, void **); const char **slot = va_arg(va, const char **); if (arg != NULL) { *rx = sip_api_convert_rx((sipWrapper *)self, sig, arg, NULL, slot, 0); if (*rx == NULL) return FALSE; } break; } case 'Y': { /* Python slot to disconnect. */ char *sig = va_arg(va, char *); void **rx = va_arg(va, void **); const char **slot = va_arg(va, const char **); if (arg != NULL) *rx = sipGetRx(self, sig, arg, NULL, slot); break; } case 'r': { /* Sequence of mapped type instances. */ const sipTypeDef *td; void **array; Py_ssize_t *nr_elem; td = va_arg(va, const sipTypeDef *); array = va_arg(va, void **); nr_elem = va_arg(va, Py_ssize_t *); if (arg != NULL && !convertFromSequence(arg, td, array, nr_elem)) return FALSE; break; } case '>': { /* Sequence or sip.array of class instances. */ const sipTypeDef *td; void **array; Py_ssize_t *nr_elem; int *is_temp; td = va_arg(va, const sipTypeDef *); array = va_arg(va, void **); nr_elem = va_arg(va, Py_ssize_t *); is_temp = va_arg(va, int *); if (arg != NULL) { if (sip_array_can_convert(arg, td)) { sip_array_convert(arg, array, nr_elem); *is_temp = FALSE; } else if (convertFromSequence(arg, td, array, nr_elem)) { /* * Note that this will leak if there is a subsequent * error. */ *is_temp = TRUE; } else { return FALSE; } } break; } case 'J': { /* Class or mapped type instance. */ int flags = *fmt++ - '0'; const sipTypeDef *td; void **p; int iflgs = 0; int *state; PyObject *xfer, **owner; td = va_arg(va, const sipTypeDef *); p = va_arg(va, void **); if (flags & FMT_AP_TRANSFER) xfer = (self ? (PyObject *)self : arg); else if (flags & FMT_AP_TRANSFER_BACK) xfer = Py_None; else xfer = NULL; if (flags & FMT_AP_DEREF) iflgs |= SIP_NOT_NONE; if (flags & FMT_AP_TRANSFER_THIS) owner = va_arg(va, PyObject **); else owner = NULL; if (flags & FMT_AP_NO_CONVERTORS) { iflgs |= SIP_NO_CONVERTORS; state = NULL; } else { state = va_arg(va, int *); } if (arg != NULL) { int iserr = FALSE; *p = sip_api_convert_to_type(arg, td, xfer, iflgs, state, &iserr); if (iserr) return FALSE; if (owner != NULL && *p != NULL) *owner = arg; } break; } case 'P': { /* Python object of any type with a sub-format. */ PyObject **p = va_arg(va, PyObject **); int flags = *fmt++ - '0'; if (arg != NULL) { if (flags & FMT_AP_TRANSFER) { Py_XINCREF(arg); } else if (flags & FMT_AP_TRANSFER_BACK) { Py_XDECREF(arg); } *p = arg; } break; } case 'X': { /* Constrained types. */ if (*fmt++ == 'E') va_arg(va, void *); va_arg(va, void *); break; } case 'A': { /* String from a Python string or None. */ PyObject **keep = va_arg(va, PyObject **); const char **p = va_arg(va, const char **); char sub_fmt = *fmt++; if (arg != NULL) { PyObject *s = NULL; switch (sub_fmt) { case 'A': s = parseString_AsASCIIString(arg, p); break; case 'L': s = parseString_AsLatin1String(arg, p); break; case '8': s = parseString_AsUTF8String(arg, p); break; } if (s == NULL) return FALSE; *keep = s; } break; } case 'a': { /* Character from a Python string. */ char *p = va_arg(va, char *); char sub_fmt = *fmt++; if (arg != NULL) { int enc; switch (sub_fmt) { case 'A': enc = parseString_AsASCIIChar(arg, p); break; case 'L': enc = parseString_AsLatin1Char(arg, p); break; case '8': enc = parseString_AsUTF8Char(arg, p); break; } if (enc < 0) return FALSE; } break; } /* * Every other argument is a pointer and only differ in how many there * are. */ case 'N': case 'T': case 'k': case 'K': case 'U': case 'E': va_arg(va, void *); /* Drop through. */ default: va_arg(va, void *); } } /* Handle any ellipsis argument. */ if (*fmt == 'W') { PyObject *al; int da = 0; /* Create a tuple for any remaining arguments. */ if ((al = PyTuple_New(nr_pos_args - a)) == NULL) return FALSE; while (a < nr_pos_args) { PyObject *arg = PyTuple_GET_ITEM(sipArgs, a); /* Add the remaining argument to the tuple. */ Py_INCREF(arg); PyTuple_SET_ITEM(al, da, arg); ++a; ++da; } /* Return the tuple. */ *va_arg(va, PyObject **) = al; } return TRUE; } /* * Return TRUE if an object is a QObject. */ static int isQObject(PyObject *obj) { return (sipQtSupport != NULL && PyObject_TypeCheck(obj, sipTypeAsPyTypeObject(sipQObjectType))); } /* * See if a Python object is a sequence of a particular type. */ static int canConvertFromSequence(PyObject *seq, const sipTypeDef *td) { Py_ssize_t i, size = PySequence_Size(seq); if (size < 0) return FALSE; /* * Check the type of each element. Note that this is inconsistent with how * similiar situations are handled elsewhere. We should instead just check * we have an iterator and assume (until the second pass) that the type is * correct. */ for (i = 0; i < size; ++i) { int ok; PyObject *val_obj; if ((val_obj = PySequence_GetItem(seq, i)) == NULL) return FALSE; ok = sip_api_can_convert_to_type(val_obj, td, SIP_NO_CONVERTORS|SIP_NOT_NONE); Py_DECREF(val_obj); if (!ok) return FALSE; } return TRUE; } /* * Convert a Python sequence to an array that has already "passed" * canConvertFromSequence(). Return TRUE if the conversion was successful. */ static int convertFromSequence(PyObject *seq, const sipTypeDef *td, void **array, Py_ssize_t *nr_elem) { int iserr = 0; Py_ssize_t i, size = PySequence_Size(seq); sipArrayFunc array_helper; sipAssignFunc assign_helper; void *array_mem; /* Get the type's helpers. */ if (sipTypeIsMapped(td)) { array_helper = ((const sipMappedTypeDef *)td)->mtd_array; assign_helper = ((const sipMappedTypeDef *)td)->mtd_assign; } else { array_helper = ((const sipClassTypeDef *)td)->ctd_array; assign_helper = ((const sipClassTypeDef *)td)->ctd_assign; } assert(array_helper != NULL); assert(assign_helper != NULL); /* * Create the memory for the array of values. Note that this will leak if * there is an error. */ array_mem = array_helper(size); for (i = 0; i < size; ++i) { PyObject *val_obj; void *val; if ((val_obj = PySequence_GetItem(seq, i)) == NULL) return FALSE; val = sip_api_convert_to_type(val_obj, td, NULL, SIP_NO_CONVERTORS|SIP_NOT_NONE, NULL, &iserr); Py_DECREF(val_obj); if (iserr) return FALSE; assign_helper(array_mem, i, val); } *array = array_mem; *nr_elem = size; return TRUE; } /* * Convert an array of a type to a Python sequence. */ static PyObject *convertToSequence(void *array, Py_ssize_t nr_elem, const sipTypeDef *td) { Py_ssize_t i; PyObject *seq; sipCopyFunc copy_helper; /* Get the type's copy helper. */ if (sipTypeIsMapped(td)) copy_helper = ((const sipMappedTypeDef *)td)->mtd_copy; else copy_helper = ((const sipClassTypeDef *)td)->ctd_copy; assert(copy_helper != NULL); if ((seq = PyTuple_New(nr_elem)) == NULL) return NULL; for (i = 0; i < nr_elem; ++i) { void *el = copy_helper(array, i); PyObject *el_obj = sip_api_convert_from_new_type(el, td, NULL); if (el_obj == NULL) { release(el, td, 0); Py_DECREF(seq); } PyTuple_SET_ITEM(seq, i, el_obj); } return seq; } /* * Perform housekeeping after a C++ instance has been destroyed. */ void sip_api_instance_destroyed(sipSimpleWrapper *sw) { sip_api_instance_destroyed_ex(&sw); } /* * Carry out actions common to all dtors. */ static void sip_api_instance_destroyed_ex(sipSimpleWrapper **sipSelfp) { /* If there is no interpreter just to the minimum and get out. */ if (sipInterpreter == NULL) { *sipSelfp = NULL; return; } SIP_BLOCK_THREADS sipSimpleWrapper *sipSelf = *sipSelfp; if (sipSelf != NULL) { PyObject *xtype, *xvalue, *xtb; /* We may be tidying up after an exception so preserve it. */ PyErr_Fetch(&xtype, &xvalue, &xtb); callPyDtor(sipSelf); PyErr_Restore(xtype, xvalue, xtb); sipOMRemoveObject(&cppPyMap, sipSelf); /* * This no longer points to anything useful. Actually it might do as * the partialy destroyed C++ instance may still be trying to invoke * reimplemented virtuals. */ clear_access_func(sipSelf); /* * If C/C++ has a reference (and therefore no parent) then remove it. * Otherwise remove the object from any parent. */ if (sipCppHasRef(sipSelf)) { sipResetCppHasRef(sipSelf); Py_DECREF(sipSelf); } else if (PyObject_TypeCheck((PyObject *)sipSelf, (PyTypeObject *)&sipWrapper_Type)) { removeFromParent((sipWrapper *)sipSelf); } /* * Normally this is done in the generated dealloc function. However * this is only called if the pointer/access function has not been * reset (which it has). It acts as a guard to prevent any further * invocations of reimplemented virtuals. */ *sipSelfp = NULL; } SIP_UNBLOCK_THREADS } /* * Clear any access function so that sip_api_get_address() will always return a * NULL pointer. */ static void clear_access_func(sipSimpleWrapper *sw) { if (sw->access_func != NULL) { sw->access_func(sw, ReleaseGuard); sw->access_func = NULL; } sw->data = NULL; } /* * Call self.__dtor__() if it is implemented. */ static void callPyDtor(sipSimpleWrapper *self) { sip_gilstate_t sipGILState; char pymc = 0; PyObject *meth; meth = sip_api_is_py_method_12_8(&sipGILState, &pymc, &self, NULL, "__dtor__"); if (meth != NULL) { PyObject *res = sip_api_call_method(0, meth, "", NULL); Py_DECREF(meth); /* Discard any result. */ Py_XDECREF(res); /* Handle any error the best we can. */ if (PyErr_Occurred()) PyErr_Print(); SIP_RELEASE_GIL(sipGILState); } } /* * Add a wrapper to it's parent owner. The wrapper must not currently have a * parent and, therefore, no siblings. */ static void addToParent(sipWrapper *self, sipWrapper *owner) { if (owner->first_child != NULL) { self->sibling_next = owner->first_child; owner->first_child->sibling_prev = self; } owner->first_child = self; self->parent = owner; /* * The owner holds a real reference so that the cyclic garbage collector * works properly. */ Py_INCREF((sipSimpleWrapper *)self); } /* * Remove a wrapper from it's parent if it has one. */ static void removeFromParent(sipWrapper *self) { if (self->parent != NULL) { if (self->parent->first_child == self) self->parent->first_child = self->sibling_next; if (self->sibling_next != NULL) self->sibling_next->sibling_prev = self->sibling_prev; if (self->sibling_prev != NULL) self->sibling_prev->sibling_next = self->sibling_next; self->parent = NULL; self->sibling_next = NULL; self->sibling_prev = NULL; /* * We must do this last, after all the pointers are correct, because * this is used by the clear slot. */ Py_DECREF((sipSimpleWrapper *)self); } } /* * Detach and children of a parent. */ static void detachChildren(sipWrapper *self) { while (self->first_child != NULL) removeFromParent(self->first_child); } /* * Convert a sequence index. Return the index or a negative value if there was * an error. */ static Py_ssize_t sip_api_convert_from_sequence_index(Py_ssize_t idx, Py_ssize_t len) { /* Negative indices start from the other end. */ if (idx < 0) idx = len + idx; if (idx < 0 || idx >= len) { PyErr_Format(PyExc_IndexError, "sequence index out of range"); return -1; } return idx; } /* * Return a tuple of the base class of a type that has no explicit super-type. */ static PyObject *getDefaultBase(void) { static PyObject *default_base = NULL; /* Only do this once. */ if (default_base == NULL) { if ((default_base = PyTuple_Pack(1, (PyObject *)&sipWrapper_Type)) == NULL) return NULL; } Py_INCREF(default_base); return default_base; } /* * Return a tuple of the base class of a simple type that has no explicit * super-type. */ static PyObject *getDefaultSimpleBase(void) { static PyObject *default_simple_base = NULL; /* Only do this once. */ if (default_simple_base == NULL) { if ((default_simple_base = PyTuple_Pack(1, (PyObject *)&sipSimpleWrapper_Type)) == NULL) return NULL; } Py_INCREF(default_simple_base); return default_simple_base; } /* * Return the dictionary of a type. */ static PyObject *getScopeDict(sipTypeDef *td, PyObject *mod_dict, sipExportedModuleDef *client) { /* * Initialise the scoping type if necessary. It will always be in the * same module if it needs doing. */ if (sipTypeIsMapped(td)) { if (createMappedType(client, (sipMappedTypeDef *)td, mod_dict) < 0) return NULL; /* Check that the mapped type can act as a container. */ assert(sipTypeAsPyTypeObject(td) != NULL); } else { if (createClassType(client, (sipClassTypeDef *)td, mod_dict) < 0) return NULL; } return (sipTypeAsPyTypeObject(td))->tp_dict; } /* * Create a container type and return a borrowed reference to it. */ static PyObject *createContainerType(sipContainerDef *cod, sipTypeDef *td, PyObject *bases, PyObject *metatype, PyObject *mod_dict, PyObject *type_dict, sipExportedModuleDef *client) { PyObject *py_type, *scope_dict, *name, *args; sipTypeDef *scope_td; /* Get the dictionary to place the type in. */ if (cod->cod_scope.sc_flag) { scope_td = NULL; scope_dict = mod_dict; } else { scope_td = getGeneratedType(&cod->cod_scope, client); scope_dict = getScopeDict(scope_td, mod_dict, client); if (scope_dict == NULL) goto reterr; } /* Create an object corresponding to the type name. */ if ((name = PyUnicode_FromString(sipPyNameOfContainer(cod, td))) == NULL) goto reterr; /* Create the type by calling the metatype. */ if ((args = PyTuple_Pack(3, name, bases, type_dict)) == NULL) goto relname; /* Pass the type via the back door. */ assert(currentType == NULL); currentType = td; py_type = PyObject_Call(metatype, args, NULL); currentType = NULL; if (py_type == NULL) goto relargs; /* Fix __qualname__ if there is a scope. */ if (scope_td != NULL) { PyHeapTypeObject *ht; PyObject *qualname = get_qualname(scope_td, name); if (qualname == NULL) goto reltype; ht = (PyHeapTypeObject *)py_type; Py_CLEAR(ht->ht_qualname); ht->ht_qualname = qualname; } /* Add the type to the "parent" dictionary. */ if (PyDict_SetItem(scope_dict, name, py_type) < 0) goto reltype; Py_DECREF(args); Py_DECREF(name); return py_type; /* Unwind on error. */ reltype: Py_DECREF(py_type); relargs: Py_DECREF(args); relname: Py_DECREF(name); reterr: return NULL; } /* * Create a single class type object. */ static int createClassType(sipExportedModuleDef *client, sipClassTypeDef *ctd, PyObject *mod_dict) { PyObject *bases, *metatype, *py_type, *type_dict; sipEncodedTypeDef *sup; int i; /* Handle the trivial case where we have already been initialised. */ if (ctd->ctd_base.td_module != NULL) return 0; /* Set this up now to gain access to the string pool. */ ctd->ctd_base.td_module = client; /* Create the tuple of super-types. */ if ((sup = ctd->ctd_supers) == NULL) { if (ctd->ctd_supertype < 0) { bases = (sipTypeIsNamespace(&ctd->ctd_base) ? getDefaultSimpleBase() : getDefaultBase()); } else { PyObject *supertype; const char *supertype_name = sipNameFromPool(client, ctd->ctd_supertype); if ((supertype = findPyType(supertype_name)) == NULL) goto reterr; bases = PyTuple_Pack(1, supertype); } if (bases == NULL) goto reterr; } else { int nrsupers = 0; do ++nrsupers; while (!sup++->sc_flag); if ((bases = PyTuple_New(nrsupers)) == NULL) goto reterr; for (sup = ctd->ctd_supers, i = 0; i < nrsupers; ++i, ++sup) { PyObject *st; sipTypeDef *sup_td = getGeneratedType(sup, client); /* * Initialise the super-class if necessary. It will always be in * the same module if it needs doing. */ if (createClassType(client, (sipClassTypeDef *)sup_td, mod_dict) < 0) goto relbases; st = (PyObject *)sipTypeAsPyTypeObject(sup_td); Py_INCREF(st); PyTuple_SET_ITEM(bases, i, st); /* * Inherit any garbage collector code rather than look for it each * time it is needed. */ if (ctd->ctd_traverse == NULL) ctd->ctd_traverse = ((sipClassTypeDef *)sup_td)->ctd_traverse; if (ctd->ctd_clear == NULL) ctd->ctd_clear = ((sipClassTypeDef *)sup_td)->ctd_clear; } } /* * Use the explicit meta-type if there is one, otherwise use the meta-type * of the first super-type. */ if (ctd->ctd_metatype >= 0) { const char *metatype_name = sipNameFromPool(client, ctd->ctd_metatype); if ((metatype = findPyType(metatype_name)) == NULL) goto relbases; } else metatype = (PyObject *)Py_TYPE(PyTuple_GET_ITEM(bases, 0)); /* Create the type dictionary and populate it with any non-lazy methods. */ if ((type_dict = createTypeDict(client)) == NULL) goto relbases; if (sipTypeHasNonlazyMethod(&ctd->ctd_base)) { PyMethodDef *pmd = ctd->ctd_container.cod_methods; for (i = 0; i < ctd->ctd_container.cod_nrmethods; ++i) { if (isNonlazyMethod(pmd) && addMethod(type_dict, pmd) < 0) goto reldict; ++pmd; } } if ((py_type = createContainerType(&ctd->ctd_container, (sipTypeDef *)ctd, bases, metatype, mod_dict, type_dict, client)) == NULL) goto reldict; if (ctd->ctd_pyslots != NULL) fix_slots((PyTypeObject *)py_type, ctd->ctd_pyslots); /* Handle the pickle function. */ if (ctd->ctd_pickle != NULL) { static PyMethodDef md = { "_pickle_type", pickle_type, METH_NOARGS, NULL }; if (setReduce((PyTypeObject *)py_type, &md) < 0) goto reltype; } /* We can now release our references. */ Py_DECREF(bases); Py_DECREF(type_dict); return 0; /* Unwind after an error. */ reltype: Py_DECREF(py_type); reldict: Py_DECREF(type_dict); relbases: Py_DECREF(bases); reterr: ctd->ctd_base.td_module = NULL; return -1; } /* * Create a single mapped type object. */ static int createMappedType(sipExportedModuleDef *client, sipMappedTypeDef *mtd, PyObject *mod_dict) { PyObject *bases, *type_dict; /* Handle the trivial case where we have already been initialised. */ if (mtd->mtd_base.td_module != NULL) return 0; /* Set this up now to gain access to the string pool. */ mtd->mtd_base.td_module = client; /* Create the tuple of super-types. */ if ((bases = getDefaultBase()) == NULL) goto reterr; /* Create the type dictionary. */ if ((type_dict = createTypeDict(client)) == NULL) goto relbases; if (createContainerType(&mtd->mtd_container, (sipTypeDef *)mtd, bases, (PyObject *)&sipWrapperType_Type, mod_dict, type_dict, client) == NULL) goto reldict; /* We can now release our references. */ Py_DECREF(bases); Py_DECREF(type_dict); return 0; /* Unwind after an error. */ reldict: Py_DECREF(type_dict); relbases: Py_DECREF(bases); reterr: mtd->mtd_base.td_module = NULL; return -1; } /* * Return the module definition for a named module. */ static sipExportedModuleDef *getModule(PyObject *mname_obj) { PyObject *mod; sipExportedModuleDef *em; /* Make sure the module is imported. */ if ((mod = PyImport_Import(mname_obj)) == NULL) return NULL; /* Find the module definition. */ for (em = moduleList; em != NULL; em = em->em_next) if (PyUnicode_Compare(mname_obj, em->em_nameobj) == 0) break; Py_DECREF(mod); if (em == NULL) PyErr_Format(PyExc_SystemError, "unable to find to find module: %U", mname_obj); return em; } /* * The type unpickler. */ static PyObject *unpickle_type(PyObject *obj, PyObject *args) { PyObject *mname_obj, *init_args; const char *tname; sipExportedModuleDef *em; int i; (void)obj; if (!PyArg_ParseTuple(args, "UsO!:_unpickle_type", &mname_obj, &tname, &PyTuple_Type, &init_args)) return NULL; /* Get the module definition. */ if ((em = getModule(mname_obj)) == NULL) return NULL; /* Find the class type object. */ for (i = 0; i < em->em_nrtypes; ++i) { sipTypeDef *td = em->em_types[i]; if (td != NULL && !sipTypeIsStub(td) && sipTypeIsClass(td)) { const char *pyname = sipPyNameOfContainer( &((sipClassTypeDef *)td)->ctd_container, td); if (strcmp(pyname, tname) == 0) return PyObject_CallObject((PyObject *)sipTypeAsPyTypeObject(td), init_args); } } PyErr_Format(PyExc_SystemError, "unable to find to find type: %s", tname); return NULL; } /* * The type pickler. */ static PyObject *pickle_type(PyObject *obj, PyObject *args) { sipExportedModuleDef *em; (void)args; /* Find the type definition and defining module. */ for (em = moduleList; em != NULL; em = em->em_next) { int i; for (i = 0; i < em->em_nrtypes; ++i) { sipTypeDef *td = em->em_types[i]; if (td != NULL && !sipTypeIsStub(td) && sipTypeIsClass(td)) if (sipTypeAsPyTypeObject(td) == Py_TYPE(obj)) { PyObject *init_args; sipClassTypeDef *ctd = (sipClassTypeDef *)td; const char *pyname = sipPyNameOfContainer(&ctd->ctd_container, td); /* * Ask the handwritten pickle code for the tuple of * arguments that will recreate the object. */ init_args = ctd->ctd_pickle(sip_api_get_cpp_ptr((sipSimpleWrapper *)obj, NULL)); if (init_args == NULL) return NULL; if (!PyTuple_Check(init_args)) { PyErr_Format(PyExc_TypeError, "%%PickleCode for type %s.%s did not return a tuple", sipNameOfModule(em), pyname); return NULL; } return Py_BuildValue("O(OsN)", type_unpickler, em->em_nameobj, pyname, init_args); } } } /* We should never get here. */ PyErr_Format(PyExc_SystemError, "attempt to pickle unknown type '%s'", Py_TYPE(obj)->tp_name); return NULL; } /* * The enum unpickler. */ static PyObject *unpickle_enum(PyObject *obj, PyObject *args) { PyObject *mname_obj, *evalue_obj; const char *ename; sipExportedModuleDef *em; int i; (void)obj; if (!PyArg_ParseTuple(args, "UsO:_unpickle_enum", &mname_obj, &ename, &evalue_obj)) return NULL; /* Get the module definition. */ if ((em = getModule(mname_obj)) == NULL) return NULL; /* Find the enum type object. */ for (i = 0; i < em->em_nrtypes; ++i) { sipTypeDef *td = em->em_types[i]; if (td != NULL && !sipTypeIsStub(td) && sipTypeIsEnum(td)) if (strcmp(sipPyNameOfEnum((sipEnumTypeDef *)td), ename) == 0) return PyObject_CallFunctionObjArgs((PyObject *)sipTypeAsPyTypeObject(td), evalue_obj, NULL); } PyErr_Format(PyExc_SystemError, "unable to find to find enum: %s", ename); return NULL; } /* * The enum pickler. */ static PyObject *pickle_enum(PyObject *obj, PyObject *args) { sipTypeDef *td = ((sipEnumTypeObject *)Py_TYPE(obj))->type; (void)args; return Py_BuildValue("O(Osi)", enum_unpickler, td->td_module->em_nameobj, sipPyNameOfEnum((sipEnumTypeDef *)td), (int)PyLong_AS_LONG(obj)); } /* * Set the __reduce__method for a type. */ static int setReduce(PyTypeObject *type, PyMethodDef *pickler) { static PyObject *rstr = NULL; PyObject *descr; int rc; if (objectify("__reduce__", &rstr) < 0) return -1; /* Create the method descripter. */ if ((descr = PyDescr_NewMethod(type, pickler)) == NULL) return -1; /* * Save the method. Note that we don't use PyObject_SetAttr() as we want * to bypass any lazy attribute loading (which may not be safe yet). */ rc = PyType_Type.tp_setattro((PyObject *)type, rstr, descr); Py_DECREF(descr); return rc; } /* * Create an enum object. */ static int createEnum(sipExportedModuleDef *client, sipEnumTypeDef *etd, int enum_nr, PyObject *mod_dict) { int rc; PyObject *name, *dict, *enum_obj; etd->etd_base.td_module = client; /* Get the dictionary into which the type will be placed. */ if (etd->etd_scope < 0) dict = mod_dict; else if ((dict = getScopeDict(client->em_types[etd->etd_scope], mod_dict, client)) == NULL) return -1; /* Create an object corresponding to the type name. */ if ((name = PyUnicode_FromString(sipPyNameOfEnum(etd))) == NULL) return -1; /* Create the enum. */ if (sipTypeIsEnum(&etd->etd_base)) enum_obj = createUnscopedEnum(client, etd, name); else enum_obj = createScopedEnum(client, etd, enum_nr, name); if (enum_obj == NULL) { Py_DECREF(name); return -1; } /* Add the enum to the "parent" dictionary. */ rc = PyDict_SetItem(dict, name, enum_obj); /* We can now release our remaining references. */ Py_DECREF(name); Py_DECREF(enum_obj); return rc; } /* * Create an unscoped enum. */ static PyObject *createUnscopedEnum(sipExportedModuleDef *client, sipEnumTypeDef *etd, PyObject *name) { static PyObject *bases = NULL; PyObject *type_dict, *args; sipEnumTypeObject *eto; /* Create the base type tuple if it hasn't already been done. */ if (bases == NULL) if ((bases = PyTuple_Pack(1, (PyObject *)&PyLong_Type)) == NULL) return NULL; /* Create the type dictionary. */ if ((type_dict = createTypeDict(client)) == NULL) return NULL; /* Create the type by calling the metatype. */ args = PyTuple_Pack(3, name, bases, type_dict); Py_DECREF(type_dict); if (args == NULL) return NULL; /* Pass the type via the back door. */ assert(currentType == NULL); currentType = &etd->etd_base; eto = (sipEnumTypeObject *)PyObject_Call((PyObject *)&sipEnumType_Type, args, NULL); currentType = NULL; Py_DECREF(args); if (eto == NULL) return NULL; if (etd->etd_pyslots != NULL) fix_slots((PyTypeObject *)eto, etd->etd_pyslots); /* * If the enum has a scope then the default __qualname__ will be incorrect. */ if (etd->etd_scope >= 0) { /* Append the name of the enum to the scope's __qualname__. */ Py_CLEAR(eto->super.ht_qualname); eto->super.ht_qualname = get_qualname( client->em_types[etd->etd_scope], name); if (eto->super.ht_qualname == NULL) { Py_DECREF((PyObject *)eto); return NULL; } } return (PyObject *)eto; } /* * Create a scoped enum. */ static PyObject *createScopedEnum(sipExportedModuleDef *client, sipEnumTypeDef *etd, int enum_nr, PyObject *name) { static PyObject *enum_type = NULL, *module_arg = NULL; static PyObject *qualname_arg = NULL; int i, nr_members; sipEnumMemberDef *enm; PyObject *members, *enum_obj, *args, *kw_args; /* Get the enum type if we haven't done so already. */ if (enum_type == NULL) { if ((enum_type = import_module_attr("enum", "IntEnum")) == NULL) goto ret_err; } /* Create a dict of the members. */ if ((members = PyDict_New()) == NULL) goto ret_err; /* * Note that the current structures for defining scoped enums are not ideal * as we are re-using the ones used for unscoped enums (which are designed * to support lazy implementations). */ if (etd->etd_scope < 0) { nr_members = client->em_nrenummembers; enm = client->em_enummembers; } else { const sipContainerDef *cod = get_container(client->em_types[etd->etd_scope]); nr_members = cod->cod_nrenummembers; enm = cod->cod_enummembers; } for (i = 0; i < nr_members; ++i) { if (enm->em_enum == enum_nr) { PyObject *val = PyLong_FromLong(enm->em_val); if (dict_set_and_discard(members, enm->em_name, val) < 0) goto rel_members; } ++enm; } if ((args = PyTuple_Pack(2, name, members)) == NULL) goto rel_members; if ((kw_args = PyDict_New()) == NULL) goto rel_args; if (objectify("module", &module_arg) < 0) goto rel_kw_args; if (PyDict_SetItem(kw_args, module_arg, client->em_nameobj) < 0) goto rel_kw_args; /* * If the enum has a scope then the default __qualname__ will be incorrect. */ if (etd->etd_scope >= 0) { int rc; PyObject *qualname; if (objectify("qualname", &qualname_arg) < 0) goto rel_kw_args; if ((qualname = get_qualname(client->em_types[etd->etd_scope], name)) == NULL) goto rel_kw_args; rc = PyDict_SetItem(kw_args, qualname_arg, qualname); Py_DECREF(qualname); if (rc < 0) goto rel_kw_args; } if ((enum_obj = PyObject_Call(enum_type, args, kw_args)) == NULL) goto rel_kw_args; Py_DECREF(kw_args); Py_DECREF(args); Py_DECREF(members); /* Note that it isn't actually a PyTypeObject. */ etd->etd_base.td_py_type = (PyTypeObject *)enum_obj; return enum_obj; /* Unwind on errors. */ rel_kw_args: Py_DECREF(kw_args); rel_args: Py_DECREF(args); rel_members: Py_DECREF(members); ret_err: return NULL; } /* * Create a type dictionary for dynamic type being created in a module. */ static PyObject *createTypeDict(sipExportedModuleDef *em) { static PyObject *mstr = NULL; PyObject *dict; if (objectify("__module__", &mstr) < 0) return NULL; /* Create the dictionary. */ if ((dict = PyDict_New()) == NULL) return NULL; /* We need to set the module name as an attribute for dynamic types. */ if (PyDict_SetItem(dict, mstr, em->em_nameobj) < 0) { Py_DECREF(dict); return NULL; } return dict; } /* * Convert an ASCII string to a Python object if it hasn't already been done. */ static int objectify(const char *s, PyObject **objp) { if (*objp == NULL) if ((*objp = PyUnicode_FromString(s)) == NULL) return -1; return 0; } /* * Add a set of static instances to a dictionary. */ static int addInstances(PyObject *dict, sipInstancesDef *id) { if (id->id_type != NULL && addTypeInstances(dict, id->id_type) < 0) return -1; if (id->id_voidp != NULL && addVoidPtrInstances(dict,id->id_voidp) < 0) return -1; if (id->id_char != NULL && addCharInstances(dict,id->id_char) < 0) return -1; if (id->id_string != NULL && addStringInstances(dict,id->id_string) < 0) return -1; if (id->id_int != NULL && addIntInstances(dict, id->id_int) < 0) return -1; if (id->id_long != NULL && addLongInstances(dict,id->id_long) < 0) return -1; if (id->id_ulong != NULL && addUnsignedLongInstances(dict, id->id_ulong) < 0) return -1; if (id->id_llong != NULL && addLongLongInstances(dict, id->id_llong) < 0) return -1; if (id->id_ullong != NULL && addUnsignedLongLongInstances(dict, id->id_ullong) < 0) return -1; if (id->id_double != NULL && addDoubleInstances(dict,id->id_double) < 0) return -1; return 0; } /* * Get "self" from the argument tuple for a method called as * Class.Method(self, ...) rather than self.Method(...). */ static int getSelfFromArgs(sipTypeDef *td, PyObject *args, int argnr, sipSimpleWrapper **selfp) { PyObject *self; /* Get self from the argument tuple. */ if (argnr >= PyTuple_GET_SIZE(args)) return FALSE; self = PyTuple_GET_ITEM(args, argnr); if (!PyObject_TypeCheck(self, sipTypeAsPyTypeObject(td))) return FALSE; *selfp = (sipSimpleWrapper *)self; return TRUE; } /* * Return non-zero if a method is non-lazy, ie. it must be added to the type * when it is created. */ static int isNonlazyMethod(PyMethodDef *pmd) { static const char *lazy[] = { "__getattribute__", "__getattr__", "__enter__", "__exit__", "__aenter__", "__aexit__", NULL }; const char **l; for (l = lazy; *l != NULL; ++l) if (strcmp(pmd->ml_name, *l) == 0) return TRUE; return FALSE; } /* * Add a method to a dictionary. */ static int addMethod(PyObject *dict, PyMethodDef *pmd) { PyObject *descr = sipMethodDescr_New(pmd); return dict_set_and_discard(dict, pmd->ml_name, descr); } /* * Populate a container's type dictionary. */ static int add_lazy_container_attrs(sipTypeDef *td, sipContainerDef *cod, PyObject *dict) { int i; PyMethodDef *pmd; sipEnumMemberDef *enm; sipVariableDef *vd; /* Do the methods. */ for (pmd = cod->cod_methods, i = 0; i < cod->cod_nrmethods; ++i, ++pmd) { /* Non-lazy methods will already have been handled. */ if (!sipTypeHasNonlazyMethod(td) || !isNonlazyMethod(pmd)) { if (addMethod(dict, pmd) < 0) return -1; } } /* Do the unscoped enum members. */ for (enm = cod->cod_enummembers, i = 0; i < cod->cod_nrenummembers; ++i, ++enm) { PyObject *val; if (enm->em_enum < 0) { /* It's an unnamed unscoped enum. */ val = PyLong_FromLong(enm->em_val); } else { sipTypeDef *etd = td->td_module->em_types[enm->em_enum]; if (sipTypeIsScopedEnum(etd)) continue; val = sip_api_convert_from_enum(enm->em_val, etd); } if (dict_set_and_discard(dict, enm->em_name, val) < 0) return -1; } /* Do the variables. */ for (vd = cod->cod_variables, i = 0; i < cod->cod_nrvariables; ++i, ++vd) { PyObject *descr; if (vd->vd_type == PropertyVariable) descr = create_property(vd); else descr = sipVariableDescr_New(vd, td, cod); if (dict_set_and_discard(dict, vd->vd_name, descr) < 0) return -1; } return 0; } /* * Create a Python property object from the SIP generated structure. */ static PyObject *create_property(sipVariableDef *vd) { PyObject *descr, *fget, *fset, *fdel, *doc; descr = fget = fset = fdel = doc = NULL; if ((fget = create_function(vd->vd_getter)) == NULL) goto done; if ((fset = create_function(vd->vd_setter)) == NULL) goto done; if ((fdel = create_function(vd->vd_deleter)) == NULL) goto done; if (vd->vd_docstring == NULL) { doc = Py_None; Py_INCREF(doc); } else if ((doc = PyUnicode_FromString(vd->vd_docstring)) == NULL) { goto done; } descr = PyObject_CallFunctionObjArgs((PyObject *)&PyProperty_Type, fget, fset, fdel, doc, NULL); done: Py_XDECREF(fget); Py_XDECREF(fset); Py_XDECREF(fdel); Py_XDECREF(doc); return descr; } /* * Return a PyCFunction as an object or Py_None if there isn't one. */ static PyObject *create_function(PyMethodDef *ml) { if (ml != NULL) return PyCFunction_New(ml, NULL); Py_INCREF(Py_None); return Py_None; } /* * Populate a type dictionary with all lazy attributes if it hasn't already * been done. */ static int add_lazy_attrs(sipTypeDef *td) { sipWrapperType *wt = (sipWrapperType *)sipTypeAsPyTypeObject(td); PyObject *dict; sipAttrGetter *ag; /* Handle the trivial case. */ if (wt->wt_dict_complete) return 0; dict = ((PyTypeObject *)wt)->tp_dict; if (sipTypeIsMapped(td)) { if (add_lazy_container_attrs(td, &((sipMappedTypeDef *)td)->mtd_container, dict) < 0) return -1; } else { sipClassTypeDef *nsx; /* Search the possible linked list of namespace extenders. */ for (nsx = (sipClassTypeDef *)td; nsx != NULL; nsx = nsx->ctd_nsextender) if (add_lazy_container_attrs((sipTypeDef *)nsx, &nsx->ctd_container, dict) < 0) return -1; } /* * Get any lazy attributes from registered getters. This must be done last * to allow any existing attributes to be replaced. */ /* TODO: Deprecate this mechanism in favour of an event handler. */ for (ag = sipAttrGetters; ag != NULL; ag = ag->next) if (ag->type == NULL || PyType_IsSubtype((PyTypeObject *)wt, ag->type)) if (ag->getter(td, dict) < 0) return -1; wt->wt_dict_complete = TRUE; PyType_Modified((PyTypeObject *)wt); return 0; } /* * Populate the type dictionary and all its super-types. */ static int add_all_lazy_attrs(sipTypeDef *td) { if (td == NULL) return 0; if (add_lazy_attrs(td) < 0) return -1; if (sipTypeIsClass(td)) { sipClassTypeDef *ctd = (sipClassTypeDef *)td; sipEncodedTypeDef *sup; if ((sup = ctd->ctd_supers) != NULL) do { sipTypeDef *sup_td = getGeneratedType(sup, td->td_module); if (add_all_lazy_attrs(sup_td) < 0) return -1; } while (!sup++->sc_flag); } return 0; } /* * Return the generated type structure corresponding to the given Python type * object. */ static const sipTypeDef *sip_api_type_from_py_type_object(PyTypeObject *py_type) { if (PyObject_TypeCheck((PyObject *)py_type, &sipWrapperType_Type)) return ((sipWrapperType *)py_type)->wt_td; if (PyObject_TypeCheck((PyObject *)py_type, &sipEnumType_Type)) return ((sipEnumTypeObject *)py_type)->type; return NULL; } /* * Return the generated type structure corresponding to the scope of the given * type. */ static const sipTypeDef *sip_api_type_scope(const sipTypeDef *td) { if (sipTypeIsEnum(td) || sipTypeIsScopedEnum(td)) { const sipEnumTypeDef *etd = (const sipEnumTypeDef *)td; if (etd->etd_scope >= 0) return td->td_module->em_types[etd->etd_scope]; } else { const sipContainerDef *cod; if (sipTypeIsMapped(td)) cod = &((const sipMappedTypeDef *)td)->mtd_container; else cod = &((const sipClassTypeDef *)td)->ctd_container; if (!cod->cod_scope.sc_flag) return getGeneratedType(&cod->cod_scope, td->td_module); } return NULL; } /* * Return TRUE if an object can be converted to a named enum. */ static int sip_api_can_convert_to_enum(PyObject *obj, const sipTypeDef *td) { assert(sipTypeIsEnum(td)); /* If the object is an enum then it must be the right enum. */ if (PyObject_TypeCheck((PyObject *)Py_TYPE(obj), &sipEnumType_Type)) return (PyObject_TypeCheck(obj, sipTypeAsPyTypeObject(td))); return PyLong_Check(obj); } /* * Convert a Python object implementing a named enum to an integer value. */ static int sip_api_convert_to_enum(PyObject *obj, const sipTypeDef *td) { return convert_to_enum(obj, td, TRUE); } /* * Convert a Python object implementing a named enum (or, optionally, an int) * to an integer value. */ static int convert_to_enum(PyObject *obj, const sipTypeDef *td, int allow_int) { int val; assert(sipTypeIsEnum(td) || sipTypeIsScopedEnum(td)); if (sipTypeIsScopedEnum(td)) { static PyObject *value = NULL; PyObject *val_obj; if (PyObject_IsInstance(obj, (PyObject *)sipTypeAsPyTypeObject(td)) <= 0) { enum_expected(obj, td); return -1; } if (objectify("value", &value) < 0) return -1; if ((val_obj = PyObject_GetAttr(obj, value)) == NULL) return -1; /* This will never overflow. */ val = long_as_nonoverflow_int(val_obj); Py_DECREF(val_obj); } else { if (PyObject_TypeCheck((PyObject *)Py_TYPE(obj), &sipEnumType_Type)) { if (!PyObject_TypeCheck(obj, sipTypeAsPyTypeObject(td))) { enum_expected(obj, td); return -1; } /* This will never overflow. */ val = long_as_nonoverflow_int(obj); } else if (allow_int && PyLong_Check(obj)) { val = long_as_nonoverflow_int(obj); } else { enum_expected(obj, td); return -1; } } return val; } /* * Raise an exception when failing to convert an enum because of its type. */ static void enum_expected(PyObject *obj, const sipTypeDef *td) { PyErr_Format(PyExc_TypeError, "a member of enum '%s' is expected not '%s'", sipPyNameOfEnum((sipEnumTypeDef *)td), Py_TYPE(obj)->tp_name); } /* Convert to a C/C++ int while checking for overflow. */ static int long_as_nonoverflow_int(PyObject *val_obj) { int old_overflow, val; old_overflow = sip_api_enable_overflow_checking(TRUE); val = sip_api_long_as_int(val_obj); sip_api_enable_overflow_checking(old_overflow); return val; } /* * Create a Python object for a member of a named enum. */ static PyObject *sip_api_convert_from_enum(int eval, const sipTypeDef *td) { assert(sipTypeIsEnum(td) || sipTypeIsScopedEnum(td)); return PyObject_CallFunction((PyObject *)sipTypeAsPyTypeObject(td), "(i)", eval); } /* * Register a getter for unknown attributes. */ static int sip_api_register_attribute_getter(const sipTypeDef *td, sipAttrGetterFunc getter) { sipAttrGetter *ag = sip_api_malloc(sizeof (sipAttrGetter)); if (ag == NULL) return -1; ag->type = sipTypeAsPyTypeObject(td); ag->getter = getter; ag->next = sipAttrGetters; sipAttrGetters = ag; return 0; } /* * Register a proxy resolver. */ static int sip_api_register_proxy_resolver(const sipTypeDef *td, sipProxyResolverFunc resolver) { sipProxyResolver *pr = sip_api_malloc(sizeof (sipProxyResolver)); if (pr == NULL) return -1; pr->td = td; pr->resolver = resolver; pr->next = proxyResolvers; proxyResolvers = pr; return 0; } /* * Report a function with invalid argument types. */ static void sip_api_no_function(PyObject *parseErr, const char *func, const char *doc) { sip_api_no_method(parseErr, NULL, func, doc); } /* * Report a method/function/signal with invalid argument types. */ static void sip_api_no_method(PyObject *parseErr, const char *scope, const char *method, const char *doc) { const char *sep = "."; if (scope == NULL) scope = ++sep; if (parseErr == NULL) { /* * If we have got this far without trying a parse then there must be no * overloads. */ PyErr_Format(PyExc_TypeError, "%s%s%s() is a private method", scope, sep, method); } else if (PyList_Check(parseErr)) { PyObject *exc; /* There is an entry for each overload that was tried. */ if (PyList_GET_SIZE(parseErr) == 1) { PyObject *detail = detail_FromFailure( PyList_GET_ITEM(parseErr, 0)); if (detail != NULL) { if (doc != NULL) { PyObject *doc_obj = signature_FromDocstring(doc, 0); if (doc_obj != NULL) { exc = PyUnicode_FromFormat("%U: %U", doc_obj, detail); Py_DECREF(doc_obj); } else { exc = NULL; } } else { exc = PyUnicode_FromFormat("%s%s%s(): %U", scope, sep, method, detail); } Py_DECREF(detail); } else { exc = NULL; } } else { static const char *summary = "arguments did not match any overloaded call:"; Py_ssize_t i; if (doc != NULL) exc = PyUnicode_FromString(summary); else exc = PyUnicode_FromFormat("%s%s%s(): %s", scope, sep, method, summary); for (i = 0; i < PyList_GET_SIZE(parseErr); ++i) { PyObject *failure; PyObject *detail = detail_FromFailure( PyList_GET_ITEM(parseErr, i)); if (detail != NULL) { if (doc != NULL) { PyObject *doc_obj = signature_FromDocstring(doc, i); if (doc_obj != NULL) { failure = PyUnicode_FromFormat("\n %U: %U", doc_obj, detail); Py_DECREF(doc_obj); } else { Py_XDECREF(exc); exc = NULL; break; } } else { failure = PyUnicode_FromFormat("\n overload %zd: %U", i + 1, detail); } Py_DECREF(detail); PyUnicode_AppendAndDel(&exc, failure); } else { Py_XDECREF(exc); exc = NULL; break; } } } if (exc != NULL) { PyErr_SetObject(PyExc_TypeError, exc); Py_DECREF(exc); } } else { /* * None is used as a marker to say that an exception has already been * raised. */ assert(parseErr == Py_None); } Py_XDECREF(parseErr); } /* * Return a string/unicode object extracted from a particular line of a * docstring. */ static PyObject *signature_FromDocstring(const char *doc, Py_ssize_t line) { const char *eol; Py_ssize_t size = 0; /* * Find the start of the line. If there is a non-default versioned * overload that has been enabled then it won't have an entry in the * docstring. This means that the returned signature may be incorrect. */ while (line-- > 0) { const char *next = strchr(doc, '\n'); if (next == NULL) break; doc = next + 1; } /* Find the last closing parenthesis. */ for (eol = doc; *eol != '\n' && *eol != '\0'; ++eol) if (*eol == ')') size = eol - doc + 1; return PyUnicode_FromStringAndSize(doc, size); } /* * Return a string/unicode object that describes the given failure. */ static PyObject *detail_FromFailure(PyObject *failure_obj) { sipParseFailure *failure; PyObject *detail; failure = (sipParseFailure *)PyCapsule_GetPointer(failure_obj, NULL); switch (failure->reason) { case Unbound: detail = PyUnicode_FromFormat( "first argument of unbound method must have type '%s'", failure->detail_str); break; case TooFew: detail = PyUnicode_FromString("not enough arguments"); break; case TooMany: detail = PyUnicode_FromString("too many arguments"); break; case KeywordNotString: detail = PyUnicode_FromFormat( "%S keyword argument name is not a string", failure->detail_obj); break; case UnknownKeyword: detail = PyUnicode_FromFormat("'%U' is not a valid keyword argument", failure->detail_obj); break; case Duplicate: detail = PyUnicode_FromFormat( "'%U' has already been given as a positional argument", failure->detail_obj); break; case WrongType: if (failure->arg_nr >= 0) detail = bad_type_str(failure->arg_nr, failure->detail_obj); else detail = PyUnicode_FromFormat( "argument '%s' has unexpected type '%s'", failure->arg_name, Py_TYPE(failure->detail_obj)->tp_name); break; case Exception: detail = failure->detail_obj; if (detail) { Py_INCREF(detail); break; } /* Drop through. */ default: detail = PyUnicode_FromString("unknown reason"); } return detail; } /* * Report an abstract method called with an unbound self. */ static void sip_api_abstract_method(const char *classname, const char *method) { PyErr_Format(PyExc_TypeError, "%s.%s() is abstract and cannot be called as an unbound method", classname, method); } /* * Report a deprecated class or method. */ int sip_api_deprecated(const char *classname, const char *method) { char buf[100]; if (classname == NULL) PyOS_snprintf(buf, sizeof (buf), "%s() is deprecated", method); else if (method == NULL) PyOS_snprintf(buf, sizeof (buf), "%s constructor is deprecated", classname); else PyOS_snprintf(buf, sizeof (buf), "%s.%s() is deprecated", classname, method); return PyErr_WarnEx(PyExc_DeprecationWarning, buf, 1); } /* * Report a bad operator argument. Only a small subset of operators need to * be handled (those that don't return Py_NotImplemented). */ static void sip_api_bad_operator_arg(PyObject *self, PyObject *arg, sipPySlotType st) { const char *sn = NULL; /* Try and get the text to match a Python exception. */ switch (st) { case concat_slot: case iconcat_slot: PyErr_Format(PyExc_TypeError, "cannot concatenate '%s' and '%s' objects", Py_TYPE(self)->tp_name, Py_TYPE(arg)->tp_name); break; case repeat_slot: sn = "*"; break; case irepeat_slot: sn = "*="; break; default: sn = "unknown"; } if (sn != NULL) PyErr_Format(PyExc_TypeError, "unsupported operand type(s) for %s: '%s' and '%s'", sn, Py_TYPE(self)->tp_name, Py_TYPE(arg)->tp_name); } /* * Report a sequence length that does not match the length of a slice. */ static void sip_api_bad_length_for_slice(Py_ssize_t seqlen, Py_ssize_t slicelen) { PyErr_Format(PyExc_ValueError, "attempt to assign sequence of size %zd to slice of size %zd", seqlen, slicelen); } /* * Report a Python object that cannot be converted to a particular class. */ static void sip_api_bad_class(const char *classname) { PyErr_Format(PyExc_TypeError, "cannot convert Python object to an instance of %s", classname); } /* * Report a Python member function with an unexpected result. */ static void sip_api_bad_catcher_result(PyObject *method) { PyObject *mname, *etype, *evalue, *etraceback; /* * Get the current exception object if there is one. Its string * representation will be used as the detail of a new exception. */ PyErr_Fetch(&etype, &evalue, &etraceback); PyErr_NormalizeException(&etype, &evalue, &etraceback); Py_XDECREF(etraceback); /* * This is part of the public API so we make no assumptions about the * method object. */ if (!PyMethod_Check(method) || PyMethod_GET_FUNCTION(method) == NULL || !PyFunction_Check(PyMethod_GET_FUNCTION(method)) || PyMethod_GET_SELF(method) == NULL) { PyErr_Format(PyExc_TypeError, "invalid argument to sipBadCatcherResult()"); return; } mname = ((PyFunctionObject *)PyMethod_GET_FUNCTION(method))->func_name; if (evalue != NULL) { PyErr_Format(etype, "invalid result from %s.%U(), %S", Py_TYPE(PyMethod_GET_SELF(method))->tp_name, mname, evalue); Py_DECREF(evalue); } else { PyErr_Format(PyExc_TypeError, "invalid result from %s.%U()", Py_TYPE(PyMethod_GET_SELF(method))->tp_name, mname); } Py_XDECREF(etype); } /* * Transfer ownership of a class instance to Python from C/C++. */ static void sip_api_transfer_back(PyObject *self) { if (self != NULL && PyObject_TypeCheck(self, (PyTypeObject *)&sipWrapper_Type)) { sipSimpleWrapper *sw = (sipSimpleWrapper *)self; if (sipCppHasRef(sw)) { sipResetCppHasRef(sw); Py_DECREF(sw); } else { removeFromParent((sipWrapper *)sw); } sipSetPyOwned(sw); } } /* * Break the association of a C++ owned Python object with any parent. This is * deprecated because it is the equivalent of sip_api_transfer_to(self, NULL). */ static void sip_api_transfer_break(PyObject *self) { if (self != NULL && PyObject_TypeCheck(self, (PyTypeObject *)&sipWrapper_Type)) { sipSimpleWrapper *sw = (sipSimpleWrapper *)self; if (sipCppHasRef(sw)) { sipResetCppHasRef(sw); Py_DECREF(sw); } else { removeFromParent((sipWrapper *)sw); } } } /* * Transfer ownership of a class instance to C/C++ from Python. */ static void sip_api_transfer_to(PyObject *self, PyObject *owner) { /* * There is a legitimate case where we try to transfer a PyObject that * may not be a SIP generated class. The virtual handler code calls * this function to keep the C/C++ instance alive when it gets rid of * the Python object returned by the Python method. A class may have * handwritten code that converts a regular Python type - so we can't * assume that we can simply cast to sipWrapper. */ if (self != NULL && PyObject_TypeCheck(self, (PyTypeObject *)&sipWrapper_Type)) { sipSimpleWrapper *sw = (sipSimpleWrapper *)self; if (owner == NULL) { /* There is no owner. */ if (sipCppHasRef(sw)) { sipResetCppHasRef(sw); } else { Py_INCREF(sw); removeFromParent((sipWrapper *)sw); sipResetPyOwned(sw); } Py_DECREF(sw); } else if (owner == Py_None) { /* * The owner is a C++ instance and not a Python object (ie. there * is no parent) so there is an explicit extra reference to keep * this Python object alive. Note that there is no way to * specify this from a .sip file - it is useful when embedding in * C/C++ applications. */ if (!sipCppHasRef(sw)) { Py_INCREF(sw); removeFromParent((sipWrapper *)sw); sipResetPyOwned(sw); sipSetCppHasRef(sw); } } else if (PyObject_TypeCheck(owner, (PyTypeObject *)&sipWrapper_Type)) { /* * The owner is a Python object (ie. the C++ instance that the * Python object wraps). */ if (sipCppHasRef(sw)) { sipResetCppHasRef(sw); } else { Py_INCREF(sw); removeFromParent((sipWrapper *)sw); sipResetPyOwned(sw); } addToParent((sipWrapper *)sw, (sipWrapper *)owner); Py_DECREF(sw); } } } /* * Add a license to a dictionary. */ static int addLicense(PyObject *dict,sipLicenseDef *lc) { int rc; PyObject *ldict, *proxy, *o; /* Convert the strings we use to objects if not already done. */ if (objectify("__license__", &licenseName) < 0) return -1; if (objectify("Licensee", &licenseeName) < 0) return -1; if (objectify("Type", &typeName) < 0) return -1; if (objectify("Timestamp", ×tampName) < 0) return -1; if (objectify("Signature", &signatureName) < 0) return -1; /* We use a dictionary to hold the license information. */ if ((ldict = PyDict_New()) == NULL) return -1; /* The license type is compulsory, the rest are optional. */ if (lc->lc_type == NULL) goto deldict; if ((o = PyUnicode_FromString(lc->lc_type)) == NULL) goto deldict; rc = PyDict_SetItem(ldict,typeName,o); Py_DECREF(o); if (rc < 0) goto deldict; if (lc->lc_licensee != NULL) { if ((o = PyUnicode_FromString(lc->lc_licensee)) == NULL) goto deldict; rc = PyDict_SetItem(ldict,licenseeName,o); Py_DECREF(o); if (rc < 0) goto deldict; } if (lc->lc_timestamp != NULL) { if ((o = PyUnicode_FromString(lc->lc_timestamp)) == NULL) goto deldict; rc = PyDict_SetItem(ldict,timestampName,o); Py_DECREF(o); if (rc < 0) goto deldict; } if (lc->lc_signature != NULL) { if ((o = PyUnicode_FromString(lc->lc_signature)) == NULL) goto deldict; rc = PyDict_SetItem(ldict,signatureName,o); Py_DECREF(o); if (rc < 0) goto deldict; } /* Create a read-only proxy. */ if ((proxy = PyDictProxy_New(ldict)) == NULL) goto deldict; Py_DECREF(ldict); rc = PyDict_SetItem(dict, licenseName, proxy); Py_DECREF(proxy); return rc; deldict: Py_DECREF(ldict); return -1; } /* * Add the void pointer instances to a dictionary. */ static int addVoidPtrInstances(PyObject *dict,sipVoidPtrInstanceDef *vi) { while (vi->vi_name != NULL) { PyObject *w = sip_api_convert_from_void_ptr(vi->vi_val); if (dict_set_and_discard(dict, vi->vi_name, w) < 0) return -1; ++vi; } return 0; } /* * Add the char instances to a dictionary. */ static int addCharInstances(PyObject *dict, sipCharInstanceDef *ci) { while (ci->ci_name != NULL) { PyObject *w; switch (ci->ci_encoding) { case 'A': w = PyUnicode_DecodeASCII(&ci->ci_val, 1, NULL); break; case 'L': w = PyUnicode_DecodeLatin1(&ci->ci_val, 1, NULL); break; case '8': w = PyUnicode_FromStringAndSize(&ci->ci_val, 1); break; default: w = PyBytes_FromStringAndSize(&ci->ci_val, 1); } if (dict_set_and_discard(dict, ci->ci_name, w) < 0) return -1; ++ci; } return 0; } /* * Add the string instances to a dictionary. */ static int addStringInstances(PyObject *dict, sipStringInstanceDef *si) { while (si->si_name != NULL) { PyObject *w; switch (si->si_encoding) { case 'A': w = PyUnicode_DecodeASCII(si->si_val, strlen(si->si_val), NULL); break; case 'L': w = PyUnicode_DecodeLatin1(si->si_val, strlen(si->si_val), NULL); break; case '8': w = PyUnicode_FromString(si->si_val); break; case 'w': /* The hack for wchar_t. */ #if defined(HAVE_WCHAR_H) w = PyUnicode_FromWideChar((const wchar_t *)si->si_val, 1); break; #else raiseNoWChar(); return -1; #endif case 'W': /* The hack for wchar_t*. */ #if defined(HAVE_WCHAR_H) w = PyUnicode_FromWideChar((const wchar_t *)si->si_val, wcslen((const wchar_t *)si->si_val)); break; #else raiseNoWChar(); return -1; #endif default: w = PyBytes_FromString(si->si_val); } if (dict_set_and_discard(dict, si->si_name, w) < 0) return -1; ++si; } return 0; } /* * Add the int instances to a dictionary. */ static int addIntInstances(PyObject *dict, sipIntInstanceDef *ii) { while (ii->ii_name != NULL) { PyObject *w = PyLong_FromLong(ii->ii_val); if (dict_set_and_discard(dict, ii->ii_name, w) < 0) return -1; ++ii; } return 0; } /* * Add the long instances to a dictionary. */ static int addLongInstances(PyObject *dict,sipLongInstanceDef *li) { while (li->li_name != NULL) { PyObject *w = PyLong_FromLong(li->li_val); if (dict_set_and_discard(dict, li->li_name, w) < 0) return -1; ++li; } return 0; } /* * Add the unsigned long instances to a dictionary. */ static int addUnsignedLongInstances(PyObject *dict, sipUnsignedLongInstanceDef *uli) { while (uli->uli_name != NULL) { PyObject *w = PyLong_FromUnsignedLong(uli->uli_val); if (dict_set_and_discard(dict, uli->uli_name, w) < 0) return -1; ++uli; } return 0; } /* * Add the long long instances to a dictionary. */ static int addLongLongInstances(PyObject *dict, sipLongLongInstanceDef *lli) { while (lli->lli_name != NULL) { PyObject *w; #if defined(HAVE_LONG_LONG) w = PyLong_FromLongLong(lli->lli_val); #else w = PyLong_FromLong(lli->lli_val); #endif if (dict_set_and_discard(dict, lli->lli_name, w) < 0) return -1; ++lli; } return 0; } /* * Add the unsigned long long instances to a dictionary. */ static int addUnsignedLongLongInstances(PyObject *dict, sipUnsignedLongLongInstanceDef *ulli) { while (ulli->ulli_name != NULL) { PyObject *w; #if defined(HAVE_LONG_LONG) w = PyLong_FromUnsignedLongLong(ulli->ulli_val); #else w = PyLong_FromUnsignedLong(ulli->ulli_val); #endif if (dict_set_and_discard(dict, ulli->ulli_name, w) < 0) return -1; ++ulli; } return 0; } /* * Add the double instances to a dictionary. */ static int addDoubleInstances(PyObject *dict,sipDoubleInstanceDef *di) { while (di->di_name != NULL) { PyObject *w = PyFloat_FromDouble(di->di_val); if (dict_set_and_discard(dict, di->di_name, w) < 0) return -1; ++di; } return 0; } /* * Wrap a set of type instances and add them to a dictionary. */ static int addTypeInstances(PyObject *dict, sipTypeInstanceDef *ti) { while (ti->ti_name != NULL) { if (addSingleTypeInstance(dict, ti->ti_name, ti->ti_ptr, *ti->ti_type, ti->ti_flags) < 0) return -1; ++ti; } return 0; } /* * Wrap a single type instance and add it to a dictionary. */ static int addSingleTypeInstance(PyObject *dict, const char *name, void *cppPtr, const sipTypeDef *td, int initflags) { PyObject *obj; if (sipTypeIsEnum(td) || sipTypeIsScopedEnum(td)) { obj = sip_api_convert_from_enum(*(int *)cppPtr, td); } else { sipConvertFromFunc cfrom; cppPtr = resolve_proxy(td, cppPtr); cfrom = get_from_convertor(td); if (cfrom != NULL) obj = cfrom(cppPtr, NULL); else obj = wrap_simple_instance(cppPtr, td, NULL, initflags); } return dict_set_and_discard(dict, name, obj); } /* * Convert a type instance and add it to a dictionary. */ static int sip_api_add_type_instance(PyObject *dict, const char *name, void *cppPtr, const sipTypeDef *td) { return addSingleTypeInstance(getDictFromObject(dict), name, cppPtr, td, 0); } /* * Return the instance dictionary for an object if it is a wrapped type. * Otherwise assume that it is a module dictionary. */ static PyObject *getDictFromObject(PyObject *obj) { if (PyObject_TypeCheck(obj, (PyTypeObject *)&sipWrapperType_Type)) obj = ((PyTypeObject *)obj)->tp_dict; return obj; } /* * Return a Python reimplementation corresponding to a C/C++ virtual function, * if any. If one was found then the GIL is acquired. This is deprecated, use * sip_api_is_python_method_12_8() instead. */ static PyObject *sip_api_is_py_method(sip_gilstate_t *gil, char *pymc, sipSimpleWrapper *sipSelf, const char *cname, const char *mname) { return sip_api_is_py_method_12_8(gil, pymc, &sipSelf, cname, mname); } /* * Return a Python reimplementation corresponding to a C/C++ virtual function, * if any. If one was found then the GIL is acquired. */ static PyObject *sip_api_is_py_method_12_8(sip_gilstate_t *gil, char *pymc, sipSimpleWrapper **sipSelfp, const char *cname, const char *mname) { sipSimpleWrapper *sipSelf; PyObject *mname_obj, *reimp, *mro, *cls; Py_ssize_t i; /* * This is the most common case (where there is no Python reimplementation) * so we take a fast shortcut without acquiring the GIL. */ if (*pymc != 0) return NULL; /* We might still have C++ going after the interpreter has gone. */ if (sipInterpreter == NULL) return NULL; #ifdef WITH_THREAD *gil = PyGILState_Ensure(); #endif /* Only read this when we have the GIL. */ sipSelf = *sipSelfp; /* * It's possible that the Python object has been deleted but the underlying * C++ instance is still working and trying to handle virtual functions. * Alternatively, an instance has started handling virtual functions before * its ctor has returned. In either case say there is no Python * reimplementation. */ if (sipSelf != NULL) sipSelf = deref_mixin(sipSelf); if (sipSelf == NULL) goto release_gil; /* * It's possible that the object's type's tp_mro is NULL. A possible * circumstance is when a type has been created dynamically and the only * reference to it is the single instance of the type which is in the * process of being garbage collected. */ cls = (PyObject *)Py_TYPE(sipSelf); mro = ((PyTypeObject *)cls)->tp_mro; if (mro == NULL) goto release_gil; /* Get any reimplementation. */ if ((mname_obj = PyUnicode_FromString(mname)) == NULL) goto release_gil; /* * We don't use PyObject_GetAttr() because that might find the generated * C function before a reimplementation defined in a mixin (ie. later in * the MRO). However that means we must explicitly check that the class * hierarchy is fully initialised. */ if (add_all_lazy_attrs(((sipWrapperType *)Py_TYPE(sipSelf))->wt_td) < 0) { Py_DECREF(mname_obj); goto release_gil; } if (sipSelf->dict != NULL) { /* Check the instance dictionary in case it has been monkey patched. */ if ((reimp = PyDict_GetItem(sipSelf->dict, mname_obj)) != NULL && PyCallable_Check(reimp)) { Py_DECREF(mname_obj); Py_INCREF(reimp); return reimp; } } assert(PyTuple_Check(mro)); reimp = NULL; for (i = 0; i < PyTuple_GET_SIZE(mro); ++i) { PyObject *cls_dict, *cls_attr; cls = PyTuple_GET_ITEM(mro, i); cls_dict = ((PyTypeObject *)cls)->tp_dict; /* * Check any possible reimplementation is not the wrapped C++ method or * a default special method implementation. */ if (cls_dict != NULL && (cls_attr = PyDict_GetItem(cls_dict, mname_obj)) != NULL && Py_TYPE(cls_attr) != &sipMethodDescr_Type && Py_TYPE(cls_attr) != &PyWrapperDescr_Type) { reimp = cls_attr; break; } } Py_DECREF(mname_obj); if (reimp != NULL) { /* * Emulate the behaviour of a descriptor to make sure we return a bound * method. */ if (PyMethod_Check(reimp)) { /* It's already a method but make sure it is bound. */ if (PyMethod_GET_SELF(reimp) != NULL) Py_INCREF(reimp); else reimp = PyMethod_New(PyMethod_GET_FUNCTION(reimp), (PyObject *)sipSelf); } else if (PyFunction_Check(reimp)) { reimp = PyMethod_New(reimp, (PyObject *)sipSelf); } else if (Py_TYPE(reimp)->tp_descr_get) { /* It is a descriptor, so assume it will do the right thing. */ reimp = Py_TYPE(reimp)->tp_descr_get(reimp, (PyObject *)sipSelf, cls); } else { /* * We don't know what it is so just return and assume that an * appropriate exception will be raised later on. */ Py_INCREF(reimp); } } else { /* Use the fast track in future. */ *pymc = 1; if (cname != NULL) { /* Note that this will only be raised once per method. */ PyErr_Format(PyExc_NotImplementedError, "%s.%s() is abstract and must be overridden", cname, mname); PyErr_Print(); } #ifdef WITH_THREAD PyGILState_Release(*gil); #endif } return reimp; release_gil: #ifdef WITH_THREAD PyGILState_Release(*gil); #endif return NULL; } /* * Convert a C/C++ pointer to the object that wraps it. */ static PyObject *sip_api_get_pyobject(void *cppPtr, const sipTypeDef *td) { return (PyObject *)sipOMFindObject(&cppPyMap, cppPtr, td); } /* * The default access function. */ void *sip_api_get_address(sipSimpleWrapper *w) { return (w->access_func != NULL) ? w->access_func(w, GuardedPointer) : w->data; } /* * The access function for handwritten access functions. */ static void *explicit_access_func(sipSimpleWrapper *sw, AccessFuncOp op) { typedef void *(*explicitAccessFunc)(void); if (op == ReleaseGuard) return NULL; return ((explicitAccessFunc)(sw->data))(); } /* * The access function for indirect access. */ static void *indirect_access_func(sipSimpleWrapper *sw, AccessFuncOp op) { void *addr; switch (op) { case UnguardedPointer: addr = sw->data; break; case GuardedPointer: addr = *((void **)sw->data); break; default: addr = NULL; } return addr; } /* * Get the C/C++ pointer for a complex object. Note that not casting the C++ * pointer is a bug. However this would only ever be called by PyQt3 signal * emitter code and PyQt doesn't contain anything that multiply inherits from * QObject. */ static void *sip_api_get_complex_cpp_ptr(sipSimpleWrapper *sw) { return getComplexCppPtr(sw, NULL); } /* * Get the C/C++ pointer for a complex object and optionally cast it to the * required type. */ static void *getComplexCppPtr(sipSimpleWrapper *sw, const sipTypeDef *td) { if (!sipIsDerived(sw)) { PyErr_SetString(PyExc_RuntimeError, "no access to protected functions or signals for objects not created from Python"); return NULL; } return sip_api_get_cpp_ptr(sw, td); } /* * Get the C/C++ pointer from a wrapper and optionally cast it to the required * type. */ void *sip_api_get_cpp_ptr(sipSimpleWrapper *sw, const sipTypeDef *td) { void *ptr = sip_api_get_address(sw); if (checkPointer(ptr, sw) < 0) return NULL; if (td != NULL) { if (PyObject_TypeCheck((PyObject *)sw, sipTypeAsPyTypeObject(td))) ptr = cast_cpp_ptr(ptr, Py_TYPE(sw), td); else ptr = NULL; if (ptr == NULL) PyErr_Format(PyExc_TypeError, "could not convert '%s' to '%s'", Py_TYPE(sw)->tp_name, sipPyNameOfContainer(&((const sipClassTypeDef *)td)->ctd_container, td)); } return ptr; } /* * Cast a C/C++ pointer from a source type to a destination type. */ static void *cast_cpp_ptr(void *ptr, PyTypeObject *src_type, const sipTypeDef *dst_type) { sipCastFunc cast = ((const sipClassTypeDef *)((sipWrapperType *)src_type)->wt_td)->ctd_cast; /* C structures and base classes don't have cast functions. */ if (cast != NULL) ptr = (*cast)(ptr, dst_type); return ptr; } /* * Check that a pointer is non-NULL. */ static int checkPointer(void *ptr, sipSimpleWrapper *sw) { if (ptr == NULL) { PyErr_Format(PyExc_RuntimeError, (sipWasCreated(sw) ? "wrapped C/C++ object of type %s has been deleted" : "super-class __init__() of type %s was never called"), Py_TYPE(sw)->tp_name); return -1; } return 0; } /* * Keep an extra reference to an object. */ static void sip_api_keep_reference(PyObject *self, int key, PyObject *obj) { PyObject *dict, *key_obj; /* * If there isn't a "self" to keep the extra reference for later garbage * collection then just take a reference and let it leak. */ if (self == NULL) { Py_XINCREF(obj); return; } /* Create the extra references dictionary if needed. */ if ((dict = ((sipSimpleWrapper *)self)->extra_refs) == NULL) { if ((dict = PyDict_New()) == NULL) return; ((sipSimpleWrapper *)self)->extra_refs = dict; } if ((key_obj = PyLong_FromLong(key)) != NULL) { /* This can happen if the argument was optional. */ if (obj == NULL) obj = Py_None; PyDict_SetItem(dict, key_obj, obj); Py_DECREF(key_obj); } } /* * Get an object that has an extra reference. */ static PyObject *sip_api_get_reference(PyObject *self, int key) { PyObject *dict, *key_obj, *obj; /* Get the extra references dictionary if there is one. */ if ((dict = ((sipSimpleWrapper *)self)->extra_refs) == NULL) return NULL; if ((key_obj = PyLong_FromLong(key)) == NULL) return NULL; obj = PyDict_GetItem(dict, key_obj); Py_DECREF(key_obj); Py_XINCREF(obj); return obj; } /* * Return TRUE if an object is owned by Python. Note that this isn't * implemented as a macro in sip.h because the position of the sw_flags field * is dependent on the version of Python. */ static int sip_api_is_owned_by_python(sipSimpleWrapper *sw) { return sipIsPyOwned(sw); } /* * Return TRUE if the type of a C++ instance is a derived class. Note that * this isn't implemented as a macro in sip.h because the position of the * sw_flags field is dependent on the version of Python. */ static int sip_api_is_derived_class(sipSimpleWrapper *sw) { return sipIsDerived(sw); } /* * Get the user defined object from a wrapped object. Note that this isn't * implemented as a macro in sip.h because the position of the user field is * dependent on the version of Python. */ static PyObject *sip_api_get_user_object(const sipSimpleWrapper *sw) { return sw->user; } /* * Set the user defined object in a wrapped object. Note that this isn't * implemented as a macro in sip.h because the position of the user field is * dependent on the version of Python. */ static void sip_api_set_user_object(sipSimpleWrapper *sw, PyObject *user) { sw->user = user; } /* * Check to see if a Python object can be converted to a type. */ static int sip_api_can_convert_to_type(PyObject *pyObj, const sipTypeDef *td, int flags) { int ok; assert(td == NULL || sipTypeIsClass(td) || sipTypeIsMapped(td)); if (td == NULL) { /* * The type must be /External/ and the module that contains the * implementation hasn't been imported. */ ok = FALSE; } else if (pyObj == Py_None) { /* If the type explicitly handles None then ignore the flags. */ if (sipTypeAllowNone(td)) ok = TRUE; else ok = ((flags & SIP_NOT_NONE) == 0); } else { sipConvertToFunc cto; if (sipTypeIsClass(td)) { cto = ((const sipClassTypeDef *)td)->ctd_cto; if (cto == NULL || (flags & SIP_NO_CONVERTORS) != 0) ok = PyObject_TypeCheck(pyObj, sipTypeAsPyTypeObject(td)); else ok = cto(pyObj, NULL, NULL, NULL); } else { cto = ((const sipMappedTypeDef *)td)->mtd_cto; ok = cto(pyObj, NULL, NULL, NULL); } } return ok; } /* * Convert a Python object to a C/C++ pointer, assuming a previous call to * sip_api_can_convert_to_type() has been successful. Allow ownership to be * transferred and any type convertors to be disabled. */ static void *sip_api_convert_to_type(PyObject *pyObj, const sipTypeDef *td, PyObject *transferObj, int flags, int *statep, int *iserrp) { void *cpp = NULL; int state = 0; assert(sipTypeIsClass(td) || sipTypeIsMapped(td)); /* Don't convert if there has already been an error. */ if (!*iserrp) { /* Do the conversion. */ if (pyObj == Py_None && !sipTypeAllowNone(td)) cpp = NULL; else { sipConvertToFunc cto; if (sipTypeIsClass(td)) { cto = ((const sipClassTypeDef *)td)->ctd_cto; if (cto == NULL || (flags & SIP_NO_CONVERTORS) != 0) { if ((cpp = sip_api_get_cpp_ptr((sipSimpleWrapper *)pyObj, td)) == NULL) *iserrp = TRUE; else if (transferObj != NULL) { if (transferObj == Py_None) sip_api_transfer_back(pyObj); else sip_api_transfer_to(pyObj, transferObj); } } else { state = cto(pyObj, &cpp, iserrp, transferObj); } } else { cto = ((const sipMappedTypeDef *)td)->mtd_cto; state = cto(pyObj, &cpp, iserrp, transferObj); } } } if (statep != NULL) *statep = state; return cpp; } /* * Convert a Python object to a C/C++ pointer and raise an exception if it * can't be done. */ void *sip_api_force_convert_to_type(PyObject *pyObj, const sipTypeDef *td, PyObject *transferObj, int flags, int *statep, int *iserrp) { /* Don't even try if there has already been an error. */ if (*iserrp) return NULL; /* See if the object's type can be converted. */ if (!sip_api_can_convert_to_type(pyObj, td, flags)) { if (sipTypeIsMapped(td)) PyErr_Format(PyExc_TypeError, "%s cannot be converted to a C/C++ %s in this context", Py_TYPE(pyObj)->tp_name, sipTypeName(td)); else PyErr_Format(PyExc_TypeError, "%s cannot be converted to %s.%s in this context", Py_TYPE(pyObj)->tp_name, sipNameOfModule(td->td_module), sipPyNameOfContainer(&((const sipClassTypeDef *)td)->ctd_container, td)); if (statep != NULL) *statep = 0; *iserrp = TRUE; return NULL; } /* Do the conversion. */ return sip_api_convert_to_type(pyObj, td, transferObj, flags, statep, iserrp); } /* * Release a possibly temporary C/C++ instance created by a type convertor. */ static void sip_api_release_type(void *cpp, const sipTypeDef *td, int state) { /* See if there is something to release. */ if (state & SIP_TEMPORARY) release(cpp, td, state); } /* * Release an instance. */ static void release(void *addr, const sipTypeDef *td, int state) { sipReleaseFunc rel; if (sipTypeIsClass(td)) { rel = ((const sipClassTypeDef *)td)->ctd_release; /* * If there is no release function then it must be a C structure and we * can just free it. */ if (rel == NULL) sip_api_free(addr); } else if (sipTypeIsMapped(td)) rel = ((const sipMappedTypeDef *)td)->mtd_release; else rel = NULL; if (rel != NULL) rel(addr, state); } /* * Convert a C/C++ instance to a Python instance. */ PyObject *sip_api_convert_from_type(void *cpp, const sipTypeDef *td, PyObject *transferObj) { PyObject *py; sipConvertFromFunc cfrom; assert(sipTypeIsClass(td) || sipTypeIsMapped(td)); /* Handle None. */ if (cpp == NULL) { Py_INCREF(Py_None); return Py_None; } cpp = resolve_proxy(td, cpp); cfrom = get_from_convertor(td); if (cfrom != NULL) return cfrom(cpp, transferObj); /* * See if we have already wrapped it. Invoking sub-class code can be * expensive so we check the cache first, even though the sub-class code * might perform a down-cast. */ if ((py = sip_api_get_pyobject(cpp, td)) == NULL && sipTypeHasSCC(td)) { void *orig_cpp = cpp; const sipTypeDef *orig_td = td; /* Apply the sub-class convertor. */ td = convertSubClass(td, &cpp); /* * If the sub-class convertor has done something then check the cache * again using the modified values. */ if (cpp != orig_cpp || td != orig_td) py = sip_api_get_pyobject(cpp, td); } if (py != NULL) Py_INCREF(py); else if ((py = wrap_simple_instance(cpp, td, NULL, SIP_SHARE_MAP)) == NULL) return NULL; /* Handle any ownership transfer. */ if (transferObj != NULL) { if (transferObj == Py_None) sip_api_transfer_back(py); else sip_api_transfer_to(py, transferObj); } return py; } /* * Convert a new C/C++ instance to a Python instance. */ static PyObject *sip_api_convert_from_new_type(void *cpp, const sipTypeDef *td, PyObject *transferObj) { sipWrapper *owner; sipConvertFromFunc cfrom; /* Handle None. */ if (cpp == NULL) { Py_INCREF(Py_None); return Py_None; } cpp = resolve_proxy(td, cpp); cfrom = get_from_convertor(td); if (cfrom != NULL) { PyObject *res = cfrom(cpp, transferObj); if (res != NULL) { /* * We no longer need the C/C++ instance so we release it (unless * its ownership is transferred). This means this call is * semantically equivalent to the case where we are wrapping a * class. */ if (transferObj == NULL || transferObj == Py_None) release(cpp, td, 0); } return res; } /* Apply any sub-class convertor. */ if (sipTypeHasSCC(td)) td = convertSubClass(td, &cpp); /* Handle any ownership transfer. */ if (transferObj == NULL || transferObj == Py_None) owner = NULL; else owner = (sipWrapper *)transferObj; return wrap_simple_instance(cpp, td, owner, (owner == NULL ? SIP_PY_OWNED : 0)); } /* * Implement the normal transfer policy for the result of %ConvertToTypeCode, * ie. it is temporary unless it is being transferred from Python. */ int sip_api_get_state(PyObject *transferObj) { return (transferObj == NULL || transferObj == Py_None) ? SIP_TEMPORARY : 0; } /* * This is set by sip_api_find_type() before calling bsearch() on the types * table for the module. This is a hack that works around the problem of * unresolved externally defined types. */ static sipExportedModuleDef *module_searched; /* * The bsearch() helper function for searching the types table. */ static int compareTypeDef(const void *key, const void *el) { const char *s1 = (const char *)key; const char *s2 = NULL; const sipTypeDef *td; char ch1, ch2; /* Allow for unresolved externally defined types. */ td = *(const sipTypeDef **)el; if (td != NULL) { s2 = sipTypeName(td); } else { sipExternalTypeDef *etd = module_searched->em_external; assert(etd != NULL); /* Find which external type it is. */ while (etd->et_nr >= 0) { const void *tdp = &module_searched->em_types[etd->et_nr]; if (tdp == el) { s2 = etd->et_name; break; } ++etd; } assert(s2 != NULL); } /* * Compare while ignoring spaces so that we don't impose a rigorous naming * standard. This only really affects template-based mapped types. */ do { while ((ch1 = *s1++) == ' ') ; while ((ch2 = *s2++) == ' ') ; /* We might be looking for a pointer or a reference. */ if ((ch1 == '*' || ch1 == '&' || ch1 == '\0') && ch2 == '\0') return 0; } while (ch1 == ch2); return (ch1 < ch2 ? -1 : 1); } /* * Return the type structure for a particular type. */ static const sipTypeDef *sip_api_find_type(const char *type) { sipExportedModuleDef *em; for (em = moduleList; em != NULL; em = em->em_next) { sipTypeDef **tdp; /* The backdoor to the comparison helper. */ module_searched = em; tdp = (sipTypeDef **)bsearch((const void *)type, (const void *)em->em_types, em->em_nrtypes, sizeof (sipTypeDef *), compareTypeDef); if (tdp != NULL) { /* * Note that this will be NULL for unresolved externally defined * types. */ return *tdp; } } return NULL; } /* * Return the mapped type structure for a particular mapped type. This is * deprecated. */ static const sipMappedType *sip_api_find_mapped_type(const char *type) { const sipTypeDef *td = sip_api_find_type(type); if (td != NULL && sipTypeIsMapped(td)) return (const sipMappedType *)td; return NULL; } /* * Return the type structure for a particular class. This is deprecated. */ static sipWrapperType *sip_api_find_class(const char *type) { const sipTypeDef *td = sip_api_find_type(type); if (td != NULL && sipTypeIsClass(td)) return (sipWrapperType *)sipTypeAsPyTypeObject(td); return NULL; } /* * Return the type structure for a particular named unscoped enum. This is * deprecated. */ static PyTypeObject *sip_api_find_named_enum(const char *type) { const sipTypeDef *td = sip_api_find_type(type); if (td != NULL && sipTypeIsEnum(td)) return sipTypeAsPyTypeObject(td); return NULL; } /* * Save the components of a Python method. */ void sipSaveMethod(sipPyMethod *pm, PyObject *meth) { pm->mfunc = PyMethod_GET_FUNCTION(meth); pm->mself = PyMethod_GET_SELF(meth); } /* * Call a hook. */ static void sip_api_call_hook(const char *hookname) { PyObject *dictofmods, *mod, *dict, *hook, *res; /* Get the dictionary of modules. */ if ((dictofmods = PyImport_GetModuleDict()) == NULL) return; /* Get the builtins module. */ if ((mod = PyDict_GetItemString(dictofmods, "builtins")) == NULL) return; /* Get it's dictionary. */ if ((dict = PyModule_GetDict(mod)) == NULL) return; /* Get the function hook. */ if ((hook = PyDict_GetItemString(dict, hookname)) == NULL) return; /* Call the hook and discard any result. */ res = PyObject_Call(hook, empty_tuple, NULL); Py_XDECREF(res); } /* * Call any sub-class convertors for a given type returning a pointer to the * sub-type object, and possibly modifying the C++ address (in the case of * multiple inheritence). */ static const sipTypeDef *convertSubClass(const sipTypeDef *td, void **cppPtr) { /* Handle the trivial case. */ if (*cppPtr == NULL) return NULL; /* Try the conversions until told to stop. */ while (convertPass(&td, cppPtr)) ; return td; } /* * Do a single pass through the available convertors. */ static int convertPass(const sipTypeDef **tdp, void **cppPtr) { PyTypeObject *py_type = sipTypeAsPyTypeObject(*tdp); sipExportedModuleDef *em; /* * Note that this code depends on the fact that a module appears in the * list of modules before any module it imports, ie. sub-class convertors * will be invoked for more specific types first. */ for (em = moduleList; em != NULL; em = em->em_next) { sipSubClassConvertorDef *scc; if ((scc = em->em_convertors) == NULL) continue; while (scc->scc_convertor != NULL) { PyTypeObject *base_type = sipTypeAsPyTypeObject(scc->scc_basetype); /* * The base type is the "root" class that may have a number of * convertors each handling a "branch" of the derived tree of * classes. The "root" normally implements the base function that * provides the RTTI used by the convertors and is re-implemented * by derived classes. We therefore see if the target type is a * sub-class of the root, ie. see if the convertor might be able to * convert the target type to something more specific. */ if (PyType_IsSubtype(py_type, base_type)) { void *ptr; const sipTypeDef *sub_td; ptr = cast_cpp_ptr(*cppPtr, py_type, scc->scc_basetype); if ((sub_td = (*scc->scc_convertor)(&ptr)) != NULL) { PyTypeObject *sub_type = sipTypeAsPyTypeObject(sub_td); /* * We are only interested in types that are not * super-classes of the target. This happens either * because it is in an earlier convertor than the one that * handles the type or it is in a later convertor that * handles a different branch of the hierarchy. Either * way, the ordering of the modules ensures that there will * be no more than one and that it will be the right one. */ if (!PyType_IsSubtype(py_type, sub_type)) { *tdp = sub_td; *cppPtr = ptr; /* * Finally we allow the convertor to return a type that * is apparently unrelated to the current convertor. * This causes the whole process to be restarted with * the new values. The use case is PyQt's QLayoutItem. */ return !PyType_IsSubtype(sub_type, base_type); } } } ++scc; } } /* * We haven't found the exact type, so return the most specific type that * it must be. This can happen legitimately if the wrapped library is * returning an internal class that is down-cast to a more generic class. * Also we want this function to be safe when a class doesn't have any * convertors. */ return FALSE; } /* * The bsearch() helper function for searching a sorted string map table. */ static int compareStringMapEntry(const void *key,const void *el) { return strcmp((const char *)key,((const sipStringTypeClassMap *)el)->typeString); } /* * A convenience function for %ConvertToSubClassCode for types represented as a * string. Returns the Python class object or NULL if the type wasn't * recognised. This is deprecated. */ static sipWrapperType *sip_api_map_string_to_class(const char *typeString, const sipStringTypeClassMap *map, int maplen) { sipStringTypeClassMap *me; me = (sipStringTypeClassMap *)bsearch((const void *)typeString, (const void *)map,maplen, sizeof (sipStringTypeClassMap), compareStringMapEntry); return ((me != NULL) ? *me->pyType : NULL); } /* * The bsearch() helper function for searching a sorted integer map table. */ static int compareIntMapEntry(const void *keyp,const void *el) { int key = *(int *)keyp; if (key > ((const sipIntTypeClassMap *)el)->typeInt) return 1; if (key < ((const sipIntTypeClassMap *)el)->typeInt) return -1; return 0; } /* * A convenience function for %ConvertToSubClassCode for types represented as * an integer. Returns the Python class object or NULL if the type wasn't * recognised. This is deprecated. */ static sipWrapperType *sip_api_map_int_to_class(int typeInt, const sipIntTypeClassMap *map, int maplen) { sipIntTypeClassMap *me; me = (sipIntTypeClassMap *)bsearch((const void *)&typeInt, (const void *)map,maplen, sizeof (sipIntTypeClassMap), compareIntMapEntry); return ((me != NULL) ? *me->pyType : NULL); } /* * Raise an unknown exception. Make no assumptions about the GIL. */ static void sip_api_raise_unknown_exception(void) { static PyObject *mobj = NULL; SIP_BLOCK_THREADS objectify("unknown", &mobj); PyErr_SetObject(PyExc_Exception, mobj); SIP_UNBLOCK_THREADS } /* * Raise an exception implemented as a type. Make no assumptions about the * GIL. */ static void sip_api_raise_type_exception(const sipTypeDef *td, void *ptr) { PyObject *self; assert(sipTypeIsClass(td)); SIP_BLOCK_THREADS self = wrap_simple_instance(ptr, td, NULL, SIP_PY_OWNED); PyErr_SetObject((PyObject *)sipTypeAsPyTypeObject(td), self); Py_XDECREF(self); SIP_UNBLOCK_THREADS } /* * Return the generated type structure of an encoded type. */ static sipTypeDef *getGeneratedType(const sipEncodedTypeDef *enc, sipExportedModuleDef *em) { if (enc->sc_module == 255) return em->em_types[enc->sc_type]; return em->em_imports[enc->sc_module].im_imported_types[enc->sc_type].it_td; } /* * Return the generated class type structure of a class's super-class. */ sipClassTypeDef *sipGetGeneratedClassType(const sipEncodedTypeDef *enc, const sipClassTypeDef *ctd) { return (sipClassTypeDef *)getGeneratedType(enc, ctd->ctd_base.td_module); } /* * Find a particular slot function for a type. */ static void *findSlot(PyObject *self, sipPySlotType st) { void *slot; PyTypeObject *py_type = Py_TYPE(self); /* See if it is a wrapper. */ if (PyObject_TypeCheck((PyObject *)py_type, &sipWrapperType_Type)) { const sipClassTypeDef *ctd; ctd = (sipClassTypeDef *)((sipWrapperType *)(py_type))->wt_td; slot = findSlotInClass(ctd, st); } else { sipEnumTypeDef *etd; /* If it is not a wrapper then it must be an enum. */ assert(PyObject_TypeCheck((PyObject *)py_type, &sipEnumType_Type)); etd = (sipEnumTypeDef *)((sipEnumTypeObject *)(py_type))->type; assert(etd->etd_pyslots != NULL); slot = findSlotInSlotList(etd->etd_pyslots, st); } return slot; } /* * Find a particular slot function in a class hierarchy. */ static void *findSlotInClass(const sipClassTypeDef *ctd, sipPySlotType st) { void *slot; if (ctd->ctd_pyslots != NULL) slot = findSlotInSlotList(ctd->ctd_pyslots, st); else slot = NULL; if (slot == NULL) { sipEncodedTypeDef *sup; /* Search any super-types. */ if ((sup = ctd->ctd_supers) != NULL) { do { const sipClassTypeDef *sup_ctd = sipGetGeneratedClassType( sup, ctd); slot = findSlotInClass(sup_ctd, st); } while (slot == NULL && !sup++->sc_flag); } } return slot; } /* * Find a particular slot function in a particular type. */ static void *findSlotInSlotList(sipPySlotDef *psd, sipPySlotType st) { while (psd->psd_func != NULL) { if (psd->psd_type == st) return psd->psd_func; ++psd; } return NULL; } /* * Return the C/C++ address and the generated class structure for a wrapper. */ static void *getPtrTypeDef(sipSimpleWrapper *self, const sipClassTypeDef **ctd) { *ctd = (const sipClassTypeDef *)((sipWrapperType *)Py_TYPE(self))->wt_td; return (sipNotInMap(self) ? NULL : sip_api_get_address(self)); } /* * Handle an objobjargproc slot. */ static int objobjargprocSlot(PyObject *self, PyObject *arg1, PyObject *arg2, sipPySlotType st) { int (*f)(PyObject *, PyObject *); int res; f = (int (*)(PyObject *, PyObject *))findSlot(self, st); if (f != NULL) { PyObject *args; /* * Slot handlers require a single PyObject *. The second argument is * optional. */ if (arg2 == NULL) { args = arg1; Py_INCREF(args); } else if ((args = PyTuple_Pack(2, arg1, arg2)) == NULL) { return -1; } res = f(self, args); Py_DECREF(args); } else { PyErr_SetNone(PyExc_NotImplementedError); res = -1; } return res; } /* * Handle an ssizeobjargproc slot. */ static int ssizeobjargprocSlot(PyObject *self, Py_ssize_t arg1, PyObject *arg2, sipPySlotType st) { int (*f)(PyObject *, PyObject *); int res; f = (int (*)(PyObject *, PyObject *))findSlot(self, st); if (f != NULL) { PyObject *args; /* * Slot handlers require a single PyObject *. The second argument is * optional. */ if (arg2 == NULL) args = PyLong_FromSsize_t(arg1); else args = Py_BuildValue("(nO)", arg1, arg2); if (args == NULL) return -1; res = f(self, args); Py_DECREF(args); } else { PyErr_SetNone(PyExc_NotImplementedError); res = -1; } return res; } /* * The metatype alloc slot. */ static PyObject *sipWrapperType_alloc(PyTypeObject *self, Py_ssize_t nitems) { PyObject *o; /* Call the standard super-metatype alloc. */ if ((o = PyType_Type.tp_alloc(self, nitems)) == NULL) return NULL; /* * Consume any extra type specific information and use it to initialise the * slots. This only happens for directly wrapped classes (and not * programmer written sub-classes). This must be done in the alloc * function because it is the only place we can break out of the default * new() function before PyType_Ready() is called. */ if (currentType != NULL) { assert(!sipTypeIsEnum(currentType)); ((sipWrapperType *)o)->wt_td = currentType; if (sipTypeIsClass(currentType)) { const sipClassTypeDef *ctd = (const sipClassTypeDef *)currentType; const char *docstring = ctd->ctd_docstring; /* * Skip the marker that identifies the docstring as being * automatically generated. */ if (docstring != NULL && *docstring == AUTO_DOCSTRING) ++docstring; ((PyTypeObject *)o)->tp_doc = docstring; addClassSlots((sipWrapperType *)o, ctd); /* Patch any mixin initialiser. */ if (ctd->ctd_init_mixin != NULL) ((PyTypeObject *)o)->tp_init = ctd->ctd_init_mixin; } } return o; } /* * The metatype init slot. */ static int sipWrapperType_init(sipWrapperType *self, PyObject *args, PyObject *kwds) { /* Call the standard super-metatype init. */ if (PyType_Type.tp_init((PyObject *)self, args, kwds) < 0) return -1; /* * If we don't yet have any extra type specific information (because we are * a programmer defined sub-class) then get it from the (first) super-type. */ if (self->wt_td == NULL) { PyTypeObject *base = ((PyTypeObject *)self)->tp_base; self->wt_user_type = TRUE; /* * We allow the class to use this as a meta-type without being derived * from a class that uses it. This allows mixin classes that need * their own meta-type to work so long as their meta-type is derived * from this meta-type. This condition is indicated by the pointer to * the generated type structure being NULL. */ if (base != NULL && PyObject_TypeCheck((PyObject *)base, (PyTypeObject *)&sipWrapperType_Type)) { /* TODO: Deprecate this mechanism in favour of an event handler. */ sipNewUserTypeFunc new_user_type_handler; self->wt_td = ((sipWrapperType *)base)->wt_td; if (self->wt_td != NULL) { /* Call any new type handler. */ new_user_type_handler = find_new_user_type_handler( (sipWrapperType *)sipTypeAsPyTypeObject(self->wt_td)); if (new_user_type_handler != NULL) if (new_user_type_handler(self) < 0) return -1; } } } else { /* * We must be a generated type so remember the type object in the * generated type structure. */ assert(self->wt_td->td_py_type == NULL); self->wt_td->td_py_type = (PyTypeObject *)self; } return 0; } /* * The metatype getattro slot. */ static PyObject *sipWrapperType_getattro(PyObject *self, PyObject *name) { if (add_all_lazy_attrs(((sipWrapperType *)self)->wt_td) < 0) return NULL; return PyType_Type.tp_getattro(self, name); } /* * The metatype setattro slot. */ static int sipWrapperType_setattro(PyObject *self, PyObject *name, PyObject *value) { if (add_all_lazy_attrs(((sipWrapperType *)self)->wt_td) < 0) return -1; return PyType_Type.tp_setattro(self, name, value); } /* * The instance new slot. */ static PyObject *sipSimpleWrapper_new(sipWrapperType *wt, PyObject *args, PyObject *kwds) { sipTypeDef *td = wt->wt_td; (void)args; (void)kwds; /* Check the base types are not being used directly. */ if (wt == &sipSimpleWrapper_Type || wt == &sipWrapper_Type) { PyErr_Format(PyExc_TypeError, "the %s type cannot be instantiated or sub-classed", ((PyTypeObject *)wt)->tp_name); return NULL; } if (add_all_lazy_attrs(td) < 0) return NULL; /* See if it is a mapped type. */ if (sipTypeIsMapped(td)) { PyErr_Format(PyExc_TypeError, "%s.%s represents a mapped type and cannot be instantiated", sipNameOfModule(td->td_module), sipPyNameOfContainer(get_container(td), td)); return NULL; } /* See if it is a namespace. */ if (sipTypeIsNamespace(td)) { PyErr_Format(PyExc_TypeError, "%s.%s represents a C++ namespace and cannot be instantiated", sipNameOfModule(td->td_module), sipPyNameOfContainer(get_container(td), td)); return NULL; } /* * See if the object is being created explicitly rather than being wrapped. */ if (!sipIsPending()) { /* * See if it cannot be instantiated or sub-classed from Python, eg. * it's an opaque class. Some restrictions might be overcome with * better SIP support. */ if (((sipClassTypeDef *)td)->ctd_init == NULL) { PyErr_Format(PyExc_TypeError, "%s.%s cannot be instantiated or sub-classed", sipNameOfModule(td->td_module), sipPyNameOfContainer(get_container(td), td)); return NULL; } /* See if it is an abstract type. */ if (sipTypeIsAbstract(td) && !wt->wt_user_type && ((sipClassTypeDef *)td)->ctd_init_mixin == NULL) { PyErr_Format(PyExc_TypeError, "%s.%s represents a C++ abstract class and cannot be instantiated", sipNameOfModule(td->td_module), sipPyNameOfContainer(get_container(td), td)); return NULL; } } /* Call the standard super-type new. */ return PyBaseObject_Type.tp_new((PyTypeObject *)wt, empty_tuple, NULL); } /* * The instance init slot. */ static int sipSimpleWrapper_init(sipSimpleWrapper *self, PyObject *args, PyObject *kwds) { void *sipNew; int sipFlags, from_cpp = TRUE; sipWrapper *owner; sipWrapperType *wt = (sipWrapperType *)Py_TYPE(self); sipTypeDef *td = wt->wt_td; sipClassTypeDef *ctd = (sipClassTypeDef *)td; PyObject *unused = NULL; sipFinalFunc final_func = find_finalisation(ctd); /* Check for an existing C++ instance waiting to be wrapped. */ if (sipGetPending(&sipNew, &owner, &sipFlags) < 0) return -1; if (sipNew == NULL) { PyObject *parseErr = NULL, **unused_p = NULL; /* See if we are interested in any unused keyword arguments. */ if (sipTypeCallSuperInit(&ctd->ctd_base) || final_func != NULL || kw_handler != NULL) unused_p = &unused; /* Call the C++ ctor. */ owner = NULL; sipNew = ctd->ctd_init(self, args, kwds, unused_p, (PyObject **)&owner, &parseErr); if (sipNew != NULL) { sipFlags = SIP_DERIVED_CLASS; } else if (parseErr == NULL) { /* * The C++ ctor must have raised an exception which has been * translated to a Python exception. */ return -1; } else { sipInitExtenderDef *ie = wt->wt_iextend; /* * If we have not found an appropriate overload then try any * extenders. */ while (PyList_Check(parseErr) && ie != NULL) { sipNew = ie->ie_extender(self, args, kwds, &unused, (PyObject **)&owner, &parseErr); if (sipNew != NULL) break; ie = ie->ie_next; } if (sipNew == NULL) { const char *docstring = ctd->ctd_docstring; /* * Use the docstring for errors if it was automatically * generated. */ if (docstring != NULL) { if (*docstring == AUTO_DOCSTRING) ++docstring; else docstring = NULL; } sip_api_no_function(parseErr, sipPyNameOfContainer(&ctd->ctd_container, td), docstring); return -1; } sipFlags = 0; } if (owner == NULL) sipFlags |= SIP_PY_OWNED; else if ((PyObject *)owner == Py_None) { /* This is the hack that means that C++ owns the new instance. */ sipFlags |= SIP_CPP_HAS_REF; Py_INCREF(self); owner = NULL; } /* The instance was created from Python. */ from_cpp = FALSE; } /* Handler any owner if the type supports the concept. */ if (PyObject_TypeCheck((PyObject *)self, (PyTypeObject *)&sipWrapper_Type)) { /* * The application may be doing something very unadvisable (like * calling __init__() for a second time), so make sure we don't already * have a parent. */ removeFromParent((sipWrapper *)self); if (owner != NULL) { assert(PyObject_TypeCheck((PyObject *)owner, (PyTypeObject *)&sipWrapper_Type)); addToParent((sipWrapper *)self, (sipWrapper *)owner); } } self->data = sipNew; self->sw_flags = sipFlags | SIP_CREATED; /* Set the access function. */ if (sipIsAccessFunc(self)) self->access_func = explicit_access_func; else if (sipIsIndirect(self)) self->access_func = indirect_access_func; else self->access_func = NULL; if (!sipNotInMap(self)) sipOMAddObject(&cppPyMap, self); /* If we are wrapping an instance returned from C/C++ then we are done. */ if (from_cpp) { /* * Invoke any event handlers for instances that are accessed directly. */ if (self->access_func == NULL) { sipEventHandler *eh; for (eh = event_handlers[sipEventWrappedInstance]; eh != NULL; eh = eh->next) { if (is_subtype(ctd, eh->ctd)) { sipWrappedInstanceEventHandler handler = (sipWrappedInstanceEventHandler)eh->handler; handler(sipNew); } } } return 0; } /* Call any finalisation code. */ if (final_func != NULL) { PyObject *new_unused = NULL, **new_unused_p; if (unused == NULL || unused != kwds) { /* * There are no unused arguments or we have already created a dict * containing the unused sub-set, so there is no need to create * another. */ new_unused_p = NULL; } else { /* * All of the keyword arguments are unused, so if some of them are * now going to be used then a new dict will be needed. */ new_unused_p = &new_unused; } if (final_func((PyObject *)self, sipNew, unused, new_unused_p) < 0) { Py_XDECREF(unused); return -1; } if (new_unused != NULL) { Py_DECREF(unused); unused = new_unused; } } /* Call the handler if we have one. */ if (kw_handler != NULL && unused != NULL && isQObject((PyObject *)self)) { int rc = kw_handler((PyObject *)self, sipNew, unused); /* * A handler will always consume all unused keyword arguments (or raise * an exception) so discard the dict now. */ Py_DECREF(unused); if (rc < 0) return -1; unused = NULL; } /* See if we should call the equivalent of super().__init__(). */ if (sipTypeCallSuperInit(&ctd->ctd_base)) { PyObject *next; /* Find the next type in the MRO. */ next = next_in_mro((PyObject *)self, (PyObject *)&sipSimpleWrapper_Type); /* * If the next type in the MRO is object then take a shortcut by not * calling super().__init__() but emulating object.__init__() instead. * This will be the most common case and also allows us to generate a * better exception message if there are unused keyword arguments. The * disadvantage is that the exception message will be different if * there is a mixin. */ if (next != (PyObject *)&PyBaseObject_Type) { int rc = super_init((PyObject *)self, empty_tuple, unused, next); Py_XDECREF(unused); return rc; } } if (unused_backdoor != NULL) { /* * We are being called by a mixin's __init__ so save any unused * arguments for it to pass on to the main class's __init__. */ *unused_backdoor = unused; } else if (unused != NULL) { /* We shouldn't have any unused keyword arguments. */ if (PyDict_Size(unused) != 0) { PyObject *key, *value; Py_ssize_t pos = 0; /* Just report one of the unused arguments. */ PyDict_Next(unused, &pos, &key, &value); PyErr_Format(PyExc_TypeError, "'%S' is an unknown keyword argument", key); Py_DECREF(unused); return -1; } Py_DECREF(unused); } return 0; } /* * Get the C++ address of a mixin. */ static void *sip_api_get_mixin_address(sipSimpleWrapper *w, const sipTypeDef *td) { PyObject *mixin; void *cpp; if ((mixin = PyObject_GetAttrString((PyObject *)w, sipTypeName(td))) == NULL) { PyErr_Clear(); return NULL; } cpp = sip_api_get_address((sipSimpleWrapper *)mixin); Py_DECREF(mixin); return cpp; } /* * Initialise a mixin. */ static int sip_api_init_mixin(PyObject *self, PyObject *args, PyObject *kwds, const sipClassTypeDef *ctd) { int rc; Py_ssize_t pos; PyObject *unused, *mixin, *mixin_name, *key, *value; PyTypeObject *self_wt = sipTypeAsPyTypeObject(((sipWrapperType *)Py_TYPE(self))->wt_td); PyTypeObject *wt = sipTypeAsPyTypeObject(&ctd->ctd_base); static PyObject *double_us = NULL; if (objectify("__", &double_us) < 0) return -1; /* If we are not a mixin to another wrapped class then behave as normal. */ if (PyType_IsSubtype(self_wt, wt)) return super_init(self, args, kwds, next_in_mro(self, (PyObject *)wt)); /* * Create the mixin instance. Retain the positional arguments for the * super-class. Remember that, even though the mixin appears after the * main class in the MRO, it appears before sipWrapperType where the main * class's arguments are actually parsed. */ unused = NULL; unused_backdoor = &unused; mixin = PyObject_Call((PyObject *)wt, empty_tuple, kwds); unused_backdoor = NULL; if (mixin == NULL) goto gc_unused; /* Make sure the mixin can find the main instance. */ ((sipSimpleWrapper *)mixin)->mixin_main = self; Py_INCREF(self); if ((mixin_name = PyUnicode_FromString(sipTypeName(&ctd->ctd_base))) == NULL) { Py_DECREF(mixin); goto gc_unused; } rc = PyObject_SetAttr(self, mixin_name, mixin); Py_DECREF(mixin); if (rc < 0) goto gc_mixin_name; /* Add the mixin's useful attributes to the main class. */ pos = 0; while (PyDict_Next(wt->tp_dict, &pos, &key, &value)) { /* Don't replace existing values. */ if (PyDict_Contains(Py_TYPE(self)->tp_dict, key) != 0) continue; /* Skip values with names that start with double underscore. */ if (!PyUnicode_Check(key)) continue; /* * Despite what the docs say this returns a Py_ssize_t - although the * docs are probably right. */ rc = (int)PyUnicode_Tailmatch(key, double_us, 0, 2, -1); if (rc < 0) goto gc_mixin_name; if (rc > 0) continue; if (PyObject_IsInstance(value, (PyObject *)&sipMethodDescr_Type)) { if ((value = sipMethodDescr_Copy(value, mixin_name)) == NULL) goto gc_mixin_name; } else if (PyObject_IsInstance(value, (PyObject *)&sipVariableDescr_Type)) { if ((value = sipVariableDescr_Copy(value, mixin_name)) == NULL) goto gc_mixin_name; } else { Py_INCREF(value); } rc = PyDict_SetItem(Py_TYPE(self)->tp_dict, key, value); Py_DECREF(value); if (rc < 0) goto gc_mixin_name; } Py_DECREF(mixin_name); /* Call the super-class's __init__ with any remaining arguments. */ rc = super_init(self, args, unused, next_in_mro(self, (PyObject *)wt)); Py_XDECREF(unused); return rc; gc_mixin_name: Py_DECREF(mixin_name); gc_unused: Py_XDECREF(unused); return -1; } /* * Return the next in the MRO of an instance after a given type. */ static PyObject *next_in_mro(PyObject *self, PyObject *after) { Py_ssize_t i; PyObject *mro; mro = Py_TYPE(self)->tp_mro; assert(PyTuple_Check(mro)); for (i = 0; i < PyTuple_GET_SIZE(mro); ++i) if (PyTuple_GET_ITEM(mro, i) == after) break; /* Assert that we have found ourself and that we are not the last. */ assert(i + 1 < PyTuple_GET_SIZE(mro)); return PyTuple_GET_ITEM(mro, i + 1); } /* * Call the equivalent of super()__init__() of an instance. */ static int super_init(PyObject *self, PyObject *args, PyObject *kwds, PyObject *type) { int i; PyObject *init, *init_args, *init_res; if ((init = PyObject_GetAttr(type, init_name)) == NULL) return -1; if ((init_args = PyTuple_New(1 + PyTuple_GET_SIZE(args))) == NULL) { Py_DECREF(init); return -1; } PyTuple_SET_ITEM(init_args, 0, self); Py_INCREF(self); for (i = 0; i < PyTuple_GET_SIZE(args); ++i) { PyObject *arg = PyTuple_GET_ITEM(args, i); PyTuple_SET_ITEM(init_args, 1 + i, arg); Py_INCREF(arg); } init_res = PyObject_Call(init, init_args, kwds); Py_DECREF(init_args); Py_DECREF(init); Py_XDECREF(init_res); return (init_res != NULL) ? 0 : -1; } /* * Find any finalisation function for a class, searching its super-classes if * necessary. */ static sipFinalFunc find_finalisation(sipClassTypeDef *ctd) { sipEncodedTypeDef *sup; if (ctd->ctd_final != NULL) return ctd->ctd_final; if ((sup = ctd->ctd_supers) != NULL) do { sipClassTypeDef *sup_ctd = sipGetGeneratedClassType(sup, ctd); sipFinalFunc func; if ((func = find_finalisation(sup_ctd)) != NULL) return func; } while (!sup++->sc_flag); return NULL; } /* * Find any new user type handler function for a class, searching its * super-classes if necessary. */ static sipNewUserTypeFunc find_new_user_type_handler(sipWrapperType *wt) { sipEncodedTypeDef *sup; sipClassTypeDef *ctd; if (wt->wt_new_user_type_handler != NULL) return wt->wt_new_user_type_handler; ctd = (sipClassTypeDef *)wt->wt_td; if ((sup = ctd->ctd_supers) != NULL) { do { sipTypeDef *sup_td = getGeneratedType(sup, ctd->ctd_base.td_module); sipNewUserTypeFunc func; wt = (sipWrapperType *)sipTypeAsPyTypeObject(sup_td); if ((func = find_new_user_type_handler(wt)) != NULL) return func; } while (!sup++->sc_flag); } return NULL; } /* * The instance traverse slot. */ static int sipSimpleWrapper_traverse(sipSimpleWrapper *self, visitproc visit, void *arg) { int vret; void *ptr; const sipClassTypeDef *ctd; /* Call any handwritten traverse code. */ if ((ptr = getPtrTypeDef(self, &ctd)) != NULL) if (ctd->ctd_traverse != NULL) if ((vret = ctd->ctd_traverse(ptr, visit, arg)) != 0) return vret; if (self->dict != NULL) if ((vret = visit(self->dict, arg)) != 0) return vret; if (self->extra_refs != NULL) if ((vret = visit(self->extra_refs, arg)) != 0) return vret; if (self->user != NULL) if ((vret = visit(self->user, arg)) != 0) return vret; if (self->mixin_main != NULL) if ((vret = visit(self->mixin_main, arg)) != 0) return vret; return 0; } /* * The instance clear slot. */ static int sipSimpleWrapper_clear(sipSimpleWrapper *self) { int vret = 0; void *ptr; const sipClassTypeDef *ctd; PyObject *tmp; /* Call any handwritten clear code. */ if ((ptr = getPtrTypeDef(self, &ctd)) != NULL) if (ctd->ctd_clear != NULL) vret = ctd->ctd_clear(ptr); /* Remove the instance dictionary. */ tmp = self->dict; self->dict = NULL; Py_XDECREF(tmp); /* Remove any extra references dictionary. */ tmp = self->extra_refs; self->extra_refs = NULL; Py_XDECREF(tmp); /* Remove any user object. */ tmp = self->user; self->user = NULL; Py_XDECREF(tmp); /* Remove any mixin main. */ tmp = self->mixin_main; self->mixin_main = NULL; Py_XDECREF(tmp); return vret; } /* * The instance get buffer slot. */ static int sipSimpleWrapper_getbuffer(sipSimpleWrapper *self, Py_buffer *buf, int flags) { void *ptr; const sipClassTypeDef *ctd; if ((ptr = getPtrTypeDef(self, &ctd)) == NULL) return -1; if (sipTypeUseLimitedAPI(&ctd->ctd_base)) { sipGetBufferFuncLimited getbuffer = (sipGetBufferFuncLimited)ctd->ctd_getbuffer; sipBufferDef bd; /* * Ensure all fields have a default value. This means that extra * fields can be appended in the future that older handwritten code * doesn't know about. */ memset(&bd, 0, sizeof(sipBufferDef)); if (getbuffer((PyObject *)self, ptr, &bd) < 0) return -1; return PyBuffer_FillInfo(buf, (PyObject *)self, bd.bd_buffer, bd.bd_length, bd.bd_readonly, flags); } return ctd->ctd_getbuffer((PyObject *)self, ptr, buf, flags); } /* * The instance release buffer slot. */ static void sipSimpleWrapper_releasebuffer(sipSimpleWrapper *self, Py_buffer *buf) { void *ptr; const sipClassTypeDef *ctd; if ((ptr = getPtrTypeDef(self, &ctd)) == NULL) return; if (sipTypeUseLimitedAPI(&ctd->ctd_base)) { sipReleaseBufferFuncLimited releasebuffer = (sipReleaseBufferFuncLimited)ctd->ctd_releasebuffer; releasebuffer((PyObject *)self, ptr); return; } ctd->ctd_releasebuffer((PyObject *)self, ptr, buf); } /* * The instance dealloc slot. */ static void sipSimpleWrapper_dealloc(sipSimpleWrapper *self) { PyObject *error_type, *error_value, *error_traceback; /* Save the current exception, if any. */ PyErr_Fetch(&error_type, &error_value, &error_traceback); forgetObject(self); /* * Now that the C++ object no longer exists we can tidy up the Python * object. We used to do this first but that meant lambda slots were * removed too soon (if they were connected to QObject.destroyed()). */ sipSimpleWrapper_clear(self); /* Call the standard super-type dealloc. */ PyBaseObject_Type.tp_dealloc((PyObject *)self); /* Restore the saved exception. */ PyErr_Restore(error_type, error_value, error_traceback); } /* * The type call slot. */ static PyObject *slot_call(PyObject *self, PyObject *args, PyObject *kw) { PyObject *(*f)(PyObject *, PyObject *, PyObject *); f = (PyObject *(*)(PyObject *, PyObject *, PyObject *))findSlot(self, call_slot); assert(f != NULL); return f(self, args, kw); } /* * The sequence type item slot. */ static PyObject *slot_sq_item(PyObject *self, Py_ssize_t n) { PyObject *(*f)(PyObject *,PyObject *); PyObject *arg, *res; if ((arg = PyLong_FromSsize_t(n)) == NULL) return NULL; f = (PyObject *(*)(PyObject *,PyObject *))findSlot(self, getitem_slot); assert(f != NULL); res = f(self,arg); Py_DECREF(arg); return res; } /* * The mapping type assign subscript slot. */ static int slot_mp_ass_subscript(PyObject *self, PyObject *key, PyObject *value) { return objobjargprocSlot(self, key, value, (value != NULL ? setitem_slot : delitem_slot)); } /* * The sequence type assign item slot. */ static int slot_sq_ass_item(PyObject *self, Py_ssize_t i, PyObject *o) { return ssizeobjargprocSlot(self, i, o, (o != NULL ? setitem_slot : delitem_slot)); } /* * The type rich compare slot. */ static PyObject *slot_richcompare(PyObject *self, PyObject *arg, int op) { PyObject *(*f)(PyObject *,PyObject *); sipPySlotType st; /* Convert the operation to a slot type. */ switch (op) { case Py_LT: st = lt_slot; break; case Py_LE: st = le_slot; break; case Py_EQ: st = eq_slot; break; case Py_NE: st = ne_slot; break; case Py_GT: st = gt_slot; break; case Py_GE: st = ge_slot; break; default: /* Suppress a compiler warning. */ st = -1; } /* It might not exist if not all the above have been implemented. */ if ((f = (PyObject *(*)(PyObject *,PyObject *))findSlot(self, st)) == NULL) { Py_INCREF(Py_NotImplemented); return Py_NotImplemented; } return f(self, arg); } /* * The __dict__ getter. */ static PyObject *sipSimpleWrapper_get_dict(sipSimpleWrapper *sw, void *closure) { (void)closure; /* Create the dictionary if needed. */ if (sw->dict == NULL) { sw->dict = PyDict_New(); if (sw->dict == NULL) return NULL; } Py_INCREF(sw->dict); return sw->dict; } /* * The __dict__ setter. */ static int sipSimpleWrapper_set_dict(sipSimpleWrapper *sw, PyObject *value, void *closure) { (void)closure; /* Check that any new value really is a dictionary. */ if (value != NULL && !PyDict_Check(value)) { PyErr_Format(PyExc_TypeError, "__dict__ must be set to a dictionary, not a '%s'", Py_TYPE(value)->tp_name); return -1; } Py_XDECREF(sw->dict); Py_XINCREF(value); sw->dict = value; return 0; } /* * The table of getters and setters. */ static PyGetSetDef sipSimpleWrapper_getset[] = { {(char *)"__dict__", (getter)sipSimpleWrapper_get_dict, (setter)sipSimpleWrapper_set_dict, NULL, NULL}, {NULL, NULL, NULL, NULL, NULL} }; /* * The type data structure. Note that we pretend to be a mapping object and a * sequence object at the same time. Python will choose one over another, * depending on the context, but we implement as much as we can and don't make * assumptions about which Python will choose. */ sipWrapperType sipSimpleWrapper_Type = { #if !defined(STACKLESS) { #endif { PyVarObject_HEAD_INIT(&sipWrapperType_Type, 0) "sip.simplewrapper", /* tp_name */ sizeof (sipSimpleWrapper), /* tp_basicsize */ 0, /* tp_itemsize */ (destructor)sipSimpleWrapper_dealloc, /* tp_dealloc */ 0, /* tp_print */ 0, /* tp_getattr */ 0, /* tp_setattr */ 0, /* tp_as_async (Python v3.5), tp_compare (Python v2) */ 0, /* tp_repr */ 0, /* tp_as_number */ 0, /* tp_as_sequence */ 0, /* tp_as_mapping */ 0, /* tp_hash */ 0, /* tp_call */ 0, /* tp_str */ 0, /* tp_getattro */ 0, /* tp_setattro */ 0, /* tp_as_buffer */ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_GC, /* tp_flags */ 0, /* tp_doc */ (traverseproc)sipSimpleWrapper_traverse, /* tp_traverse */ (inquiry)sipSimpleWrapper_clear, /* tp_clear */ 0, /* tp_richcompare */ 0, /* tp_weaklistoffset */ 0, /* tp_iter */ 0, /* tp_iternext */ 0, /* tp_methods */ 0, /* tp_members */ sipSimpleWrapper_getset, /* tp_getset */ 0, /* tp_base */ 0, /* tp_dict */ 0, /* tp_descr_get */ 0, /* tp_descr_set */ offsetof(sipSimpleWrapper, dict), /* tp_dictoffset */ (initproc)sipSimpleWrapper_init, /* tp_init */ 0, /* tp_alloc */ (newfunc)sipSimpleWrapper_new, /* tp_new */ 0, /* tp_free */ 0, /* tp_is_gc */ 0, /* tp_bases */ 0, /* tp_mro */ 0, /* tp_cache */ 0, /* tp_subclasses */ 0, /* tp_weaklist */ 0, /* tp_del */ 0, /* tp_version_tag */ 0, /* tp_finalize */ 0, /* tp_vectorcall */ }, { 0, /* am_await */ 0, /* am_aiter */ 0, /* am_anext */ }, { 0, /* nb_add */ 0, /* nb_subtract */ 0, /* nb_multiply */ 0, /* nb_remainder */ 0, /* nb_divmod */ 0, /* nb_power */ 0, /* nb_negative */ 0, /* nb_positive */ 0, /* nb_absolute */ 0, /* nb_bool */ 0, /* nb_invert */ 0, /* nb_lshift */ 0, /* nb_rshift */ 0, /* nb_and */ 0, /* nb_xor */ 0, /* nb_or */ 0, /* nb_int */ 0, /* nb_reserved */ 0, /* nb_float */ 0, /* nb_inplace_add */ 0, /* nb_inplace_subtract */ 0, /* nb_inplace_multiply */ 0, /* nb_inplace_remainder */ 0, /* nb_inplace_power */ 0, /* nb_inplace_lshift */ 0, /* nb_inplace_rshift */ 0, /* nb_inplace_and */ 0, /* nb_inplace_xor */ 0, /* nb_inplace_or */ 0, /* nb_floor_divide */ 0, /* nb_true_divide */ 0, /* nb_inplace_floor_divide */ 0, /* nb_inplace_true_divide */ 0, /* nb_index */ 0, /* nb_matrix_multiply */ 0, /* nb_inplace_matrix_multiply */ }, { 0, /* mp_length */ 0, /* mp_subscript */ 0, /* mp_ass_subscript */ }, { 0, /* sq_length */ 0, /* sq_concat */ 0, /* sq_repeat */ 0, /* sq_item */ 0, /* was_sq_slice */ 0, /* sq_ass_item */ 0, /* was_sq_ass_slice */ 0, /* sq_contains */ 0, /* sq_inplace_concat */ 0, /* sq_inplace_repeat */ }, { 0, /* bf_getbuffer */ 0, /* bf_releasebuffer */ }, 0, /* ht_name */ 0, /* ht_slots */ 0, /* ht_qualname */ 0, /* ht_cached_keys */ #if PY_VERSION_HEX >= 0x03090000 0, /* ht_module */ #endif #if !defined(STACKLESS) }, #endif 0, /* wt_user_type */ 0, /* wt_dict_complete */ 0, /* wt_unused */ 0, /* wt_td */ 0, /* wt_iextend */ 0, /* wt_new_user_type_handler */ 0, /* wt_user_data */ }; /* * The wrapper clear slot. */ static int sipWrapper_clear(sipWrapper *self) { int vret; sipSimpleWrapper *sw = (sipSimpleWrapper *)self; vret = sipSimpleWrapper_clear(sw); /* Remove any slots connected via a proxy. */ if (sipQtSupport != NULL && sipPossibleProxy(sw) && !sipNotInMap(sw)) { void *tx = sip_api_get_address(sw); if (tx != NULL) { sipSlot *slot; void *context = NULL; assert (sipQtSupport->qt_find_sipslot); while ((slot = sipQtSupport->qt_find_sipslot(tx, &context)) != NULL) { sip_api_clear_any_slot_reference(slot); if (context == NULL) break; } } } /* Detach any children (which will be owned by C/C++). */ detachChildren(self); return vret; } /* * The wrapper dealloc slot. */ static void sipWrapper_dealloc(sipWrapper *self) { PyObject *error_type, *error_value, *error_traceback; /* Save the current exception, if any. */ PyErr_Fetch(&error_type, &error_value, &error_traceback); /* * We can't simply call the super-type because things have to be done in a * certain order. The first thing is to get rid of the wrapped instance. */ forgetObject((sipSimpleWrapper *)self); sipWrapper_clear(self); /* Skip the super-type's dealloc. */ PyBaseObject_Type.tp_dealloc((PyObject *)self); /* Restore the saved exception. */ PyErr_Restore(error_type, error_value, error_traceback); } /* * The wrapper traverse slot. */ static int sipWrapper_traverse(sipWrapper *self, visitproc visit, void *arg) { int vret; sipSimpleWrapper *sw = (sipSimpleWrapper *)self; sipWrapper *w; if ((vret = sipSimpleWrapper_traverse(sw, visit, arg)) != 0) return vret; /* * This should be handwritten code in PyQt. The map check is a bit of a * hack to work around PyQt4 problems with qApp and a user created * instance. qt_find_sipslot() will return the same slot information for * both causing the gc module to trigger assert() failures. */ if (sipQtSupport != NULL && sipQtSupport->qt_find_sipslot && !sipNotInMap(sw)) { void *tx = sip_api_get_address(sw); if (tx != NULL) { sipSlot *slot; void *context = NULL; while ((slot = sipQtSupport->qt_find_sipslot(tx, &context)) != NULL) { if ((vret = sip_api_visit_slot(slot, visit, arg)) != 0) return vret; if (context == NULL) break; } } } for (w = self->first_child; w != NULL; w = w->sibling_next) { /* * We don't traverse if the wrapper is a child of itself. We do this * so that wrapped objects returned by virtual methods with the * /Factory/ don't have those objects collected. This then means that * plugins implemented in Python have a chance of working. */ if (w != self) if ((vret = visit((PyObject *)w, arg)) != 0) return vret; } return 0; } /* * Add the slots for a class type and all its super-types. */ static void addClassSlots(sipWrapperType *wt, const sipClassTypeDef *ctd) { PyHeapTypeObject *heap_to = &wt->super; PyBufferProcs *bp = &heap_to->as_buffer; /* Add the buffer interface. */ if (ctd->ctd_getbuffer != NULL) bp->bf_getbuffer = (getbufferproc)sipSimpleWrapper_getbuffer; if (ctd->ctd_releasebuffer != NULL) bp->bf_releasebuffer = (releasebufferproc)sipSimpleWrapper_releasebuffer; /* Add the slots for this type. */ if (ctd->ctd_pyslots != NULL) addTypeSlots(heap_to, ctd->ctd_pyslots); } /* * Add the slot handler for each slot present in the type. */ static void addTypeSlots(PyHeapTypeObject *heap_to, sipPySlotDef *slots) { PyTypeObject *to; PyNumberMethods *nb; PySequenceMethods *sq; PyMappingMethods *mp; PyAsyncMethods *am; void *f; to = &heap_to->ht_type; nb = &heap_to->as_number; sq = &heap_to->as_sequence; mp = &heap_to->as_mapping; am = &heap_to->as_async; while ((f = slots->psd_func) != NULL) switch (slots++->psd_type) { case str_slot: to->tp_str = (reprfunc)f; break; case int_slot: nb->nb_int = (unaryfunc)f; break; case float_slot: nb->nb_float = (unaryfunc)f; break; case len_slot: mp->mp_length = (lenfunc)f; sq->sq_length = (lenfunc)f; break; case contains_slot: sq->sq_contains = (objobjproc)f; break; case add_slot: nb->nb_add = (binaryfunc)f; break; case concat_slot: sq->sq_concat = (binaryfunc)f; break; case sub_slot: nb->nb_subtract = (binaryfunc)f; break; case mul_slot: nb->nb_multiply = (binaryfunc)f; break; case repeat_slot: sq->sq_repeat = (ssizeargfunc)f; break; case div_slot: nb->nb_true_divide = (binaryfunc)f; break; case mod_slot: nb->nb_remainder = (binaryfunc)f; break; case floordiv_slot: nb->nb_floor_divide = (binaryfunc)f; break; case truediv_slot: nb->nb_true_divide = (binaryfunc)f; break; case and_slot: nb->nb_and = (binaryfunc)f; break; case or_slot: nb->nb_or = (binaryfunc)f; break; case xor_slot: nb->nb_xor = (binaryfunc)f; break; case lshift_slot: nb->nb_lshift = (binaryfunc)f; break; case rshift_slot: nb->nb_rshift = (binaryfunc)f; break; case iadd_slot: nb->nb_inplace_add = (binaryfunc)f; break; case iconcat_slot: sq->sq_inplace_concat = (binaryfunc)f; break; case isub_slot: nb->nb_inplace_subtract = (binaryfunc)f; break; case imul_slot: nb->nb_inplace_multiply = (binaryfunc)f; break; case irepeat_slot: sq->sq_inplace_repeat = (ssizeargfunc)f; break; case idiv_slot: nb->nb_inplace_true_divide = (binaryfunc)f; break; case imod_slot: nb->nb_inplace_remainder = (binaryfunc)f; break; case ifloordiv_slot: nb->nb_inplace_floor_divide = (binaryfunc)f; break; case itruediv_slot: nb->nb_inplace_true_divide = (binaryfunc)f; break; case iand_slot: nb->nb_inplace_and = (binaryfunc)f; break; case ior_slot: nb->nb_inplace_or = (binaryfunc)f; break; case ixor_slot: nb->nb_inplace_xor = (binaryfunc)f; break; case ilshift_slot: nb->nb_inplace_lshift = (binaryfunc)f; break; case irshift_slot: nb->nb_inplace_rshift = (binaryfunc)f; break; case invert_slot: nb->nb_invert = (unaryfunc)f; break; case call_slot: to->tp_call = slot_call; break; case getitem_slot: mp->mp_subscript = (binaryfunc)f; sq->sq_item = slot_sq_item; break; case setitem_slot: case delitem_slot: mp->mp_ass_subscript = slot_mp_ass_subscript; sq->sq_ass_item = slot_sq_ass_item; break; case lt_slot: case le_slot: case eq_slot: case ne_slot: case gt_slot: case ge_slot: to->tp_richcompare = slot_richcompare; break; case bool_slot: nb->nb_bool = (inquiry)f; break; case neg_slot: nb->nb_negative = (unaryfunc)f; break; case repr_slot: to->tp_repr = (reprfunc)f; break; case hash_slot: to->tp_hash = (hashfunc)f; break; case pos_slot: nb->nb_positive = (unaryfunc)f; break; case abs_slot: nb->nb_absolute = (unaryfunc)f; break; case index_slot: nb->nb_index = (unaryfunc)f; break; case iter_slot: to->tp_iter = (getiterfunc)f; break; case next_slot: to->tp_iternext = (iternextfunc)f; break; case setattr_slot: to->tp_setattro = (setattrofunc)f; break; case matmul_slot: nb->nb_matrix_multiply = (binaryfunc)f; break; case imatmul_slot: nb->nb_inplace_matrix_multiply = (binaryfunc)f; break; case await_slot: am->am_await = (unaryfunc)f; break; case aiter_slot: am->am_aiter = (unaryfunc)f; break; case anext_slot: am->am_anext = (unaryfunc)f; break; /* Suppress a compiler warning. */ default: ; } } /* * Remove the object from the map and call the C/C++ dtor if we own the * instance. */ static void forgetObject(sipSimpleWrapper *sw) { sipEventHandler *eh; const sipClassTypeDef *ctd = (const sipClassTypeDef *)((sipWrapperType *)Py_TYPE(sw))->wt_td; /* Invoke any event handlers. */ for (eh = event_handlers[sipEventCollectingWrapper]; eh != NULL; eh = eh->next) { if (is_subtype(ctd, eh->ctd)) { sipCollectingWrapperEventHandler handler = (sipCollectingWrapperEventHandler)eh->handler; handler(sw); } } /* * This is needed because we might release the GIL when calling a C++ dtor. * Without it the cyclic garbage collector can be invoked from another * thread resulting in a crash. */ PyObject_GC_UnTrack((PyObject *)sw); /* * Remove the object from the map before calling the class specific dealloc * code. This code calls the C++ dtor and may result in further calls that * pass the instance as an argument. If this is still in the map then it's * reference count would be increased (to one) and bad things happen when * it drops back to zero again. (An example is PyQt events generated * during the dtor call being passed to an event filter implemented in * Python.) By removing it from the map first we ensure that a new Python * object is created. */ sipOMRemoveObject(&cppPyMap, sw); if (sipInterpreter != NULL || destroy_on_exit) { const sipClassTypeDef *ctd; if (getPtrTypeDef(sw, &ctd) != NULL && ctd->ctd_dealloc != NULL) ctd->ctd_dealloc(sw); } clear_access_func(sw); } /* * If the given name is that of a typedef then the corresponding type is * returned. */ static const char *sip_api_resolve_typedef(const char *name) { const sipExportedModuleDef *em; /* * Note that if the same name is defined as more than one type (which is * possible if more than one completely independent modules are being * used) then we might pick the wrong one. */ for (em = moduleList; em != NULL; em = em->em_next) { if (em->em_nrtypedefs > 0) { sipTypedefDef *tdd; tdd = (sipTypedefDef *)bsearch(name, em->em_typedefs, em->em_nrtypedefs, sizeof (sipTypedefDef), compareTypedefName); if (tdd != NULL) return tdd->tdd_type_name; } } return NULL; } /* * The bsearch() helper function for searching a sorted typedef table. */ static int compareTypedefName(const void *key, const void *el) { return strcmp((const char *)key, ((const sipTypedefDef *)el)->tdd_name); } /* * Add the given Python object to the given list. Return 0 if there was no * error. */ static int addPyObjectToList(sipPyObject **head, PyObject *object) { sipPyObject *po; if ((po = sip_api_malloc(sizeof (sipPyObject))) == NULL) return -1; po->object = object; po->next = *head; *head = po; return 0; } /* * Register a symbol with a name. A negative value is returned if the name was * already registered. */ static int sip_api_export_symbol(const char *name, void *sym) { sipSymbol *ss; if (sip_api_import_symbol(name) != NULL) return -1; if ((ss = sip_api_malloc(sizeof (sipSymbol))) == NULL) return -1; ss->name = name; ss->symbol = sym; ss->next = sipSymbolList; sipSymbolList = ss; return 0; } /* * Return the symbol registered with the given name. NULL is returned if the * name was not registered. */ static void *sip_api_import_symbol(const char *name) { sipSymbol *ss; for (ss = sipSymbolList; ss != NULL; ss = ss->next) if (strcmp(ss->name, name) == 0) return ss->symbol; return NULL; } /* * Visit a slot connected to an object for the cyclic garbage collector. This * would only be called externally by PyQt3. */ static int sip_api_visit_slot(sipSlot *slot, visitproc visit, void *arg) { /* See if the slot has an extra reference. */ if (slot->weakSlot == Py_True && slot->pyobj != Py_None) return visit(slot->pyobj, arg); return 0; } /* * Clear a slot if it has an extra reference to keep it alive. This would only * be called externally by PyQt3. */ static void sip_api_clear_any_slot_reference(sipSlot *slot) { if (slot->weakSlot == Py_True) { PyObject *xref = slot->pyobj; /* * Replace the slot with None. We don't use NULL as this has another * meaning. */ Py_INCREF(Py_None); slot->pyobj = Py_None; Py_DECREF(xref); } } /* * Convert a Python object to a character and raise an exception if there was * an error. */ static char sip_api_bytes_as_char(PyObject *obj) { char ch; if (parseBytes_AsChar(obj, &ch) < 0) { PyErr_Format(PyExc_TypeError, "bytes of length 1 expected not '%s'", Py_TYPE(obj)->tp_name); return '\0'; } return ch; } /* * Convert a Python object to a string and raise an exception if there was * an error. */ static const char *sip_api_bytes_as_string(PyObject *obj) { const char *a; if (parseBytes_AsString(obj, &a) < 0) { PyErr_Format(PyExc_TypeError, "bytes expected not '%s'", Py_TYPE(obj)->tp_name); return NULL; } return a; } /* * Convert a Python ASCII string object to a character and raise an exception * if there was an error. */ static char sip_api_string_as_ascii_char(PyObject *obj) { char ch; if (parseString_AsASCIIChar(obj, &ch) < 0) ch = '\0'; return ch; } /* * Parse an ASCII character and return it. */ static int parseString_AsASCIIChar(PyObject *obj, char *ap) { if (parseString_AsEncodedChar(PyUnicode_AsASCIIString(obj), obj, ap) < 0) { /* Use the exception set if it was an encoding error. */ if (!PyUnicode_Check(obj) || PyUnicode_GET_LENGTH(obj) != 1) PyErr_SetString(PyExc_TypeError, "bytes or ASCII string of length 1 expected"); return -1; } return 0; } /* * Convert a Python Latin-1 string object to a character and raise an exception * if there was an error. */ static char sip_api_string_as_latin1_char(PyObject *obj) { char ch; if (parseString_AsLatin1Char(obj, &ch) < 0) ch = '\0'; return ch; } /* * Parse a Latin-1 character and return it via a pointer. */ static int parseString_AsLatin1Char(PyObject *obj, char *ap) { if (parseString_AsEncodedChar(PyUnicode_AsLatin1String(obj), obj, ap) < 0) { /* Use the exception set if it was an encoding error. */ if (!PyUnicode_Check(obj) || PyUnicode_GET_LENGTH(obj) != 1) PyErr_SetString(PyExc_TypeError, "bytes or Latin-1 string of length 1 expected"); return -1; } return 0; } /* * Convert a Python UTF-8 string object to a character and raise an exception * if there was an error. */ static char sip_api_string_as_utf8_char(PyObject *obj) { char ch; if (parseString_AsUTF8Char(obj, &ch) < 0) ch = '\0'; return ch; } /* * Parse a UTF-8 character and return it. */ static int parseString_AsUTF8Char(PyObject *obj, char *ap) { if (parseString_AsEncodedChar(PyUnicode_AsUTF8String(obj), obj, ap) < 0) { /* Use the exception set if it was an encoding error. */ if (!PyUnicode_Check(obj) || PyUnicode_GET_LENGTH(obj) != 1) PyErr_SetString(PyExc_TypeError, "bytes or UTF-8 string of length 1 expected"); return -1; } return 0; } /* * Parse an encoded character and return it. */ static int parseString_AsEncodedChar(PyObject *bytes, PyObject *obj, char *ap) { Py_ssize_t size; if (bytes == NULL) { PyErr_Clear(); return parseBytes_AsChar(obj, ap); } size = PyBytes_GET_SIZE(bytes); if (size != 1) { Py_DECREF(bytes); return -1; } if (ap != NULL) *ap = *PyBytes_AS_STRING(bytes); Py_DECREF(bytes); return 0; } /* * Convert a Python ASCII string object to a string and raise an exception if * there was an error. The object is updated with the one that owns the * string. Note that None is considered an error. */ static const char *sip_api_string_as_ascii_string(PyObject **obj) { PyObject *s = *obj; const char *a; if (s == Py_None || (*obj = parseString_AsASCIIString(s, &a)) == NULL) { /* Use the exception set if it was an encoding error. */ if (!PyUnicode_Check(s)) PyErr_Format(PyExc_TypeError, "bytes or ASCII string expected not '%s'", Py_TYPE(s)->tp_name); return NULL; } return a; } /* * Parse an ASCII string and return it and a new reference to the object that * owns the string. */ static PyObject *parseString_AsASCIIString(PyObject *obj, const char **ap) { return parseString_AsEncodedString(PyUnicode_AsASCIIString(obj), obj, ap); } /* * Convert a Python Latin-1 string object to a string and raise an exception if * there was an error. The object is updated with the one that owns the * string. Note that None is considered an error. */ static const char *sip_api_string_as_latin1_string(PyObject **obj) { PyObject *s = *obj; const char *a; if (s == Py_None || (*obj = parseString_AsLatin1String(s, &a)) == NULL) { /* Use the exception set if it was an encoding error. */ if (!PyUnicode_Check(s)) PyErr_Format(PyExc_TypeError, "bytes or Latin-1 string expected not '%s'", Py_TYPE(s)->tp_name); return NULL; } return a; } /* * Parse a Latin-1 string and return it and a new reference to the object that * owns the string. */ static PyObject *parseString_AsLatin1String(PyObject *obj, const char **ap) { return parseString_AsEncodedString(PyUnicode_AsLatin1String(obj), obj, ap); } /* * Convert a Python UTF-8 string object to a string and raise an exception if * there was an error. The object is updated with the one that owns the * string. Note that None is considered an error. */ static const char *sip_api_string_as_utf8_string(PyObject **obj) { PyObject *s = *obj; const char *a; if (s == Py_None || (*obj = parseString_AsUTF8String(s, &a)) == NULL) { /* Use the exception set if it was an encoding error. */ if (!PyUnicode_Check(s)) PyErr_Format(PyExc_TypeError, "bytes or UTF-8 string expected not '%s'", Py_TYPE(s)->tp_name); return NULL; } return a; } /* * Parse a UTF-8 string and return it and a new reference to the object that * owns the string. */ static PyObject *parseString_AsUTF8String(PyObject *obj, const char **ap) { return parseString_AsEncodedString(PyUnicode_AsUTF8String(obj), obj, ap); } /* * Parse an encoded string and return it and a new reference to the object that * owns the string. */ static PyObject *parseString_AsEncodedString(PyObject *bytes, PyObject *obj, const char **ap) { if (bytes != NULL) { *ap = PyBytes_AS_STRING(bytes); return bytes; } /* Don't try anything else if there was an encoding error. */ if (PyUnicode_Check(obj)) return NULL; PyErr_Clear(); if (parseBytes_AsString(obj, ap) < 0) return NULL; Py_INCREF(obj); return obj; } /* * Parse a character array and return it's address and length. */ static int parseBytes_AsCharArray(PyObject *obj, const char **ap, Py_ssize_t *aszp) { const char *a; Py_ssize_t asz; if (obj == Py_None) { a = NULL; asz = 0; } else if (PyBytes_Check(obj)) { a = PyBytes_AS_STRING(obj); asz = PyBytes_GET_SIZE(obj); } else { Py_buffer view; if (PyObject_GetBuffer(obj, &view, PyBUF_SIMPLE) < 0) return -1; a = view.buf; asz = view.len; PyBuffer_Release(&view); } if (ap != NULL) *ap = a; if (aszp != NULL) *aszp = asz; return 0; } /* * Parse a character and return it. */ static int parseBytes_AsChar(PyObject *obj, char *ap) { const char *chp; Py_ssize_t sz; if (PyBytes_Check(obj)) { chp = PyBytes_AS_STRING(obj); sz = PyBytes_GET_SIZE(obj); } else { Py_buffer view; if (PyObject_GetBuffer(obj, &view, PyBUF_SIMPLE) < 0) return -1; chp = view.buf; sz = view.len; PyBuffer_Release(&view); } if (sz != 1) return -1; if (ap != NULL) *ap = *chp; return 0; } /* * Parse a character string and return it. */ static int parseBytes_AsString(PyObject *obj, const char **ap) { const char *a; Py_ssize_t sz; if (parseBytes_AsCharArray(obj, &a, &sz) < 0) return -1; if (ap != NULL) *ap = a; return 0; } #if defined(HAVE_WCHAR_H) /* * Convert a Python object to a wide character. */ static wchar_t sip_api_unicode_as_wchar(PyObject *obj) { wchar_t ch; if (parseWChar(obj, &ch) < 0) { PyErr_Format(PyExc_ValueError, "string of length 1 expected, not %s", Py_TYPE(obj)->tp_name); return L'\0'; } return ch; } /* * Convert a Python object to a wide character string on the heap. */ static wchar_t *sip_api_unicode_as_wstring(PyObject *obj) { wchar_t *p; if (parseWCharString(obj, &p) < 0) { PyErr_Format(PyExc_ValueError, "string expected, not %s", Py_TYPE(obj)->tp_name); return NULL; } return p; } /* * Parse a wide character array and return it's address and length. */ static int parseWCharArray(PyObject *obj, wchar_t **ap, Py_ssize_t *aszp) { wchar_t *a; Py_ssize_t asz; if (obj == Py_None) { a = NULL; asz = 0; } else if (PyUnicode_Check(obj)) { if (convertToWCharArray(obj, &a, &asz) < 0) return -1; } else { return -1; } if (ap != NULL) *ap = a; if (aszp != NULL) *aszp = asz; return 0; } /* * Convert a Unicode object to a wide character array and return it's address * and length. */ static int convertToWCharArray(PyObject *obj, wchar_t **ap, Py_ssize_t *aszp) { Py_ssize_t ulen; wchar_t *wc; ulen = PyUnicode_GET_LENGTH(obj); if ((wc = sip_api_malloc(ulen * sizeof (wchar_t))) == NULL) return -1; if ((ulen = PyUnicode_AsWideChar(obj, wc, ulen)) < 0) { sip_api_free(wc); return -1; } *ap = wc; *aszp = ulen; return 0; } /* * Parse a wide character and return it. */ static int parseWChar(PyObject *obj, wchar_t *ap) { wchar_t a; if (PyUnicode_Check(obj)) { if (convertToWChar(obj, &a) < 0) return -1; } else { return -1; } if (ap != NULL) *ap = a; return 0; } /* * Convert a Unicode object to a wide character and return it. */ static int convertToWChar(PyObject *obj, wchar_t *ap) { if (PyUnicode_GET_LENGTH(obj) != 1) return -1; if (PyUnicode_AsWideChar(obj, ap, 1) != 1) return -1; return 0; } /* * Parse a wide character string and return a copy on the heap. */ static int parseWCharString(PyObject *obj, wchar_t **ap) { wchar_t *a; if (obj == Py_None) { a = NULL; } else if (PyUnicode_Check(obj)) { if (convertToWCharString(obj, &a) < 0) return -1; } else { return -1; } if (ap != NULL) *ap = a; return 0; } /* * Convert a Unicode object to a wide character string and return a copy on * the heap. */ static int convertToWCharString(PyObject *obj, wchar_t **ap) { Py_ssize_t ulen; wchar_t *wc; ulen = PyUnicode_GET_LENGTH(obj); if ((wc = sip_api_malloc((ulen + 1) * sizeof (wchar_t))) == NULL) return -1; if ((ulen = PyUnicode_AsWideChar(obj, wc, ulen)) < 0) { sip_api_free(wc); return -1; } wc[ulen] = L'\0'; *ap = wc; return 0; } #else /* * Convert a Python object to a wide character. */ static int sip_api_unicode_as_wchar(PyObject *obj) { raiseNoWChar(); return 0; } /* * Convert a Python object to a wide character. */ static int *sip_api_unicode_as_wstring(PyObject *obj) { raiseNoWChar(); return NULL; } /* * Report the need for absent wide character support. */ static void raiseNoWChar() { PyErr_SetString(PyExc_SystemError, "sip built without wchar_t support"); } #endif /* * The enum type alloc slot. */ static PyObject *sipEnumType_alloc(PyTypeObject *self, Py_ssize_t nitems) { sipEnumTypeObject *py_type; sipPySlotDef *psd; if (currentType == NULL) { PyErr_SetString(PyExc_TypeError, "enums cannot be sub-classed"); return NULL; } assert(sipTypeIsEnum(currentType)); /* Call the standard super-metatype alloc. */ if ((py_type = (sipEnumTypeObject *)PyType_Type.tp_alloc(self, nitems)) == NULL) return NULL; /* * Set the links between the Python type object and the generated type * structure. Strictly speaking this doesn't need to be done here. */ py_type->type = currentType; currentType->td_py_type = (PyTypeObject *)py_type; /* * Initialise any slots. This must be done here, after the type is * allocated but before PyType_Ready() is called. */ if ((psd = ((sipEnumTypeDef *)currentType)->etd_pyslots) != NULL) addTypeSlots(&py_type->super, psd); return (PyObject *)py_type; } /* * The enum type getattro slot. */ static PyObject *sipEnumType_getattro(PyObject *self, PyObject *name) { PyObject *res; sipEnumTypeDef *etd; sipExportedModuleDef *client; const sipEnumMemberDef *enm, *emd; int enum_nr, nr_members, m; const char *name_str; /* * Try a generic lookup first. This has the side effect of checking the * type of the name object. */ if ((res = PyObject_GenericGetAttr(self, name)) != NULL) return res; if (!PyErr_ExceptionMatches(PyExc_AttributeError)) return NULL; PyErr_Clear(); /* Get the member name. */ if ((name_str = PyUnicode_AsUTF8(name)) == NULL) return NULL; etd = (sipEnumTypeDef *)((sipEnumTypeObject *)self)->type; client = ((sipTypeDef *)etd)->td_module; /* Find the number of this enum. */ for (enum_nr = 0; enum_nr < client->em_nrtypes; ++enum_nr) if (client->em_types[enum_nr] == (sipTypeDef *)etd) break; /* Get the enum members in the same scope. */ if (etd->etd_scope < 0) { nr_members = client->em_nrenummembers; enm = client->em_enummembers; } else { const sipContainerDef *cod = get_container(client->em_types[etd->etd_scope]); nr_members = cod->cod_nrenummembers; enm = cod->cod_enummembers; } /* Find the enum member. */ for (emd = enm, m = 0; m < nr_members; ++m, ++emd) if (emd->em_enum == enum_nr && strcmp(emd->em_name, name_str) == 0) return sip_api_convert_from_enum(emd->em_val, (sipTypeDef *)etd); PyErr_Format(PyExc_AttributeError, "sip.enumtype object '%s' has no member '%s'", sipPyNameOfEnum(etd), name_str); return NULL; } /* * Check if an object is of the right type to convert to an encoded string. */ static int check_encoded_string(PyObject *obj) { Py_buffer view; if (obj == Py_None) return 0; if (PyUnicode_Check(obj)) return 0; if (PyBytes_Check(obj)) return 0; if (PyObject_GetBuffer(obj, &view, PyBUF_SIMPLE) < 0) { PyErr_Clear(); } else { PyBuffer_Release(&view); return 0; } return -1; } /* * This is called by the atexit module. */ static PyObject *sip_exit(PyObject *self, PyObject *args) { (void)self; (void)args; /* Disable all Python reimplementations of virtuals. */ sipInterpreter = NULL; Py_INCREF(Py_None); return Py_None; } /* * Register an exit notifier with the atexit module. */ static int sip_api_register_exit_notifier(PyMethodDef *md) { static PyObject *register_func = NULL; PyObject *notifier, *res; if (register_func == NULL && (register_func = import_module_attr("atexit", "register")) == NULL) return -1; if ((notifier = PyCFunction_New(md, NULL)) == NULL) return -1; res = PyObject_CallFunctionObjArgs(register_func, notifier, NULL); Py_DECREF(notifier); if (res == NULL) return -1; Py_DECREF(res); return 0; } /* * Return the function that converts a C++ instance to a Python object. */ static sipConvertFromFunc get_from_convertor(const sipTypeDef *td) { if (sipTypeIsMapped(td)) return ((const sipMappedTypeDef *)td)->mtd_cfrom; assert(sipTypeIsClass(td)); if (autoconversion_disabled(td) != NULL) return NULL; return ((const sipClassTypeDef *)td)->ctd_cfrom; } /* * Enable or disable the auto-conversion. Returns the previous enabled state * or -1 on error. */ static int sip_api_enable_autoconversion(const sipTypeDef *td, int enable) { sipPyObject **pop; assert(sipTypeIsClass(td)); pop = autoconversion_disabled(td); /* See if there is anything to do. */ if (pop == NULL && enable) return TRUE; if (pop != NULL && !enable) return FALSE; if (pop != NULL) { /* Remove it from the list. */ sipPyObject *po = *pop; *pop = po->next; sip_api_free(po); } else { /* Add it to the list. */ if (addPyObjectToList(&sipDisabledAutoconversions, (PyObject *)sipTypeAsPyTypeObject(td)) < 0) return -1; } return !enable; } /* * Return a pointer to the entry in the list of disabled auto-conversions for a * type. */ static sipPyObject **autoconversion_disabled(const sipTypeDef *td) { PyObject *type = (PyObject *)sipTypeAsPyTypeObject(td); sipPyObject **pop; for (pop = &sipDisabledAutoconversions; *pop != NULL; pop = &(*pop)->next) if ((*pop)->object == type) return pop; return NULL; } /* * Enable or disable auto-conversion of a class that supports it. */ static PyObject *enableAutoconversion(PyObject *self, PyObject *args) { sipWrapperType *wt; int enable; (void)self; if (PyArg_ParseTuple(args, "O!i:enableautoconversion", &sipWrapperType_Type, &wt, &enable)) { sipTypeDef *td = wt->wt_td; int was_enabled; PyObject *res; if (!sipTypeIsClass(td) || ((sipClassTypeDef *)td)->ctd_cfrom == NULL) { PyErr_Format(PyExc_TypeError, "%s is not a wrapped class that supports optional auto-conversion", ((PyTypeObject *)wt)->tp_name); return NULL; } if ((was_enabled = sip_api_enable_autoconversion(td, enable)) < 0) return NULL; res = (was_enabled ? Py_True : Py_False); Py_INCREF(res); return res; } return NULL; } /* * Python copies the nb_inplace_add slot to the sq_inplace_concat slot and vice * versa if either are missing. This is a bug because they don't have the same * API. We therefore reverse this. */ static void fix_slots(PyTypeObject *py_type, sipPySlotDef *psd) { while (psd->psd_func != NULL) { if (psd->psd_type == iadd_slot && py_type->tp_as_sequence != NULL) py_type->tp_as_sequence->sq_inplace_concat = NULL; if (psd->psd_type == iconcat_slot && py_type->tp_as_number != NULL) py_type->tp_as_number->nb_inplace_add = NULL; ++psd; } } /* * Return the main instance for an object if it is a mixin. */ static sipSimpleWrapper *deref_mixin(sipSimpleWrapper *w) { return w->mixin_main != NULL ? (sipSimpleWrapper *)w->mixin_main : w; } /* * Convert a new C/C++ pointer to a Python instance. */ static PyObject *wrap_simple_instance(void *cpp, const sipTypeDef *td, sipWrapper *owner, int flags) { return sipWrapInstance(cpp, sipTypeAsPyTypeObject(td), empty_tuple, owner, flags); } /* * Resolve a proxy, if applicable. */ static void *resolve_proxy(const sipTypeDef *td, void *proxy) { sipProxyResolver *pr; /* TODO: Deprecate this mechanism in favour of an event handler. */ for (pr = proxyResolvers; pr != NULL; pr = pr->next) if (pr->td == td) proxy = pr->resolver(proxy); return proxy; } /* * Clear a simple wrapper. */ static void clear_wrapper(sipSimpleWrapper *sw) { if (PyObject_TypeCheck((PyObject *)sw, (PyTypeObject *)&sipWrapper_Type)) removeFromParent((sipWrapper *)sw); /* * Transfer ownership to C++ so we don't try to release it when the * Python object is garbage collected. */ sipResetPyOwned(sw); sipOMRemoveObject(&cppPyMap, sw); clear_access_func(sw); } /* * Set the handler to invoke when a new user Python sub-class is defined and * return the old handler. */ static sipNewUserTypeFunc sip_api_set_new_user_type_handler( const sipTypeDef *td, sipNewUserTypeFunc handler) { sipWrapperType *wt = (sipWrapperType *)sipTypeAsPyTypeObject(td); sipNewUserTypeFunc old_handler = wt->wt_new_user_type_handler;; wt->wt_new_user_type_handler = handler; return old_handler; } /* * Set the user-specific type data. */ static void sip_api_set_type_user_data(sipWrapperType *wt, void *data) { wt->wt_user_data = data; } /* * Get the user-specific type data. */ static void *sip_api_get_type_user_data(const sipWrapperType *wt) { return wt->wt_user_data; } /* * Get a borrowed reference to the dict of a Python type (on behalf of the * limited API). This is deprecated in ABI v12.13 and must not be used with * Python v3.12 and later. */ static PyObject *sip_api_py_type_dict(const PyTypeObject *py_type) { PyErr_WarnEx(PyExc_DeprecationWarning, "sipPyTypeDict() is deprecated, the extension module should use " "sipPyTypeDictRef() instead", 1); return py_type->tp_dict; } /* * Get a new reference to the dict of a Python type (on behalf of the limited * API). */ static PyObject *sip_api_py_type_dict_ref(PyTypeObject *py_type) { #if PY_VERSION_HEX >= 0x030c0000 return PyType_GetDict(py_type); #else PyObject *ref = py_type->tp_dict; Py_XINCREF(ref); return ref; #endif } /* * Get the name of a Python type (on behalf of the limited API). */ static const char *sip_api_py_type_name(const PyTypeObject *py_type) { return py_type->tp_name; } /* * Check an object is a method and return TRUE and its component parts if it * is. */ static int sip_api_get_method(PyObject *obj, sipMethodDef *method) { if (!PyMethod_Check(obj)) return FALSE; if (method != NULL) { method->pm_self = PyMethod_GET_SELF(obj); method->pm_function = PyMethod_GET_FUNCTION(obj); } return TRUE; } /* * Create a method from its component parts. */ static PyObject *sip_api_from_method(const sipMethodDef *method) { return PyMethod_New(method->pm_function, method->pm_self); } /* * Check an object is a C function and return TRUE and its component parts if * it is. */ static int sip_api_get_c_function(PyObject *obj, sipCFunctionDef *c_function) { if (!PyCFunction_Check(obj)) return FALSE; if (c_function != NULL) { c_function->cf_function = ((PyCFunctionObject *)obj)->m_ml; c_function->cf_self = PyCFunction_GET_SELF(obj); } return TRUE; } /* * Check an object is a date and return TRUE and its component parts if it is. */ static int sip_api_get_date(PyObject *obj, sipDateDef *date) { if (!PyDateTimeAPI) PyDateTime_IMPORT; if (!PyDate_Check(obj)) return FALSE; if (date != NULL) { date->pd_year = PyDateTime_GET_YEAR(obj); date->pd_month = PyDateTime_GET_MONTH(obj); date->pd_day = PyDateTime_GET_DAY(obj); } return TRUE; } /* * Create a date from its component parts. */ static PyObject *sip_api_from_date(const sipDateDef *date) { if (!PyDateTimeAPI) PyDateTime_IMPORT; return PyDate_FromDate(date->pd_year, date->pd_month, date->pd_day); } /* * Check an object is a datetime and return TRUE and its component parts if it * is. */ static int sip_api_get_datetime(PyObject *obj, sipDateDef *date, sipTimeDef *time) { if (!PyDateTimeAPI) PyDateTime_IMPORT; if (!PyDateTime_Check(obj)) return FALSE; if (date != NULL) { date->pd_year = PyDateTime_GET_YEAR(obj); date->pd_month = PyDateTime_GET_MONTH(obj); date->pd_day = PyDateTime_GET_DAY(obj); } if (time != NULL) { time->pt_hour = PyDateTime_DATE_GET_HOUR(obj); time->pt_minute = PyDateTime_DATE_GET_MINUTE(obj); time->pt_second = PyDateTime_DATE_GET_SECOND(obj); time->pt_microsecond = PyDateTime_DATE_GET_MICROSECOND(obj); } return TRUE; } /* * Create a datetime from its component parts. */ static PyObject *sip_api_from_datetime(const sipDateDef *date, const sipTimeDef *time) { if (!PyDateTimeAPI) PyDateTime_IMPORT; return PyDateTime_FromDateAndTime(date->pd_year, date->pd_month, date->pd_day, time->pt_hour, time->pt_minute, time->pt_second, time->pt_microsecond); } /* * Check an object is a time and return TRUE and its component parts if it is. */ static int sip_api_get_time(PyObject *obj, sipTimeDef *time) { if (!PyDateTimeAPI) PyDateTime_IMPORT; if (!PyTime_Check(obj)) return FALSE; if (time != NULL) { time->pt_hour = PyDateTime_TIME_GET_HOUR(obj); time->pt_minute = PyDateTime_TIME_GET_MINUTE(obj); time->pt_second = PyDateTime_TIME_GET_SECOND(obj); time->pt_microsecond = PyDateTime_TIME_GET_MICROSECOND(obj); } return TRUE; } /* * Create a time from its component parts. */ static PyObject *sip_api_from_time(const sipTimeDef *time) { if (!PyDateTimeAPI) PyDateTime_IMPORT; return PyTime_FromTime(time->pt_hour, time->pt_minute, time->pt_second, time->pt_microsecond); } /* * See if a type is user defined. */ static int sip_api_is_user_type(const sipWrapperType *wt) { return wt->wt_user_type; } /* * Return a frame from the execution stack. Note that we use 'struct _frame' * rather than PyFrameObject because the latter wasn't exposed to the limited * API until Python v3.9. */ static struct _frame *sip_api_get_frame(int depth) { #if defined(PYPY_VERSION) /* PyPy only supports a depth of 0. */ return NULL; #else struct _frame *frame = PyEval_GetFrame(); while (frame != NULL && depth > 0) { #if PY_VERSION_HEX < 0x03090000 frame = frame->f_back; #else frame = PyFrame_GetBack(frame); /* Historically we return a borrowed reference. */ Py_XDECREF(frame); #endif --depth; } return frame; #endif } /* * Check if a type was generated using the given plugin. Note that, although * this is part of the public API it is undocumented on purpose. */ static int sip_api_check_plugin_for_type(const sipTypeDef *td, const char *name) { /* * The current thinking on plugins is that SIP v5 will look for a plugin * with a name derived from the name as the current module in the same * directory as the .sip defining the module (ie. no %Plugin directive). A * module hierachy may have multiple plugins but they must co-operate. If * a plugin generates user data then it should include a void* (and a * run-time API) so that other plugins can extend it further. This * approach means that a plugin's user data structure can be opaque. */ sipExportedModuleDef *em = td->td_module; sipImportedModuleDef *im; if (strcmp(sipNameOfModule(em), name) == 0) return TRUE; if ((im = em->em_imports) == NULL) return FALSE; while (im->im_name != NULL) { if (strcmp(im->im_name, name) == 0) return TRUE; ++im; } return FALSE; } /* * Create a new Unicode object and return the character size and buffer. */ static PyObject *sip_api_unicode_new(Py_ssize_t len, unsigned maxchar, int *kind, void **data) { PyObject *obj; if ((obj = PyUnicode_New(len, maxchar)) != NULL) { *kind = PyUnicode_KIND(obj); *data = PyUnicode_DATA(obj); } return obj; } /* * Update a new Unicode object with a new character. */ static void sip_api_unicode_write(int kind, void *data, int index, unsigned value) { PyUnicode_WRITE(kind, data, index, value); } /* * Get the address of the contents of a Unicode object, the character size and * the length. */ static void *sip_api_unicode_data(PyObject *obj, int *char_size, Py_ssize_t *len) { void *data; /* Assume there will be an error. */ *char_size = -1; if (PyUnicode_READY(obj) < 0) return NULL; *len = PyUnicode_GET_LENGTH(obj); switch (PyUnicode_KIND(obj)) { case PyUnicode_1BYTE_KIND: *char_size = 1; data = PyUnicode_1BYTE_DATA(obj); break; case PyUnicode_2BYTE_KIND: *char_size = 2; data = PyUnicode_2BYTE_DATA(obj); break; case PyUnicode_4BYTE_KIND: *char_size = 4; data = PyUnicode_4BYTE_DATA(obj); break; default: data = NULL; } return data; } /* * Get the buffer information supplied by an object that supports the buffer * protocol. */ static int sip_api_get_buffer_info(PyObject *obj, sipBufferInfoDef *bi) { int rc; Py_buffer *buffer; if (!PyObject_CheckBuffer(obj)) return 0; if (bi == NULL) return 1; if ((bi->bi_internal = sip_api_malloc(sizeof (Py_buffer))) == NULL) return -1; buffer = (Py_buffer *)bi->bi_internal; if (PyObject_GetBuffer(obj, buffer, PyBUF_FORMAT) < 0) return -1; if (buffer->ndim == 1) { bi->bi_buf = buffer->buf; bi->bi_obj = buffer->obj; bi->bi_len = buffer->len; bi->bi_format = buffer->format; rc = 1; } else { PyErr_SetString(PyExc_TypeError, "a 1-dimensional buffer is required"); PyBuffer_Release(buffer); rc = -1; } return rc; } /* * Release the buffer information obtained from a previous call to * sipGetBufferInfo(). */ static void sip_api_release_buffer_info(sipBufferInfoDef *bi) { if (bi->bi_internal != NULL) { PyBuffer_Release((Py_buffer *)bi->bi_internal); sip_api_free(bi->bi_internal); bi->bi_internal = NULL; } } /* * Import all the required types from an imported module. */ static int importTypes(sipExportedModuleDef *client, sipImportedModuleDef *im, sipExportedModuleDef *em) { const char *name; int i, e; /* * Look for each required type in turn. Both tables are sorted so a single * pass will find them all. */ for (i = e = 0; (name = im->im_imported_types[i].it_name) != NULL; ++i) { sipTypeDef *td = NULL; do { sipTypeDef *e_td; if (e >= em->em_nrtypes) { PyErr_Format(PyExc_RuntimeError, "%s cannot import type '%s' from %s", sipNameOfModule(client), name, sipNameOfModule(em)); return -1; } e_td = em->em_types[e++]; /* Ignore unresolved external types. */ if (e_td != NULL && strcmp(name, sipTypeName(e_td)) == 0) td = e_td; } while (td == NULL); im->im_imported_types[i].it_td = td; } return 0; } /* * Import all the required virtual error handlers from an imported module. */ static int importErrorHandlers(sipExportedModuleDef *client, sipImportedModuleDef *im, sipExportedModuleDef *em) { const char *name; int i; for (i = 0; (name = im->im_imported_veh[i].iveh_name) != NULL; ++i) { sipVirtErrorHandlerDef *veh = em->em_virterrorhandlers; sipVirtErrorHandlerFunc handler = NULL; if (veh != NULL) { while (veh->veh_name != NULL) { if (strcmp(veh->veh_name, name) == 0) { handler = veh->veh_handler; break; } ++veh; } } if (handler == NULL) { PyErr_Format(PyExc_RuntimeError, "%s cannot import virtual error handler '%s' from %s", sipNameOfModule(client), name, sipNameOfModule(em)); return -1; } im->im_imported_veh[i].iveh_handler = handler; } return 0; } /* * Import all the required exceptions from an imported module. */ static int importExceptions(sipExportedModuleDef *client, sipImportedModuleDef *im, sipExportedModuleDef *em) { const char *name; int i; for (i = 0; (name = im->im_imported_exceptions[i].iexc_name) != NULL; ++i) { PyObject **exc = em->em_exceptions; PyObject *exception = NULL; if (exc != NULL) { while (*exc != NULL) { if (strcmp(((PyTypeObject *)(*exc))->tp_name, name) == 0) { exception = *exc; break; } ++exc; } } if (exception == NULL) { PyErr_Format(PyExc_RuntimeError, "%s cannot import exception '%s' from %s", sipNameOfModule(client), name, sipNameOfModule(em)); return -1; } im->im_imported_exceptions[i].iexc_object = exception; } return 0; } /* * Enable or disable the garbage collector. Return the previous state or -1 if * there was an error. */ static int sip_api_enable_gc(int enable) { static PyObject *enable_func = NULL, *disable_func, *isenabled_func; PyObject *result; int was_enabled; /* * This may be -ve in the highly unusual event that a previous call failed. */ if (enable < 0) return -1; /* Get the functions if we haven't already got them. */ if (enable_func == NULL) { PyObject *gc_module; if ((gc_module = PyImport_ImportModule("gc")) == NULL) return -1; if ((enable_func = PyObject_GetAttrString(gc_module, "enable")) == NULL) { Py_DECREF(gc_module); return -1; } if ((disable_func = PyObject_GetAttrString(gc_module, "disable")) == NULL) { Py_DECREF(enable_func); Py_DECREF(gc_module); return -1; } if ((isenabled_func = PyObject_GetAttrString(gc_module, "isenabled")) == NULL) { Py_DECREF(disable_func); Py_DECREF(enable_func); Py_DECREF(gc_module); return -1; } Py_DECREF(gc_module); } /* Get the current state. */ if ((result = PyObject_Call(isenabled_func, empty_tuple, NULL)) == NULL) return -1; was_enabled = PyObject_IsTrue(result); Py_DECREF(result); if (was_enabled < 0) return -1; /* See if the state needs changing. */ if (!was_enabled != !enable) { /* Enable or disable as required. */ result = PyObject_Call((enable ? enable_func : disable_func), empty_tuple, NULL); Py_XDECREF(result); if (result != Py_None) return -1; } return was_enabled; } /* * A thin wrapper around PyObject_Print() usually used when debugging with the * limited API. */ static void sip_api_print_object(PyObject *o) { PyObject_Print(o, stdout, 0); } /* * Register a handler for a particular event. */ static int sip_api_register_event_handler(sipEventType type, const sipTypeDef *td, void *handler) { sipEventHandler *eh; assert(sipTypeIsClass(td)); if ((eh = sip_api_malloc(sizeof (sipEventHandler))) == NULL) return -1; eh->ctd = (const sipClassTypeDef *)td; eh->handler = handler; eh->next = event_handlers[(int)type]; event_handlers[(int)type] = eh; return 0; } /* * Returns TRUE if a generated class type is a sub-class of a base generated * class type. */ static int is_subtype(const sipClassTypeDef *ctd, const sipClassTypeDef *base_ctd) { const sipEncodedTypeDef *sup; /* Handle the trivial cases. */ if (ctd == base_ctd) return TRUE; if ((sup = ctd->ctd_supers) == NULL) return FALSE; /* Search the super-types. */ do { const sipClassTypeDef *sup_ctd = sipGetGeneratedClassType(sup, ctd); if (is_subtype(sup_ctd, base_ctd)) return TRUE; } while (!sup++->sc_flag); return FALSE; } /* * Return an attribute of an imported module. */ static PyObject *import_module_attr(const char *module, const char *attr) { PyObject *mod_obj, *attr_obj; if ((mod_obj = PyImport_ImportModule(module)) == NULL) return NULL; attr_obj = PyObject_GetAttrString(mod_obj, attr); Py_DECREF(mod_obj); return attr_obj; } /* * Get the container for a generated type. */ static const sipContainerDef *get_container(const sipTypeDef *td) { if (sipTypeIsMapped(td)) return &((const sipMappedTypeDef *)td)->mtd_container; return &((const sipClassTypeDef *)td)->ctd_container; } /* * Get the __qualname__ of an object based on its enclosing scope. */ static PyObject *get_qualname(const sipTypeDef *td, PyObject *name) { PyTypeObject *scope_type; /* Get the type that is the scope. */ scope_type = sipTypeAsPyTypeObject(td); return PyUnicode_FromFormat("%U.%U", ((PyHeapTypeObject *)scope_type)->ht_qualname, name); } /* * Implement PySlice_GetIndicesEx() (or its subsequent replacement). */ int sip_api_convert_from_slice_object(PyObject *slice, Py_ssize_t length, Py_ssize_t *start, Py_ssize_t *stop, Py_ssize_t *step, Py_ssize_t *slicelength) { if (PySlice_Unpack(slice, start, stop, step) < 0) return -1; *slicelength = PySlice_AdjustIndices(length, start, stop, *step); return 0; } /* * Call a visitor function for every wrapped object. */ static void sip_api_visit_wrappers(sipWrapperVisitorFunc visitor, void *closure) { const sipHashEntry *he; unsigned long i; for (he = cppPyMap.hash_array, i = 0; i < cppPyMap.size; ++i, ++he) { if (he->key != NULL) { sipSimpleWrapper *sw; for (sw = he->first; sw != NULL; sw = sw->next) visitor(sw, closure); } } } /* * Return the next exception handler. The order is undefined. */ sipExceptionHandler sip_api_next_exception_handler(void **statep) { sipExportedModuleDef *em = *(sipExportedModuleDef **)statep; if (em != NULL) em = em->em_next; else em = moduleList; while (em->em_exception_handler == NULL) if ((em = em->em_next) == NULL) return NULL; *statep = em; return em->em_exception_handler; } sip-6.8.6/sipbuild/module/source/12/threads.c000066400000000000000000000102711464421045000207470ustar00rootroot00000000000000/* SPDX-License-Identifier: BSD-2-Clause */ /* * Thread support for the SIP library. This module provides the hooks for * C++ classes that provide a thread interface to interact properly with the * Python threading infrastructure. * * Copyright (c) 2024 Phil Thompson */ #include "sipint.h" /* * The data associated with pending request to wrap an object. */ typedef struct _pendingDef { void *cpp; /* The C/C++ object ot be wrapped. */ sipWrapper *owner; /* The owner of the object. */ int flags; /* The flags. */ } pendingDef; #ifdef WITH_THREAD #include /* * The per thread data we need to maintain. */ typedef struct _threadDef { long thr_ident; /* The thread identifier. */ pendingDef pending; /* An object waiting to be wrapped. */ struct _threadDef *next; /* Next in the list. */ } threadDef; static threadDef *threads = NULL; /* Linked list of threads. */ static threadDef *currentThreadDef(int auto_alloc); #endif static pendingDef *get_pending(int auto_alloc); /* * Get the address etc. of any C/C++ object waiting to be wrapped. */ int sipGetPending(void **pp, sipWrapper **op, int *fp) { pendingDef *pd; if ((pd = get_pending(TRUE)) == NULL) return -1; *pp = pd->cpp; *op = pd->owner; *fp = pd->flags; /* Clear in case we execute Python code before finishing this wrapping. */ pd->cpp = NULL; return 0; } /* * Return TRUE if anything is pending. */ int sipIsPending(void) { pendingDef *pd; if ((pd = get_pending(FALSE)) == NULL) return FALSE; return (pd->cpp != NULL); } /* * Convert a new C/C++ pointer to a Python instance. */ PyObject *sipWrapInstance(void *cpp, PyTypeObject *py_type, PyObject *args, sipWrapper *owner, int flags) { pendingDef old_pending, *pd; PyObject *self; if (cpp == NULL) { Py_INCREF(Py_None); return Py_None; } /* * Object creation can trigger the Python garbage collector which in turn * can execute arbitrary Python code which can then call this function * recursively. Therefore we save any existing pending object before * setting the new one. */ if ((pd = get_pending(TRUE)) == NULL) return NULL; old_pending = *pd; pd->cpp = cpp; pd->owner = owner; pd->flags = flags; self = PyObject_Call((PyObject *)py_type, args, NULL); *pd = old_pending; return self; } /* * Handle the termination of a thread. */ void sip_api_end_thread(void) { #ifdef WITH_THREAD threadDef *thread; PyGILState_STATE gil = PyGILState_Ensure(); if ((thread = currentThreadDef(FALSE)) != NULL) thread->thr_ident = 0; PyGILState_Release(gil); #endif } /* * Return the pending data for the current thread, allocating it if necessary, * or NULL if there was an error. */ static pendingDef *get_pending(int auto_alloc) { #ifdef WITH_THREAD threadDef *thread; if ((thread = currentThreadDef(auto_alloc)) == NULL) return NULL; return &thread->pending; #else static pendingDef pending; return &pending; #endif } #ifdef WITH_THREAD /* * Return the thread data for the current thread, allocating it if necessary, * or NULL if there was an error. */ static threadDef *currentThreadDef(int auto_alloc) { threadDef *thread, *empty = NULL; long ident = PyThread_get_thread_ident(); /* See if we already know about the thread. */ for (thread = threads; thread != NULL; thread = thread->next) { if (thread->thr_ident == ident) return thread; if (thread->thr_ident == 0) empty = thread; } if (!auto_alloc) { /* This is not an error. */ return NULL; } if (empty != NULL) { /* Use an empty entry in the list. */ thread = empty; } else if ((thread = sip_api_malloc(sizeof (threadDef))) == NULL) { return NULL; } else { thread->next = threads; threads = thread; } thread->thr_ident = ident; thread->pending.cpp = NULL; return thread; } #endif sip-6.8.6/sipbuild/module/source/12/voidptr.c000066400000000000000000000446361464421045000210200ustar00rootroot00000000000000/* SPDX-License-Identifier: BSD-2-Clause */ /* * This file implements the API for the voidptr type. * * Copyright (c) 2024 Phil Thompson */ /* Remove when Python v3.12 is no longer supported. */ #define PY_SSIZE_T_CLEAN #include #include #include #include "sipint.h" #include "sip_array.h" /* The object data structure. */ typedef struct { PyObject_HEAD void *voidptr; Py_ssize_t size; int rw; } sipVoidPtrObject; /* The structure used to hold the results of a voidptr conversion. */ struct vp_values { void *voidptr; Py_ssize_t size; int rw; }; static int check_size(PyObject *self); static int check_rw(PyObject *self); static int check_index(PyObject *self, Py_ssize_t idx); static void bad_key(PyObject *key); static int check_slice_size(Py_ssize_t size, Py_ssize_t value_size); static PyObject *make_voidptr(void *voidptr, Py_ssize_t size, int rw); static int vp_convertor(PyObject *arg, struct vp_values *vp); static Py_ssize_t get_size_from_arg(sipVoidPtrObject *v, Py_ssize_t size); /* * Implement ascapsule() for the type. */ static PyObject *sipVoidPtr_ascapsule(sipVoidPtrObject *v, PyObject *arg) { (void)arg; return PyCapsule_New(v->voidptr, NULL, NULL); } /* * Implement asarray() for the type. */ static PyObject *sipVoidPtr_asarray(sipVoidPtrObject *v, PyObject *args, PyObject *kw) { #if PY_VERSION_HEX >= 0x030d0000 static char * const kwlist[] = {"size", NULL}; #else static char *kwlist[] = {"size", NULL}; #endif Py_ssize_t size = -1; if (!PyArg_ParseTupleAndKeywords(args, kw, "|n:asarray", kwlist, &size)) return NULL; if ((size = get_size_from_arg(v, size)) < 0) return NULL; return sip_api_convert_to_array(v->voidptr, "B", size, (v->rw ? 0 : SIP_READ_ONLY)); } /* * Implement asstring() for the type. */ static PyObject *sipVoidPtr_asstring(sipVoidPtrObject *v, PyObject *args, PyObject *kw) { #if PY_VERSION_HEX >= 0x030d0000 static char * const kwlist[] = {"size", NULL}; #else static char *kwlist[] = {"size", NULL}; #endif Py_ssize_t size = -1; if (!PyArg_ParseTupleAndKeywords(args, kw, "|n:asstring", kwlist, &size)) return NULL; if ((size = get_size_from_arg(v, size)) < 0) return NULL; return PyBytes_FromStringAndSize(v->voidptr, size); } /* * Implement getsize() for the type. */ static PyObject *sipVoidPtr_getsize(sipVoidPtrObject *v, PyObject *arg) { (void)arg; return PyLong_FromSsize_t(v->size); } /* * Implement setsize() for the type. */ static PyObject *sipVoidPtr_setsize(sipVoidPtrObject *v, PyObject *arg) { Py_ssize_t size = PyLong_AsSsize_t(arg); if (PyErr_Occurred()) return NULL; v->size = size; Py_INCREF(Py_None); return Py_None; } /* * Implement getwriteable() for the type. */ static PyObject *sipVoidPtr_getwriteable(sipVoidPtrObject *v, PyObject *arg) { (void)arg; return PyBool_FromLong(v->rw); } /* * Implement setwriteable() for the type. */ static PyObject *sipVoidPtr_setwriteable(sipVoidPtrObject *v, PyObject *arg) { int rw; if ((rw = PyObject_IsTrue(arg)) < 0) return NULL; v->rw = rw; Py_INCREF(Py_None); return Py_None; } /* The methods data structure. */ static PyMethodDef sipVoidPtr_Methods[] = { {"asarray", (PyCFunction)sipVoidPtr_asarray, METH_VARARGS|METH_KEYWORDS, NULL}, {"ascapsule", (PyCFunction)sipVoidPtr_ascapsule, METH_NOARGS, NULL}, {"asstring", (PyCFunction)sipVoidPtr_asstring, METH_VARARGS|METH_KEYWORDS, NULL}, {"getsize", (PyCFunction)sipVoidPtr_getsize, METH_NOARGS, NULL}, {"setsize", (PyCFunction)sipVoidPtr_setsize, METH_O, NULL}, {"getwriteable", (PyCFunction)sipVoidPtr_getwriteable, METH_NOARGS, NULL}, {"setwriteable", (PyCFunction)sipVoidPtr_setwriteable, METH_O, NULL}, {NULL, NULL, 0, NULL} }; /* * Implement bool() for the type. */ static int sipVoidPtr_bool(PyObject *self) { return (((sipVoidPtrObject *)self)->voidptr != NULL); } /* * Implement int() for the type. */ static PyObject *sipVoidPtr_int(PyObject *self) { return PyLong_FromVoidPtr(((sipVoidPtrObject *)self)->voidptr); } /* The number methods data structure. */ static PyNumberMethods sipVoidPtr_NumberMethods = { 0, /* nb_add */ 0, /* nb_subtract */ 0, /* nb_multiply */ 0, /* nb_remainder */ 0, /* nb_divmod */ 0, /* nb_power */ 0, /* nb_negative */ 0, /* nb_positive */ 0, /* nb_absolute */ sipVoidPtr_bool, /* nb_bool */ 0, /* nb_invert */ 0, /* nb_lshift */ 0, /* nb_rshift */ 0, /* nb_and */ 0, /* nb_xor */ 0, /* nb_or */ sipVoidPtr_int, /* nb_int */ 0, /* nb_reserved */ 0, /* nb_float */ 0, /* nb_inplace_add */ 0, /* nb_inplace_subtract */ 0, /* nb_inplace_multiply */ 0, /* nb_inplace_remainder */ 0, /* nb_inplace_power */ 0, /* nb_inplace_lshift */ 0, /* nb_inplace_rshift */ 0, /* nb_inplace_and */ 0, /* nb_inplace_xor */ 0, /* nb_inplace_or */ 0, /* nb_floor_divide */ 0, /* nb_true_divide */ 0, /* nb_inplace_floor_divide */ 0, /* nb_inplace_true_divide */ 0, /* nb_index */ 0, /* nb_matrix_multiply */ 0, /* nb_inplace_matrix_multiply */ }; /* * Implement len() for the type. */ static Py_ssize_t sipVoidPtr_length(PyObject *self) { if (check_size(self) < 0) return -1; return ((sipVoidPtrObject *)self)->size; } /* * Implement sequence item sub-script for the type. */ static PyObject *sipVoidPtr_item(PyObject *self, Py_ssize_t idx) { if (check_size(self) < 0 || check_index(self, idx) < 0) return NULL; return PyBytes_FromStringAndSize( (char *)((sipVoidPtrObject *)self)->voidptr + idx, 1); } /* The sequence methods data structure. */ static PySequenceMethods sipVoidPtr_SequenceMethods = { sipVoidPtr_length, /* sq_length */ 0, /* sq_concat */ 0, /* sq_repeat */ sipVoidPtr_item, /* sq_item */ 0, /* sq_slice */ 0, /* sq_ass_item */ 0, /* sq_ass_slice */ 0, /* sq_contains */ 0, /* sq_inplace_concat */ 0, /* sq_inplace_repeat */ }; /* * Implement mapping sub-script for the type. */ static PyObject *sipVoidPtr_subscript(PyObject *self, PyObject *key) { sipVoidPtrObject *v; if (check_size(self) < 0) return NULL; v = (sipVoidPtrObject *)self; if (PyIndex_Check(key)) { Py_ssize_t idx = PyNumber_AsSsize_t(key, PyExc_IndexError); if (idx == -1 && PyErr_Occurred()) return NULL; if (idx < 0) idx += v->size; return sipVoidPtr_item(self, idx); } if (PySlice_Check(key)) { Py_ssize_t start, stop, step, slicelength; if (sip_api_convert_from_slice_object(key, v->size, &start, &stop, &step, &slicelength) < 0) return NULL; if (step != 1) { PyErr_SetNone(PyExc_NotImplementedError); return NULL; } return make_voidptr((char *)v->voidptr + start, slicelength, v->rw); } bad_key(key); return NULL; } /* * Implement mapping assignment sub-script for the type. */ static int sipVoidPtr_ass_subscript(PyObject *self, PyObject *key, PyObject *value) { sipVoidPtrObject *v; Py_ssize_t start, size; Py_buffer value_view; if (check_rw(self) < 0 || check_size(self) < 0) return -1; v = (sipVoidPtrObject *)self; if (PyIndex_Check(key)) { start = PyNumber_AsSsize_t(key, PyExc_IndexError); if (start == -1 && PyErr_Occurred()) return -1; if (start < 0) start += v->size; if (check_index(self, start) < 0) return -1; size = 1; } else if (PySlice_Check(key)) { Py_ssize_t stop, step; if (sip_api_convert_from_slice_object(key, v->size, &start, &stop, &step, &size) < 0) return -1; if (step != 1) { PyErr_SetNone(PyExc_NotImplementedError); return -1; } } else { bad_key(key); return -1; } if (PyObject_GetBuffer(value, &value_view, PyBUF_CONTIG_RO) < 0) return -1; /* We could allow any item size... */ if (value_view.itemsize != 1) { PyErr_Format(PyExc_TypeError, "'%s' must have an item size of 1", Py_TYPE(value_view.obj)->tp_name); PyBuffer_Release(&value_view); return -1; } if (check_slice_size(size, value_view.len) < 0) { PyBuffer_Release(&value_view); return -1; } memmove((char *)v->voidptr + start, value_view.buf, size); PyBuffer_Release(&value_view); return 0; } /* The mapping methods data structure. */ static PyMappingMethods sipVoidPtr_MappingMethods = { sipVoidPtr_length, /* mp_length */ sipVoidPtr_subscript, /* mp_subscript */ sipVoidPtr_ass_subscript, /* mp_ass_subscript */ }; /* * The buffer implementation for Python v2.6.3 and later. */ static int sipVoidPtr_getbuffer(PyObject *self, Py_buffer *buf, int flags) { sipVoidPtrObject *v; if (check_size(self) < 0) return -1; v = (sipVoidPtrObject *)self; return PyBuffer_FillInfo(buf, self, v->voidptr, v->size, !v->rw, flags); } /* The buffer methods data structure. */ static PyBufferProcs sipVoidPtr_BufferProcs = { sipVoidPtr_getbuffer, /* bf_getbuffer */ 0 /* bf_releasebuffer */ }; /* * Implement __new__ for the type. */ static PyObject *sipVoidPtr_new(PyTypeObject *subtype, PyObject *args, PyObject *kw) { #if PY_VERSION_HEX >= 0x030d0000 static char * const kwlist[] = {"address", "size", "writeable", NULL}; #else static char *kwlist[] = {"address", "size", "writeable", NULL}; #endif struct vp_values vp_conversion; Py_ssize_t size = -1; int rw = -1; PyObject *obj; if (!PyArg_ParseTupleAndKeywords(args, kw, "O&|ni:voidptr", kwlist, vp_convertor, &vp_conversion, &size, &rw)) return NULL; /* Use the explicit size if one was given. */ if (size >= 0) vp_conversion.size = size; /* Use the explicit writeable flag if one was given. */ if (rw >= 0) vp_conversion.rw = rw; /* Create the instance. */ if ((obj = subtype->tp_alloc(subtype, 0)) == NULL) return NULL; /* Save the values. */ ((sipVoidPtrObject *)obj)->voidptr = vp_conversion.voidptr; ((sipVoidPtrObject *)obj)->size = vp_conversion.size; ((sipVoidPtrObject *)obj)->rw = vp_conversion.rw; return obj; } /* The type data structure. */ PyTypeObject sipVoidPtr_Type = { PyVarObject_HEAD_INIT(NULL, 0) "sip.voidptr", /* tp_name */ sizeof (sipVoidPtrObject), /* tp_basicsize */ 0, /* tp_itemsize */ 0, /* tp_dealloc */ 0, /* tp_print */ 0, /* tp_getattr */ 0, /* tp_setattr */ 0, /* tp_reserved (Python v3), tp_compare (Python v2) */ 0, /* tp_repr */ &sipVoidPtr_NumberMethods, /* tp_as_number */ &sipVoidPtr_SequenceMethods, /* tp_as_sequence */ &sipVoidPtr_MappingMethods, /* tp_as_mapping */ 0, /* tp_hash */ 0, /* tp_call */ 0, /* tp_str */ 0, /* tp_getattro */ 0, /* tp_setattro */ &sipVoidPtr_BufferProcs, /* tp_as_buffer */ #if defined(Py_TPFLAGS_HAVE_NEWBUFFER) Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_NEWBUFFER, /* tp_flags */ #else Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /* tp_flags */ #endif 0, /* tp_doc */ 0, /* tp_traverse */ 0, /* tp_clear */ 0, /* tp_richcompare */ 0, /* tp_weaklistoffset */ 0, /* tp_iter */ 0, /* tp_iternext */ sipVoidPtr_Methods, /* tp_methods */ 0, /* tp_members */ 0, /* tp_getset */ 0, /* tp_base */ 0, /* tp_dict */ 0, /* tp_descr_get */ 0, /* tp_descr_set */ 0, /* tp_dictoffset */ 0, /* tp_init */ 0, /* tp_alloc */ sipVoidPtr_new, /* tp_new */ 0, /* tp_free */ 0, /* tp_is_gc */ 0, /* tp_bases */ 0, /* tp_mro */ 0, /* tp_cache */ 0, /* tp_subclasses */ 0, /* tp_weaklist */ 0, /* tp_del */ 0, /* tp_version_tag */ 0, /* tp_finalize */ 0, /* tp_vectorcall */ }; /* * A convenience function to convert a C/C++ void pointer from a Python object. */ void *sip_api_convert_to_void_ptr(PyObject *obj) { struct vp_values vp; if (obj == NULL) { PyErr_SetString(PyExc_TypeError, "sip.voidptr is NULL"); return NULL; } if (vp_convertor(obj, &vp)) return vp.voidptr; return PyLong_AsVoidPtr(obj); } /* * Convert a C/C++ void pointer to a sip.voidptr object. */ PyObject *sip_api_convert_from_void_ptr(void *val) { return make_voidptr(val, -1, TRUE); } /* * Convert a C/C++ void pointer to a sip.voidptr object. */ PyObject *sip_api_convert_from_const_void_ptr(const void *val) { return make_voidptr((void *)val, -1, FALSE); } /* * Convert a sized C/C++ void pointer to a sip.voidptr object. */ PyObject *sip_api_convert_from_void_ptr_and_size(void *val, Py_ssize_t size) { return make_voidptr(val, size, TRUE); } /* * Convert a sized C/C++ const void pointer to a sip.voidptr object. */ PyObject *sip_api_convert_from_const_void_ptr_and_size(const void *val, Py_ssize_t size) { return make_voidptr((void *)val, size, FALSE); } /* * Check that a void pointer has an explicit size and raise an exception if it * hasn't. */ static int check_size(PyObject *self) { if (((sipVoidPtrObject *)self)->size >= 0) return 0; PyErr_SetString(PyExc_IndexError, "sip.voidptr object has an unknown size"); return -1; } /* * Check that a void pointer is writable. */ static int check_rw(PyObject *self) { if (((sipVoidPtrObject *)self)->rw) return 0; PyErr_SetString(PyExc_TypeError, "cannot modify a read-only sip.voidptr object"); return -1; } /* * Check that an index is valid for a void pointer. */ static int check_index(PyObject *self, Py_ssize_t idx) { if (idx >= 0 && idx < ((sipVoidPtrObject *)self)->size) return 0; PyErr_SetString(PyExc_IndexError, "index out of bounds"); return -1; } /* * Raise an exception about a bad sub-script key. */ static void bad_key(PyObject *key) { PyErr_Format(PyExc_TypeError, "cannot index a sip.voidptr object using '%s'", Py_TYPE(key)->tp_name); } /* * Check that the size of a value is the same as the size of the slice it is * replacing. */ static int check_slice_size(Py_ssize_t size, Py_ssize_t value_size) { if (value_size == size) return 0; PyErr_SetString(PyExc_ValueError, "cannot modify the size of a sip.voidptr object"); return -1; } /* * Do the work of converting a void pointer. */ static PyObject *make_voidptr(void *voidptr, Py_ssize_t size, int rw) { sipVoidPtrObject *self; if (voidptr == NULL) { Py_INCREF(Py_None); return Py_None; } if ((self = PyObject_NEW(sipVoidPtrObject, &sipVoidPtr_Type)) == NULL) return NULL; self->voidptr = voidptr; self->size = size; self->rw = rw; return (PyObject *)self; } /* * Convert a Python object to the values needed to create a voidptr. */ static int vp_convertor(PyObject *arg, struct vp_values *vp) { void *ptr; Py_ssize_t size = -1; int rw = TRUE; if (arg == Py_None) { ptr = NULL; } else if (PyCapsule_CheckExact(arg)) { ptr = PyCapsule_GetPointer(arg, NULL); } else if (PyObject_TypeCheck(arg, &sipVoidPtr_Type)) { ptr = ((sipVoidPtrObject *)arg)->voidptr; size = ((sipVoidPtrObject *)arg)->size; rw = ((sipVoidPtrObject *)arg)->rw; } else if (PyObject_CheckBuffer(arg)) { Py_buffer view; if (PyObject_GetBuffer(arg, &view, PyBUF_SIMPLE) < 0) return 0; ptr = view.buf; size = view.len; rw = !view.readonly; PyBuffer_Release(&view); } else { PyErr_Clear(); ptr = PyLong_AsVoidPtr(arg); if (PyErr_Occurred()) { PyErr_SetString(PyExc_TypeError, "a single integer, Capsule, None, bytes-like object or another sip.voidptr object is required"); return 0; } } vp->voidptr = ptr; vp->size = size; vp->rw = rw; return 1; } /* * Get a size possibly supplied as an argument, otherwise get it from the * object. Raise an exception if there was no size specified. */ static Py_ssize_t get_size_from_arg(sipVoidPtrObject *v, Py_ssize_t size) { /* Use the current size if one wasn't explicitly given. */ if (size < 0) size = v->size; if (size < 0) PyErr_SetString(PyExc_ValueError, "a size must be given or the sip.voidptr object must have a size"); return size; } sip-6.8.6/sipbuild/module/source/13/000077500000000000000000000000001464421045000171515ustar00rootroot00000000000000sip-6.8.6/sipbuild/module/source/13/LICENSE000066400000000000000000000024241464421045000201600ustar00rootroot00000000000000Copyright 2024 Phil Thompson Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. sip-6.8.6/sipbuild/module/source/13/MANIFEST.in000066400000000000000000000000431464421045000207040ustar00rootroot00000000000000include *.h include pyproject.toml sip-6.8.6/sipbuild/module/source/13/README.in000066400000000000000000000002001464421045000204260ustar00rootroot00000000000000sip Extension Module ==================== The sip extension module provides support for the @SIP_MODULE_PACKAGE_NAME@ package. sip-6.8.6/sipbuild/module/source/13/pyproject.toml000066400000000000000000000000711464421045000220630ustar00rootroot00000000000000[build-system] requires = ["setuptools >=30.3", "wheel"] sip-6.8.6/sipbuild/module/source/13/setup.cfg.in000066400000000000000000000003701464421045000213770ustar00rootroot00000000000000[metadata] description = The sip module support for @SIP_MODULE_PACKAGE_NAME@ long_description = file: README author = Phil Thompson author_email = phil@riverbankcomputing.com url = https://github.com/Python-SIP/sip platforms = X11, macOS, Windows sip-6.8.6/sipbuild/module/source/13/setup.py.in000066400000000000000000000007461464421045000212770ustar00rootroot00000000000000# SPDX-License-Identifier: BSD-2-Clause # Copyright (c) 2024 Phil Thompson import glob from setuptools import Extension, setup # Build the extension module. module_src = sorted(glob.glob('*.c')) module = Extension('@_SIP_MODULE_FQ_NAME@', module_src) # Do the setup. setup( name='@SIP_MODULE_PROJECT_NAME@', version='@SIP_MODULE_VERSION@', license='SIP', python_requires='>=3.8', ext_modules=[module] ) sip-6.8.6/sipbuild/module/source/13/sip.h.in000066400000000000000000001413001464421045000205210ustar00rootroot00000000000000/* SPDX-License-Identifier: BSD-2-Clause */ /* * The SIP module interface. * * Copyright (c) 2024 Phil Thompson */ #ifndef _SIP_H #define _SIP_H #include /* Sanity check on the Python version. */ #if PY_VERSION_HEX < 0x03080000 #error "This version of @_SIP_MODULE_FQ_NAME@ requires Python v3.8 or later" #endif #ifdef __cplusplus #include typedef bool (*sipExceptionHandler)(std::exception_ptr); #else typedef void *sipExceptionHandler; #endif #ifdef __cplusplus extern "C" { #endif /* The version of the ABI. */ #define SIP_ABI_MAJOR_VERSION 13 #define SIP_ABI_MINOR_VERSION 8 #define SIP_MODULE_PATCH_VERSION 0 /* * The change history of the ABI. * * v13.8 * - Added the 'I' conversion character to the argument and result parsers. * * v13.7 * - Added support for Python v3.13. * - Python v3.8 or later is required. * - C99 support is assumed. * * v13.6 * - Added support for Python v3.12. * - Added sipPyTypeDictRef(). * - Deprecated sipPyTypeDict(). * * v13.5 * - Added the '#' conversion character to the argument parsers. * * v13.4 * - The enum implementation now supports missing members. * - Published the 'array' type. * - Added the ctd_sizeof, ctd_array_delete members to sipClassTypeDef. * - Added the '>' conversion character to the argument parsers. * * v13.3 * - Python v3.7 or later is required. * * v13.2 * - Added support for SIP_ENUM_UINT_ENUM to represent IntEnums with unsigned * members. * * v13.1 * - Added sipNextExceptionHandler(). */ /* The version of the code generator. */ #define SIP_VERSION @_SIP_VERSION@ #define SIP_VERSION_STR "@_SIP_VERSION_STR@" /* These are all dependent on the user-specified name of the sip module. */ #define _SIP_MODULE_FQ_NAME "@_SIP_MODULE_FQ_NAME@" #define _SIP_MODULE_NAME "@_SIP_MODULE_NAME@" #define _SIP_MODULE_SHARED @_SIP_MODULE_SHARED@ #define _SIP_MODULE_ENTRY @_SIP_MODULE_ENTRY@ /* * Qt includes this typedef and its meta-object system explicitly converts * types to uint. If these correspond to signal arguments then that conversion * is exposed. Therefore SIP generates code that uses it. This definition is * for the cases that SIP is generating non-Qt related bindings with compilers * that don't include it themselves (i.e. MSVC). */ typedef unsigned int uint; /* Some C++ compatibility stuff. */ #if defined(__cplusplus) /* * Cast a PyCFunctionWithKeywords to a PyCFunction in such a way that it * suppresses the GCC -Wcast-function-type warning. */ #define SIP_MLMETH_CAST(m) reinterpret_cast(reinterpret_cast(m)) #if __cplusplus >= 201103L || defined(_MSVC_LANG) /* C++11 and later. */ #define SIP_NULLPTR nullptr #define SIP_OVERRIDE override #else /* Earlier versions of C++. */ #define SIP_NULLPTR NULL #define SIP_OVERRIDE #endif #else /* Cast a PyCFunctionWithKeywords to a PyCFunction. */ #define SIP_MLMETH_CAST(m) ((PyCFunction)(m)) /* C. */ #define SIP_NULLPTR NULL #define SIP_OVERRIDE #endif /* * The mask that can be passed to sipTrace(). */ #define SIP_TRACE_CATCHERS 0x0001 #define SIP_TRACE_CTORS 0x0002 #define SIP_TRACE_DTORS 0x0004 #define SIP_TRACE_INITS 0x0008 #define SIP_TRACE_DEALLOCS 0x0010 #define SIP_TRACE_METHODS 0x0020 /* * Hide some thread dependent stuff. This can be removed when support for * Python v3.6 is removed. */ #ifdef WITH_THREAD typedef PyGILState_STATE sip_gilstate_t; #define SIP_RELEASE_GIL(gs) PyGILState_Release(gs); #define SIP_BLOCK_THREADS {PyGILState_STATE sipGIL = PyGILState_Ensure(); #define SIP_UNBLOCK_THREADS PyGILState_Release(sipGIL);} #else typedef int sip_gilstate_t; #define SIP_RELEASE_GIL(gs) #define SIP_BLOCK_THREADS #define SIP_UNBLOCK_THREADS #endif /* * Forward declarations of types. */ struct _sipBufferDef; typedef struct _sipBufferDef sipBufferDef; struct _sipBufferInfoDef; typedef struct _sipBufferInfoDef sipBufferInfoDef; struct _sipCFunctionDef; typedef struct _sipCFunctionDef sipCFunctionDef; struct _sipDateDef; typedef struct _sipDateDef sipDateDef; struct _sipMethodDef; typedef struct _sipMethodDef sipMethodDef; struct _sipSimpleWrapper; typedef struct _sipSimpleWrapper sipSimpleWrapper; struct _sipTimeDef; typedef struct _sipTimeDef sipTimeDef; struct _sipTypeDef; typedef struct _sipTypeDef sipTypeDef; struct _sipWrapperType; typedef struct _sipWrapperType sipWrapperType; struct _sipWrapper; typedef struct _sipWrapper sipWrapper; /* * The different events a handler can be registered for. */ typedef enum { sipEventWrappedInstance, /* After wrapping a C/C++ instance. */ sipEventCollectingWrapper, /* When garbage collecting a wrapper object. */ sipEventNrEvents } sipEventType; /* * The event handlers. */ typedef void (*sipWrappedInstanceEventHandler)(void *sipCpp); typedef void (*sipCollectingWrapperEventHandler)(sipSimpleWrapper *sipSelf); /* * The operation an access function is being asked to perform. */ typedef enum { UnguardedPointer, /* Return the unguarded pointer. */ GuardedPointer, /* Return the guarded pointer, ie. 0 if it has gone. */ ReleaseGuard /* Release the guard, if any. */ } AccessFuncOp; /* * Some convenient function pointers. */ typedef void *(*sipInitFunc)(sipSimpleWrapper *, PyObject *, PyObject *, PyObject **, PyObject **, PyObject **); typedef int (*sipFinalFunc)(PyObject *, void *, PyObject *, PyObject **); typedef void *(*sipAccessFunc)(sipSimpleWrapper *, AccessFuncOp); typedef int (*sipTraverseFunc)(void *, visitproc, void *); typedef int (*sipClearFunc)(void *); typedef int (*sipGetBufferFuncLimited)(PyObject *, void *, sipBufferDef *); typedef void (*sipReleaseBufferFuncLimited)(PyObject *, void *); #if !defined(Py_LIMITED_API) typedef int (*sipGetBufferFunc)(PyObject *, void *, Py_buffer *, int); typedef void (*sipReleaseBufferFunc)(PyObject *, void *, Py_buffer *); #endif typedef void (*sipDeallocFunc)(sipSimpleWrapper *); typedef void *(*sipCastFunc)(void *, const sipTypeDef *); typedef const sipTypeDef *(*sipSubClassConvertFunc)(void **); typedef int (*sipConvertToFunc)(PyObject *, void **, int *, PyObject *, void **); typedef PyObject *(*sipConvertFromFunc)(void *, PyObject *); typedef void (*sipVirtErrorHandlerFunc)(sipSimpleWrapper *, sip_gilstate_t); typedef int (*sipVirtHandlerFunc)(sip_gilstate_t, sipVirtErrorHandlerFunc, sipSimpleWrapper *, PyObject *, ...); typedef void (*sipAssignFunc)(void *, Py_ssize_t, void *); typedef void *(*sipArrayFunc)(Py_ssize_t); typedef void (*sipArrayDeleteFunc)(void *); typedef void *(*sipCopyFunc)(const void *, Py_ssize_t); typedef void (*sipReleaseFunc)(void *, int); typedef void (*sipReleaseUSFunc)(void *, int, void *); typedef PyObject *(*sipPickleFunc)(void *); typedef int (*sipAttrGetterFunc)(const sipTypeDef *, PyObject *); typedef PyObject *(*sipVariableGetterFunc)(void *, PyObject *, PyObject *); typedef int (*sipVariableSetterFunc)(void *, PyObject *, PyObject *); typedef void *(*sipProxyResolverFunc)(void *); typedef void (*sipWrapperVisitorFunc)(sipSimpleWrapper *, void *); #if !defined(Py_LIMITED_API) /* * The meta-type of a wrapper type. */ struct _sipWrapperType { /* * The super-metatype. This must be first in the structure so that it can * be cast to a PyTypeObject *. */ PyHeapTypeObject super; /* Set if the type is a user implemented Python sub-class. */ unsigned wt_user_type : 1; /* Set if the type's dictionary contains all lazy attributes. */ unsigned wt_dict_complete : 1; /* Unused and available for future use. */ unsigned wt_unused : 30; /* The generated type structure. */ sipTypeDef *wt_td; /* The list of init extenders. */ struct _sipInitExtenderDef *wt_iextend; /* * For the user to use. Note that any data structure will leak if the * type is garbage collected. */ void *wt_user_data; /* Reserved for future use by this ABI version. */ void *wt_reserved; }; /* * The type of a simple C/C++ wrapper object. */ struct _sipSimpleWrapper { PyObject_HEAD /* * The data, initially a pointer to the C/C++ object, as interpreted by the * access function. */ void *data; /* The optional access function. */ sipAccessFunc access_func; /* Object flags. */ unsigned sw_flags; /* The optional dictionary of extra references keyed by argument number. */ PyObject *extra_refs; /* For the user to use. */ PyObject *user; /* The instance dictionary. */ PyObject *dict; /* The main instance if this is a mixin. */ PyObject *mixin_main; /* Reserved for future use by this ABI version. */ void *reserved; /* Next object at this address. */ struct _sipSimpleWrapper *next; }; /* * The type of a C/C++ wrapper object that supports parent/child relationships. */ struct _sipWrapper { /* The super-type. */ sipSimpleWrapper super; /* First child object. */ struct _sipWrapper *first_child; /* Next sibling. */ struct _sipWrapper *sibling_next; /* Previous sibling. */ struct _sipWrapper *sibling_prev; /* Owning object. */ struct _sipWrapper *parent; }; #endif /* * The information describing an encoded type ID. */ typedef struct _sipEncodedTypeDef { /* The type number. */ unsigned sc_type : 16; /* The module number (255 for this one). */ unsigned sc_module : 8; /* A context specific flag. */ unsigned sc_flag : 1; } sipEncodedTypeDef; /* * The information describing static instances. */ typedef struct _sipInstancesDef { /* The types. */ struct _sipTypeInstanceDef *id_type; /* The void *. */ struct _sipVoidPtrInstanceDef *id_voidp; /* The chars. */ struct _sipCharInstanceDef *id_char; /* The strings. */ struct _sipStringInstanceDef *id_string; /* The ints. */ struct _sipIntInstanceDef *id_int; /* The longs. */ struct _sipLongInstanceDef *id_long; /* The unsigned longs. */ struct _sipUnsignedLongInstanceDef *id_ulong; /* The long longs. */ struct _sipLongLongInstanceDef *id_llong; /* The unsigned long longs. */ struct _sipUnsignedLongLongInstanceDef *id_ullong; /* The doubles. */ struct _sipDoubleInstanceDef *id_double; } sipInstancesDef; /* * The information describing a type initialiser extender. */ typedef struct _sipInitExtenderDef { /* The extender function. */ sipInitFunc ie_extender; /* The class being extended. */ sipEncodedTypeDef ie_class; /* The next extender for this class. */ struct _sipInitExtenderDef *ie_next; } sipInitExtenderDef; /* * The information describing a sub-class convertor. */ typedef struct _sipSubClassConvertorDef { /* The convertor. */ sipSubClassConvertFunc scc_convertor; /* The encoded base type. */ sipEncodedTypeDef scc_base; /* The base type. */ struct _sipTypeDef *scc_basetype; } sipSubClassConvertorDef; /* * The structure populated by %BIGetBufferCode when the limited API is enabled. */ struct _sipBufferDef { /* The address of the buffer. */ void *bd_buffer; /* The length of the buffer. */ Py_ssize_t bd_length; /* Set if the buffer is read-only. */ int bd_readonly; }; /* * The structure describing a Python buffer. */ struct _sipBufferInfoDef { /* This is internal to sip. */ void *bi_internal; /* The address of the buffer. */ void *bi_buf; /* A reference to the object implementing the buffer interface. */ PyObject *bi_obj; /* The length of the buffer in bytes. */ Py_ssize_t bi_len; /* Set if the buffer is read-only. */ int bi_readonly; /* The format of each element of the buffer. */ char *bi_format; }; /* * The structure describing a Python C function. */ struct _sipCFunctionDef { /* The C function. */ PyMethodDef *cf_function; /* The optional bound object. */ PyObject *cf_self; }; /* * The structure describing a Python method. */ struct _sipMethodDef { /* The function that implements the method. */ PyObject *pm_function; /* The bound object. */ PyObject *pm_self; }; /* * The structure describing a Python date. */ struct _sipDateDef { /* The year. */ int pd_year; /* The month (1-12). */ int pd_month; /* The day (1-31). */ int pd_day; }; /* * The structure describing a Python time. */ struct _sipTimeDef { /* The hour (0-23). */ int pt_hour; /* The minute (0-59). */ int pt_minute; /* The second (0-59). */ int pt_second; /* The microsecond (0-999999). */ int pt_microsecond; }; /* * The different error states of handwritten code. */ typedef enum { sipErrorNone, /* There is no error. */ sipErrorFail, /* The error is a failure. */ sipErrorContinue /* It may not apply if a later operation succeeds. */ } sipErrorState; /* * The different Python slot types. New slots must be added to the end, * otherwise the major version of the internal ABI must be changed. */ typedef enum { str_slot, /* __str__ */ int_slot, /* __int__ */ float_slot, /* __float__ */ len_slot, /* __len__ */ contains_slot, /* __contains__ */ add_slot, /* __add__ for number */ concat_slot, /* __add__ for sequence types */ sub_slot, /* __sub__ */ mul_slot, /* __mul__ for number types */ repeat_slot, /* __mul__ for sequence types */ div_slot, /* __div__ */ mod_slot, /* __mod__ */ floordiv_slot, /* __floordiv__ */ truediv_slot, /* __truediv__ */ and_slot, /* __and__ */ or_slot, /* __or__ */ xor_slot, /* __xor__ */ lshift_slot, /* __lshift__ */ rshift_slot, /* __rshift__ */ iadd_slot, /* __iadd__ for number types */ iconcat_slot, /* __iadd__ for sequence types */ isub_slot, /* __isub__ */ imul_slot, /* __imul__ for number types */ irepeat_slot, /* __imul__ for sequence types */ idiv_slot, /* __idiv__ */ imod_slot, /* __imod__ */ ifloordiv_slot, /* __ifloordiv__ */ itruediv_slot, /* __itruediv__ */ iand_slot, /* __iand__ */ ior_slot, /* __ior__ */ ixor_slot, /* __ixor__ */ ilshift_slot, /* __ilshift__ */ irshift_slot, /* __irshift__ */ invert_slot, /* __invert__ */ call_slot, /* __call__ */ getitem_slot, /* __getitem__ */ setitem_slot, /* __setitem__ */ delitem_slot, /* __delitem__ */ lt_slot, /* __lt__ */ le_slot, /* __le__ */ eq_slot, /* __eq__ */ ne_slot, /* __ne__ */ gt_slot, /* __gt__ */ ge_slot, /* __ge__ */ bool_slot, /* __bool__, __nonzero__ */ neg_slot, /* __neg__ */ repr_slot, /* __repr__ */ hash_slot, /* __hash__ */ pos_slot, /* __pos__ */ abs_slot, /* __abs__ */ index_slot, /* __index__ */ iter_slot, /* __iter__ */ next_slot, /* __next__ */ setattr_slot, /* __setattr__, __delattr__ */ matmul_slot, /* __matmul__ (for Python v3.5 and later) */ imatmul_slot, /* __imatmul__ (for Python v3.5 and later) */ await_slot, /* __await__ (for Python v3.5 and later) */ aiter_slot, /* __aiter__ (for Python v3.5 and later) */ anext_slot, /* __anext__ (for Python v3.5 and later) */ } sipPySlotType; /* * The information describing a Python slot function. */ typedef struct _sipPySlotDef { /* The function. */ void *psd_func; /* The type. */ sipPySlotType psd_type; } sipPySlotDef; /* * The information describing a Python slot extender. */ typedef struct _sipPySlotExtenderDef { /* The function. */ void *pse_func; /* The type. */ sipPySlotType pse_type; /* The encoded class. */ sipEncodedTypeDef pse_class; } sipPySlotExtenderDef; /* * The information describing a typedef. */ typedef struct _sipTypedefDef { /* The typedef name. */ const char *tdd_name; /* The typedef value. */ const char *tdd_type_name; } sipTypedefDef; /* * The information describing a variable or property. */ typedef enum { PropertyVariable, /* A property. */ InstanceVariable, /* An instance variable. */ ClassVariable /* A class (i.e. static) variable. */ } sipVariableType; typedef struct _sipVariableDef { /* The type of variable. */ sipVariableType vd_type; /* The name. */ const char *vd_name; /* * The getter. If this is a variable (rather than a property) then the * actual type is sipVariableGetterFunc. */ PyMethodDef *vd_getter; /* * The setter. If this is a variable (rather than a property) then the * actual type is sipVariableSetterFunc. It is NULL if the property cannot * be set or the variable is const. */ PyMethodDef *vd_setter; /* The property deleter. */ PyMethodDef *vd_deleter; /* The docstring. */ const char *vd_docstring; } sipVariableDef; /* * The information describing a type, either a C++ class (or C struct), a C++ * namespace, a mapped type or a named enum. */ struct _sipTypeDef { /* * The module, 0 if the type hasn't been initialised. */ struct _sipExportedModuleDef *td_module; /* Type flags, see the sipType*() macros. */ int td_flags; /* The C/C++ name of the type. */ int td_cname; /* The Python type object. */ PyTypeObject *td_py_type; /* Any additional fixed data generated by a plugin. */ void *td_plugin_data; }; /* * The information describing a container (ie. a class, namespace or a mapped * type). */ typedef struct _sipContainerDef { /* * The Python name of the type, -1 if this is a namespace extender (in the * context of a class) or doesn't require a namespace (in the context of a * mapped type). */ int cod_name; /* * The scoping type or the namespace this is extending if it is a namespace * extender. */ sipEncodedTypeDef cod_scope; /* The number of lazy methods. */ int cod_nrmethods; /* The table of lazy methods. */ PyMethodDef *cod_methods; /* The number of variables. */ int cod_nrvariables; /* The table of variables. */ sipVariableDef *cod_variables; /* The static instances. */ sipInstancesDef cod_instances; } sipContainerDef; /* * The information describing a C++ class (or C struct) or a C++ namespace. */ typedef struct _sipClassTypeDef { /* The base type information. */ sipTypeDef ctd_base; /* The container information. */ sipContainerDef ctd_container; /* The docstring. */ const char *ctd_docstring; /* * The meta-type name, -1 to use the meta-type of the first super-type * (normally sipWrapperType). */ int ctd_metatype; /* The super-type name, -1 to use sipWrapper. */ int ctd_supertype; /* The super-types. */ sipEncodedTypeDef *ctd_supers; /* The table of Python slots. */ sipPySlotDef *ctd_pyslots; /* The initialisation function. */ sipInitFunc ctd_init; /* The traverse function. */ sipTraverseFunc ctd_traverse; /* The clear function. */ sipClearFunc ctd_clear; /* The get buffer function. */ #if defined(Py_LIMITED_API) sipGetBufferFuncLimited ctd_getbuffer; #else sipGetBufferFunc ctd_getbuffer; #endif /* The release buffer function. */ #if defined(Py_LIMITED_API) sipReleaseBufferFuncLimited ctd_releasebuffer; #else sipReleaseBufferFunc ctd_releasebuffer; #endif /* The deallocation function. */ sipDeallocFunc ctd_dealloc; /* The optional assignment function. */ sipAssignFunc ctd_assign; /* The optional array allocation function. */ sipArrayFunc ctd_array; /* The optional copy function. */ sipCopyFunc ctd_copy; /* The release function, 0 if a C struct. */ sipReleaseFunc ctd_release; /* The cast function, 0 if a C struct. */ sipCastFunc ctd_cast; /* The optional convert to function. */ sipConvertToFunc ctd_cto; /* The optional convert from function. */ sipConvertFromFunc ctd_cfrom; /* The next namespace extender. */ struct _sipClassTypeDef *ctd_nsextender; /* The pickle function. */ sipPickleFunc ctd_pickle; /* The finalisation function. */ sipFinalFunc ctd_final; /* The mixin initialisation function. */ initproc ctd_init_mixin; /* The optional array delete function. */ sipArrayDeleteFunc ctd_array_delete; /* The sizeof the class. */ size_t ctd_sizeof; } sipClassTypeDef; /* * The information describing a mapped type. */ typedef struct _sipMappedTypeDef { /* The base type information. */ sipTypeDef mtd_base; /* The container information. */ sipContainerDef mtd_container; /* The optional assignment function. */ sipAssignFunc mtd_assign; /* The optional array allocation function. */ sipArrayFunc mtd_array; /* The optional copy function. */ sipCopyFunc mtd_copy; /* The optional release function. */ sipReleaseUSFunc mtd_release; /* The optional convert to function. */ sipConvertToFunc mtd_cto; /* The optional convert from function. */ sipConvertFromFunc mtd_cfrom; } sipMappedTypeDef; /* * The information describing a named enum. */ typedef struct _sipEnumTypeDef { /* The base type information. */ sipTypeDef etd_base; /* The Python base type. */ int etd_base_type; /* The Python name of the enum. */ int etd_name; /* The scoping type, -1 if it is defined at the module level. */ int etd_scope; /* The number of members. */ int etd_nr_members; /* The Python slots. */ struct _sipPySlotDef *etd_pyslots; } sipEnumTypeDef; /* * The information describing an external type. */ typedef struct _sipExternalTypeDef { /* The index into the type table. */ int et_nr; /* The name of the type. */ const char *et_name; } sipExternalTypeDef; /* * Defines an entry in the module specific list of delayed dtor calls. */ typedef struct _sipDelayedDtor { /* The C/C++ instance. */ void *dd_ptr; /* The class name. */ const char *dd_name; /* Non-zero if dd_ptr is a derived class instance. */ int dd_isderived; /* Next in the list. */ struct _sipDelayedDtor *dd_next; } sipDelayedDtor; /* * Defines a virtual error handler. */ typedef struct _sipVirtErrorHandlerDef { /* The name of the handler. */ const char *veh_name; /* The handler function. */ sipVirtErrorHandlerFunc veh_handler; } sipVirtErrorHandlerDef; /* * Defines a type imported from another module. */ typedef union _sipImportedTypeDef { /* The type name before the module is imported. */ const char *it_name; /* The type after the module is imported. */ sipTypeDef *it_td; } sipImportedTypeDef; /* * Defines a virtual error handler imported from another module. */ typedef union _sipImportedVirtErrorHandlerDef { /* The handler name before the module is imported. */ const char *iveh_name; /* The handler after the module is imported. */ sipVirtErrorHandlerFunc iveh_handler; } sipImportedVirtErrorHandlerDef; /* * Defines an exception imported from another module. */ typedef union _sipImportedExceptionDef { /* The exception name before the module is imported. */ const char *iexc_name; /* The exception object after the module is imported. */ PyObject *iexc_object; } sipImportedExceptionDef; /* * The information describing an imported module. */ typedef struct _sipImportedModuleDef { /* The module name. */ const char *im_name; /* The types imported from the module. */ sipImportedTypeDef *im_imported_types; /* The virtual error handlers imported from the module. */ sipImportedVirtErrorHandlerDef *im_imported_veh; /* The exceptions imported from the module. */ sipImportedExceptionDef *im_imported_exceptions; } sipImportedModuleDef; /* * The main client module structure. */ typedef struct _sipExportedModuleDef { /* The next in the list. */ struct _sipExportedModuleDef *em_next; /* The SIP API minor version number. */ unsigned em_api_minor; /* The module name. */ int em_name; /* The module name as an object. */ PyObject *em_nameobj; /* The string pool. */ const char *em_strings; /* The imported modules. */ sipImportedModuleDef *em_imports; /* The number of types. */ int em_nrtypes; /* The table of types. */ sipTypeDef **em_types; /* The table of external types. */ sipExternalTypeDef *em_external; /* The number of typedefs. */ int em_nrtypedefs; /* The table of typedefs. */ sipTypedefDef *em_typedefs; /* The table of virtual error handlers. */ sipVirtErrorHandlerDef *em_virterrorhandlers; /* The sub-class convertors. */ sipSubClassConvertorDef *em_convertors; /* The static instances. */ sipInstancesDef em_instances; /* The license. */ struct _sipLicenseDef *em_license; /* The table of exception types. */ PyObject **em_exceptions; /* The table of Python slot extenders. */ sipPySlotExtenderDef *em_slotextend; /* The table of initialiser extenders. */ sipInitExtenderDef *em_initextend; /* The delayed dtor handler. */ void (*em_delayeddtors)(const sipDelayedDtor *); /* The list of delayed dtors. */ sipDelayedDtor *em_ddlist; /* The exception handler. */ sipExceptionHandler em_exception_handler; } sipExportedModuleDef; /* * The information describing a license to be added to a dictionary. */ typedef struct _sipLicenseDef { /* The type of license. */ const char *lc_type; /* The licensee. */ const char *lc_licensee; /* The timestamp. */ const char *lc_timestamp; /* The signature. */ const char *lc_signature; } sipLicenseDef; /* * The information describing a void pointer instance to be added to a * dictionary. */ typedef struct _sipVoidPtrInstanceDef { /* The void pointer name. */ const char *vi_name; /* The void pointer value. */ void *vi_val; } sipVoidPtrInstanceDef; /* * The information describing a char instance to be added to a dictionary. */ typedef struct _sipCharInstanceDef { /* The char name. */ const char *ci_name; /* The char value. */ char ci_val; /* The encoding used, either 'A', 'L', '8' or 'N'. */ char ci_encoding; } sipCharInstanceDef; /* * The information describing a string instance to be added to a dictionary. * This is also used as a hack to add (or fix) other types rather than add a * new table type and so requiring a new major version of the API. */ typedef struct _sipStringInstanceDef { /* The string name. */ const char *si_name; /* The string value. */ const char *si_val; /* * The encoding used, either 'A', 'L', '8' or 'N'. 'w' and 'W' are also * used to support the fix for wchar_t. */ char si_encoding; } sipStringInstanceDef; /* * The information describing an int instance to be added to a dictionary. */ typedef struct _sipIntInstanceDef { /* The int name. */ const char *ii_name; /* The int value. */ int ii_val; } sipIntInstanceDef; /* * The information describing a long instance to be added to a dictionary. */ typedef struct _sipLongInstanceDef { /* The long name. */ const char *li_name; /* The long value. */ long li_val; } sipLongInstanceDef; /* * The information describing an unsigned long instance to be added to a * dictionary. */ typedef struct _sipUnsignedLongInstanceDef { /* The unsigned long name. */ const char *uli_name; /* The unsigned long value. */ unsigned long uli_val; } sipUnsignedLongInstanceDef; /* * The information describing a long long instance to be added to a dictionary. */ typedef struct _sipLongLongInstanceDef { /* The long long name. */ const char *lli_name; /* The long long value. */ long long lli_val; } sipLongLongInstanceDef; /* * The information describing an unsigned long long instance to be added to a * dictionary. */ typedef struct _sipUnsignedLongLongInstanceDef { /* The unsigned long long name. */ const char *ulli_name; /* The unsigned long long value. */ unsigned long long ulli_val; } sipUnsignedLongLongInstanceDef; /* * The information describing a double instance to be added to a dictionary. */ typedef struct _sipDoubleInstanceDef { /* The double name. */ const char *di_name; /* The double value. */ double di_val; } sipDoubleInstanceDef; /* * The information describing a class or enum instance to be added to a * dictionary. */ typedef struct _sipTypeInstanceDef { /* The type instance name. */ const char *ti_name; /* The actual instance. */ void *ti_ptr; /* A pointer to the generated type. */ struct _sipTypeDef **ti_type; /* The wrapping flags. */ int ti_flags; } sipTypeInstanceDef; /* * The API exported by the SIP module, ie. pointers to all the data and * functions that can be used by generated code. */ typedef struct _sipAPIDef { /* * This must be the first entry and it's signature must not change so that * version number mismatches can be detected and reported. */ int (*api_export_module)(sipExportedModuleDef *client, unsigned api_major, unsigned api_minor, void *unused); /* * The following are part of the public API. */ PyTypeObject *api_simplewrapper_type; PyTypeObject *api_wrapper_type; PyTypeObject *api_wrappertype_type; PyTypeObject *api_voidptr_type; void (*api_bad_catcher_result)(PyObject *method); void (*api_bad_length_for_slice)(Py_ssize_t seqlen, Py_ssize_t slicelen); PyObject *(*api_build_result)(int *isErr, const char *fmt, ...); PyObject *(*api_call_method)(int *isErr, PyObject *method, const char *fmt, ...); void (*api_call_procedure_method)(sip_gilstate_t, sipVirtErrorHandlerFunc, sipSimpleWrapper *, PyObject *, const char *, ...); Py_ssize_t (*api_convert_from_sequence_index)(Py_ssize_t idx, Py_ssize_t len); int (*api_can_convert_to_type)(PyObject *pyObj, const sipTypeDef *td, int flags); void *(*api_convert_to_type)(PyObject *pyObj, const sipTypeDef *td, PyObject *transferObj, int flags, int *statep, int *iserrp); void *(*api_convert_to_type_us)(PyObject *pyObj, const sipTypeDef *td, PyObject *transferObj, int flags, int *statep, void **user_statep, int *iserrp); void *(*api_force_convert_to_type)(PyObject *pyObj, const sipTypeDef *td, PyObject *transferObj, int flags, int *statep, int *iserrp); void *(*api_force_convert_to_type_us)(PyObject *pyObj, const sipTypeDef *td, PyObject *transferObj, int flags, int *statep, void **user_statep, int *iserrp); void (*api_release_type)(void *cpp, const sipTypeDef *td, int state); void (*api_release_type_us)(void *cpp, const sipTypeDef *td, int state, void *user_state); PyObject *(*api_convert_from_type)(void *cpp, const sipTypeDef *td, PyObject *transferObj); PyObject *(*api_convert_from_new_type)(void *cpp, const sipTypeDef *td, PyObject *transferObj); PyObject *(*api_convert_from_enum)(int eval, const sipTypeDef *td); int (*api_get_state)(PyObject *transferObj); void (*api_free)(void *mem); PyObject *(*api_get_pyobject)(void *cppPtr, const sipTypeDef *td); void *(*api_malloc)(size_t nbytes); int (*api_parse_result)(int *isErr, PyObject *method, PyObject *res, const char *fmt, ...); void (*api_trace)(unsigned mask, const char *fmt, ...); void (*api_transfer_back)(PyObject *self); void (*api_transfer_to)(PyObject *self, PyObject *owner); unsigned long (*api_long_as_unsigned_long)(PyObject *o); PyObject *(*api_convert_from_void_ptr)(void *val); PyObject *(*api_convert_from_const_void_ptr)(const void *val); PyObject *(*api_convert_from_void_ptr_and_size)(void *val, Py_ssize_t size); PyObject *(*api_convert_from_const_void_ptr_and_size)(const void *val, Py_ssize_t size); void *(*api_convert_to_void_ptr)(PyObject *obj); int (*api_export_symbol)(const char *name, void *sym); void *(*api_import_symbol)(const char *name); const sipTypeDef *(*api_find_type)(const char *type); int (*api_register_py_type)(PyTypeObject *type); const sipTypeDef *(*api_type_from_py_type_object)(PyTypeObject *py_type); const sipTypeDef *(*api_type_scope)(const sipTypeDef *td); const char *(*api_resolve_typedef)(const char *name); int (*api_register_attribute_getter)(const sipTypeDef *td, sipAttrGetterFunc getter); sipErrorState (*api_bad_callable_arg)(int arg_nr, PyObject *arg); void *(*api_get_address)(struct _sipSimpleWrapper *w); int (*api_enable_autoconversion)(const sipTypeDef *td, int enable); void *(*api_get_mixin_address)(struct _sipSimpleWrapper *w, const sipTypeDef *td); PyObject *(*api_convert_from_new_pytype)(void *cpp, PyTypeObject *py_type, sipWrapper *owner, sipSimpleWrapper **selfp, const char *fmt, ...); PyObject *(*api_convert_to_typed_array)(void *data, const sipTypeDef *td, const char *format, size_t stride, Py_ssize_t len, int flags); PyObject *(*api_convert_to_array)(void *data, const char *format, Py_ssize_t len, int flags); int (*api_register_proxy_resolver)(const sipTypeDef *td, sipProxyResolverFunc resolver); PyInterpreterState *(*api_get_interpreter)(void); void (*api_set_type_user_data)(sipWrapperType *, void *); void *(*api_get_type_user_data)(const sipWrapperType *); PyObject *(*api_py_type_dict)(const PyTypeObject *); const char *(*api_py_type_name)(const PyTypeObject *); int (*api_get_method)(PyObject *, sipMethodDef *); PyObject *(*api_from_method)(const sipMethodDef *); int (*api_get_c_function)(PyObject *, sipCFunctionDef *); int (*api_get_date)(PyObject *, sipDateDef *); PyObject *(*api_from_date)(const sipDateDef *); int (*api_get_datetime)(PyObject *, sipDateDef *, sipTimeDef *); PyObject *(*api_from_datetime)(const sipDateDef *, const sipTimeDef *); int (*api_get_time)(PyObject *, sipTimeDef *); PyObject *(*api_from_time)(const sipTimeDef *); int (*api_is_user_type)(const sipWrapperType *); int (*api_check_plugin_for_type)(const sipTypeDef *, const char *); PyObject *(*api_unicode_new)(Py_ssize_t, unsigned, int *, void **); void (*api_unicode_write)(int, void *, int, unsigned); void *(*api_unicode_data)(PyObject *, int *, Py_ssize_t *); int (*api_get_buffer_info)(PyObject *, sipBufferInfoDef *); void (*api_release_buffer_info)(sipBufferInfoDef *); PyObject *(*api_get_user_object)(const sipSimpleWrapper *); void (*api_set_user_object)(sipSimpleWrapper *, PyObject *); void (*api_instance_destroyed)(sipSimpleWrapper *sipSelf); int (*api_is_owned_by_python)(sipSimpleWrapper *); int (*api_enable_gc)(int enable); void (*api_print_object)(PyObject *o); int (*api_register_event_handler)(sipEventType type, const sipTypeDef *td, void *handler); int (*api_convert_to_enum)(PyObject *obj, const sipTypeDef *td); int (*api_convert_to_bool)(PyObject *obj); char (*api_long_as_char)(PyObject *o); signed char (*api_long_as_signed_char)(PyObject *o); unsigned char (*api_long_as_unsigned_char)(PyObject *o); short (*api_long_as_short)(PyObject *o); unsigned short (*api_long_as_unsigned_short)(PyObject *o); int (*api_long_as_int)(PyObject *o); unsigned int (*api_long_as_unsigned_int)(PyObject *o); long (*api_long_as_long)(PyObject *o); long long (*api_long_as_long_long)(PyObject *o); unsigned long long (*api_long_as_unsigned_long_long)(PyObject *o); int (*api_convert_from_slice_object)(PyObject *slice, Py_ssize_t length, Py_ssize_t *start, Py_ssize_t *stop, Py_ssize_t *step, Py_ssize_t *slicelength); size_t (*api_long_as_size_t)(PyObject *o); void (*api_visit_wrappers)(sipWrapperVisitorFunc visitor, void *closure); int (*api_register_exit_notifier)(PyMethodDef *md); int (*api_is_enum_flag)(PyObject *o); PyObject *(*api_py_type_dict_ref)(PyTypeObject *); void (*unused_public_2)(void); void (*unused_public_3)(void); void (*unused_public_4)(void); void (*unused_public_5)(void); /* * The following are not part of the public API. */ int (*api_init_module)(sipExportedModuleDef *client, PyObject *mod_dict); int (*api_parse_args)(PyObject **parseErrp, PyObject *sipArgs, const char *fmt, ...); int (*api_parse_pair)(PyObject **parseErrp, PyObject *arg0, PyObject *arg1, const char *fmt, ...); void (*api_no_function)(PyObject *parseErr, const char *func, const char *doc); void (*api_no_method)(PyObject *parseErr, const char *scope, const char *method, const char *doc); void (*api_abstract_method)(const char *classname, const char *method); void (*api_bad_class)(const char *classname); void *(*api_get_cpp_ptr)(sipSimpleWrapper *w, const sipTypeDef *td); void *(*api_get_complex_cpp_ptr)(sipSimpleWrapper *w); PyObject *(*api_is_py_method)(sip_gilstate_t *gil, char *pymc, sipSimpleWrapper *sipSelf, const char *cname, const char *mname); void (*api_call_hook)(const char *hookname); void (*api_end_thread)(void); void (*api_raise_unknown_exception)(void); void (*api_raise_type_exception)(const sipTypeDef *td, void *ptr); int (*api_add_type_instance)(PyObject *dict, const char *name, void *cppPtr, const sipTypeDef *td); void (*api_bad_operator_arg)(PyObject *self, PyObject *arg, sipPySlotType st); PyObject *(*api_pyslot_extend)(sipExportedModuleDef *mod, sipPySlotType st, const sipTypeDef *type, PyObject *arg0, PyObject *arg1); void (*api_add_delayed_dtor)(sipSimpleWrapper *w); char (*api_bytes_as_char)(PyObject *obj); const char *(*api_bytes_as_string)(PyObject *obj); char (*api_string_as_ascii_char)(PyObject *obj); const char *(*api_string_as_ascii_string)(PyObject **obj); char (*api_string_as_latin1_char)(PyObject *obj); const char *(*api_string_as_latin1_string)(PyObject **obj); char (*api_string_as_utf8_char)(PyObject *obj); const char *(*api_string_as_utf8_string)(PyObject **obj); #if defined(HAVE_WCHAR_H) wchar_t (*api_unicode_as_wchar)(PyObject *obj); wchar_t *(*api_unicode_as_wstring)(PyObject *obj); #else int (*api_unicode_as_wchar)(PyObject *obj); int *(*api_unicode_as_wstring)(PyObject *obj); #endif int (*api_deprecated)(const char *classname, const char *method); void (*api_keep_reference)(PyObject *self, int key, PyObject *obj); int (*api_parse_kwd_args)(PyObject **parseErrp, PyObject *sipArgs, PyObject *sipKwdArgs, const char **kwdlist, PyObject **unused, const char *fmt, ...); void (*api_add_exception)(sipErrorState es, PyObject **parseErrp); int (*api_parse_result_ex)(sip_gilstate_t, sipVirtErrorHandlerFunc, sipSimpleWrapper *, PyObject *method, PyObject *res, const char *fmt, ...); void (*api_call_error_handler)(sipVirtErrorHandlerFunc, sipSimpleWrapper *, sip_gilstate_t); int (*api_init_mixin)(PyObject *self, PyObject *args, PyObject *kwds, const sipClassTypeDef *ctd); PyObject *(*api_get_reference)(PyObject *self, int key); int (*api_is_derived_class)(sipSimpleWrapper *); void (*api_instance_destroyed_ex)(sipSimpleWrapper **sipSelfp); PyObject *(*api_is_py_method_12_8)(sip_gilstate_t *gil, char *pymc, sipSimpleWrapper **sipSelfp, const char *cname, const char *mname); sipExceptionHandler (*api_next_exception_handler)(void **statep); void (*unused_private_2)(void); void (*unused_private_3)(void); void (*unused_private_4)(void); void (*unused_private_5)(void); } sipAPIDef; const sipAPIDef *sip_init_library(PyObject *mod_dict); /* * These are flags that can be passed to sipCanConvertToType(), * sipConvertToType() and sipForceConvertToType(). */ #define SIP_NOT_NONE 0x01 /* Disallow None. */ #define SIP_NO_CONVERTORS 0x02 /* Disable any type convertors. */ /* * These are flags that can be passed to sipConvertToArray(). */ #define SIP_READ_ONLY 0x01 /* The array is read-only. */ #define SIP_OWNS_MEMORY 0x02 /* The array owns its memory. */ /* * These are the state flags returned by %ConvertToTypeCode. Note that the * values share the same "flagspace" as the contents of sw_flags. */ #define SIP_TEMPORARY 0x0001 /* A temporary instance. */ #define SIP_DERIVED_CLASS 0x0002 /* The instance is derived. */ #define SIP_USER 0x0004 /* For use by the user. */ #define SIP_RESERVED 0x0008 /* Reserved for future use by this ABI version. */ /* * These flags are specific to the Qt support API. */ #define SIP_SINGLE_SHOT 0x0001 /* The connection is single shot. */ /* * Useful macros, not part of the public API. */ /* These are held in sw_flags. */ #define SIP_INDIRECT 0x0010 /* If there is a level of indirection. */ #define SIP_ACCFUNC 0x0020 /* If there is an access function. */ #define SIP_NOT_IN_MAP 0x0040 /* If Python object is not in the map. */ #if !defined(Py_LIMITED_API) #define SIP_PY_OWNED 0x0080 /* If owned by Python. */ #define SIP_SHARE_MAP 0x0100 /* If the map slot might be occupied. */ #define SIP_CPP_HAS_REF 0x0200 /* If C/C++ has a reference. */ #define SIP_POSSIBLE_PROXY 0x0400 /* If there might be a proxy slot. */ #define SIP_ALIAS 0x0800 /* If it is an alias. */ #define SIP_CREATED 0x1000 /* If the C/C++ object has been created. */ #define sipIsDerived(sw) ((sw)->sw_flags & SIP_DERIVED_CLASS) #define sipIsIndirect(sw) ((sw)->sw_flags & SIP_INDIRECT) #define sipIsAccessFunc(sw) ((sw)->sw_flags & SIP_ACCFUNC) #define sipNotInMap(sw) ((sw)->sw_flags & SIP_NOT_IN_MAP) #define sipSetNotInMap(sw) ((sw)->sw_flags |= SIP_NOT_IN_MAP) #define sipIsPyOwned(sw) ((sw)->sw_flags & SIP_PY_OWNED) #define sipSetPyOwned(sw) ((sw)->sw_flags |= SIP_PY_OWNED) #define sipResetPyOwned(sw) ((sw)->sw_flags &= ~SIP_PY_OWNED) #define sipCppHasRef(sw) ((sw)->sw_flags & SIP_CPP_HAS_REF) #define sipSetCppHasRef(sw) ((sw)->sw_flags |= SIP_CPP_HAS_REF) #define sipResetCppHasRef(sw) ((sw)->sw_flags &= ~SIP_CPP_HAS_REF) #define sipPossibleProxy(sw) ((sw)->sw_flags & SIP_POSSIBLE_PROXY) #define sipSetPossibleProxy(sw) ((sw)->sw_flags |= SIP_POSSIBLE_PROXY) #define sipIsAlias(sw) ((sw)->sw_flags & SIP_ALIAS) #define sipWasCreated(sw) ((sw)->sw_flags & SIP_CREATED) #endif #define SIP_TYPE_TYPE_MASK 0x0003 /* The type type mask. */ #define SIP_TYPE_CLASS 0x0000 /* If the type is a C++ class. */ #define SIP_TYPE_NAMESPACE 0x0001 /* If the type is a C++ namespace. */ #define SIP_TYPE_MAPPED 0x0002 /* If the type is a mapped type. */ #define SIP_TYPE_ENUM 0x0003 /* If the type is an enum. */ #define SIP_TYPE_USER_STATE 0x0004 /* If the convertors need user state. */ #define SIP_TYPE_ABSTRACT 0x0008 /* If the type is abstract. */ #define SIP_TYPE_SCC 0x0010 /* If the type is subject to sub-class convertors. */ #define SIP_TYPE_ALLOW_NONE 0x0020 /* If the type can handle None. */ #define SIP_TYPE_STUB 0x0040 /* If the type is a stub. */ #define SIP_TYPE_NONLAZY 0x0080 /* If the type has a non-lazy method. */ #define SIP_TYPE_SUPER_INIT 0x0100 /* If the instance's super init should be called. */ #define SIP_TYPE_LIMITED_API 0x0200 /* Use the limited API. If this is more generally required it may need to be moved to the module definition. */ /* The Python base types of enums. */ #define SIP_ENUM_ENUM 0 /* The base type is Enum. */ #define SIP_ENUM_FLAG 1 /* The base type is Flag. */ #define SIP_ENUM_INT_ENUM 2 /* The base type is IntEnum. */ #define SIP_ENUM_INT_FLAG 3 /* The base type is IntFlag. */ #define SIP_ENUM_UINT_ENUM 4 /* The base type is IntEnum with unsigned members. */ /* * The following are part of the public API. */ #define sipTypeIsClass(td) (((td)->td_flags & SIP_TYPE_TYPE_MASK) == SIP_TYPE_CLASS) #define sipTypeIsNamespace(td) (((td)->td_flags & SIP_TYPE_TYPE_MASK) == SIP_TYPE_NAMESPACE) #define sipTypeIsMapped(td) (((td)->td_flags & SIP_TYPE_TYPE_MASK) == SIP_TYPE_MAPPED) #define sipTypeIsEnum(td) (((td)->td_flags & SIP_TYPE_TYPE_MASK) == SIP_TYPE_ENUM) #define sipTypeAsPyTypeObject(td) ((td)->td_py_type) #define sipTypeName(td) sipNameFromPool((td)->td_module, (td)->td_cname) #define sipTypePluginData(td) ((td)->td_plugin_data) /* * The following are not part of the public API. */ #define sipTypeNeedsUserState(td) ((td)->td_flags & SIP_TYPE_USER_STATE) #define sipTypeIsAbstract(td) ((td)->td_flags & SIP_TYPE_ABSTRACT) #define sipTypeHasSCC(td) ((td)->td_flags & SIP_TYPE_SCC) #define sipTypeAllowNone(td) ((td)->td_flags & SIP_TYPE_ALLOW_NONE) #define sipTypeIsStub(td) ((td)->td_flags & SIP_TYPE_STUB) #define sipTypeSetStub(td) ((td)->td_flags |= SIP_TYPE_STUB) #define sipTypeHasNonlazyMethod(td) ((td)->td_flags & SIP_TYPE_NONLAZY) #define sipTypeCallSuperInit(td) ((td)->td_flags & SIP_TYPE_SUPER_INIT) #define sipTypeUseLimitedAPI(td) ((td)->td_flags & SIP_TYPE_LIMITED_API) /* * Get various names from the string pool for various data types. */ #define sipNameFromPool(em, mr) (&((em)->em_strings)[(mr)]) #define sipNameOfModule(em) sipNameFromPool((em), (em)->em_name) #define sipPyNameOfContainer(cod, td) sipNameFromPool((td)->td_module, (cod)->cod_name) #define sipPyNameOfEnum(etd) sipNameFromPool((etd)->etd_base.td_module, (etd)->etd_name) /* * The following are PyQt-specific extensions. These may be removed in SIP v7. * Note that, at the moment, the PyQt5 and PyQt6 extensions are identical. */ /* * The description of a Qt signal for PyQt5. */ typedef int (*pyqt5EmitFunc)(void *, PyObject *); typedef struct _pyqt5QtSignal { /* The normalised C++ name and signature of the signal. */ const char *signature; /* The optional docstring. */ const char *docstring; /* * If the signal is an overload of regular methods then this points to the * code that implements those methods. */ PyMethodDef *non_signals; /* * If the signal has optional arguments then this function will implement * emit() for the signal. */ pyqt5EmitFunc emitter; } pyqt5QtSignal; /* * This is the PyQt5-specific extension to the generated class type structure. */ typedef struct _pyqt5ClassPluginDef { /* A pointer to the QObject sub-class's staticMetaObject class variable. */ const void *static_metaobject; /* * A set of flags. At the moment only bit 0 is used to say if the type is * derived from QFlags. */ unsigned flags; /* * The table of signals emitted by the type. These are grouped by signal * name. */ const pyqt5QtSignal *qt_signals; /* The name of the interface that the class defines. */ const char *qt_interface; } pyqt5ClassPluginDef; /* * The description of a Qt signal for PyQt6. */ typedef int (*pyqt6EmitFunc)(void *, PyObject *); typedef struct _pyqt6QtSignal { /* The normalised C++ name and signature of the signal. */ const char *signature; /* The optional docstring. */ const char *docstring; /* * If the signal is an overload of regular methods then this points to the * code that implements those methods. */ PyMethodDef *non_signals; /* * If the signal has optional arguments then this function will implement * emit() for the signal. */ pyqt6EmitFunc emitter; } pyqt6QtSignal; /* * This is the PyQt6-specific extension to the generated class type structure. */ typedef struct _pyqt6ClassPluginDef { /* A pointer to the QObject sub-class's staticMetaObject class variable. */ const void *static_metaobject; /* * The table of signals emitted by the type. These are grouped by signal * name. */ const pyqt6QtSignal *qt_signals; /* The name of the interface that the class defines. */ const char *qt_interface; } pyqt6ClassPluginDef; /* * This is the PyQt6-specific extension to the generated mapped type structure. */ typedef struct _pyqt6MappedTypePluginDef { /* * A set of flags. At the moment only bit 0 is used to say if the type is * mapped to/from QFlags. */ unsigned flags; } pyqt6MappedTypePluginDef; #ifdef __cplusplus } #endif #endif sip-6.8.6/sipbuild/module/source/13/sip.pyi000066400000000000000000000053251464421045000204740ustar00rootroot00000000000000# SPDX-License-Identifier: BSD-2-Clause # Copyright (c) 2024 Phil Thompson from typing import Any, Generic, Iterable, overload, Sequence, TypeVar, Union # PEP 484 has no explicit support for the buffer protocol so we just name types # we know that implement it. Buffer = Union[bytes, bytearray, memoryview, 'array', 'voidptr'] # Constants. SIP_VERSION = ... # type: int SIP_VERSION_STR = ... # type: str # The bases for SIP generated types. class wrappertype: def __init__(self, *args, **kwargs) -> None: ... class simplewrapper: def __init__(self, *args, **kwargs) -> None: ... class wrapper(simplewrapper): ... # The array type. _T = TypeVar('_T') class array(Sequence[_T], Generic[_T]): @overload def __getitem__(self, key: int) -> _T: ... @overload def __getitem__(self, key: slice) -> 'array[_T]': ... @overload def __setitem__(self, key: int, value: _T) -> None: ... @overload def __setitem__(self, key: slice, value: Iterable[_T]) -> None: ... @overload def __delitem__(self, key: int) -> None: ... @overload def __delitem__(self, key: slice) -> None: ... def __len__(self) -> int: ... # The voidptr type. class voidptr: def __init__(self, addr: Union[int, Buffer], size: int = -1, writeable: bool = True) -> None: ... def __int__(self) -> int: ... @overload def __getitem__(self, i: int) -> bytes: ... @overload def __getitem__(self, s: slice) -> 'voidptr': ... def __len__(self) -> int: ... def __setitem__(self, i: Union[int, slice], v: Buffer) -> None: ... def asarray(self, size: int = -1) -> array[int]: ... # Python doesn't expose the capsule type. def ascapsule(self) -> Any: ... def asstring(self, size: int = -1) -> bytes: ... def getsize(self) -> int: ... def getwriteable(self) -> bool: ... def setsize(self, size: int) -> None: ... def setwriteable(self, writeable: bool) -> None: ... # Remaining functions. def assign(obj: simplewrapper, other: simplewrapper) -> None: ... def cast(obj: simplewrapper, type: wrappertype) -> simplewrapper: ... def delete(obj: simplewrapper) -> None: ... def dump(obj: simplewrapper) -> None: ... def enableautoconversion(type: wrappertype, enable: bool) -> bool: ... def isdeleted(obj: simplewrapper) -> bool: ... def ispycreated(obj: simplewrapper) -> bool: ... def ispyowned(obj: simplewrapper) -> bool: ... def setdeleted(obj: simplewrapper) -> None: ... def settracemask(mask: int) -> None: ... def transferback(obj: wrapper) -> None: ... def transferto(obj: wrapper, owner: wrapper) -> None: ... def unwrapinstance(obj: simplewrapper) -> None: ... def wrapinstance(addr: int, type: wrappertype) -> simplewrapper: ... sip-6.8.6/sipbuild/module/source/13/sip.rst.in000066400000000000000000000332141464421045000211060ustar00rootroot00000000000000The main purpose of the :py:mod:`~@SIP_MODULE_FQ_NAME@` module is to provide functionality common to all SIP generated bindings. It is loaded automatically and most of the time you will completely ignore it. However, it does expose some functionality that can be used by applications. .. py:class:: @SIP_MODULE_FQ_NAME@.array This is the type object for the type SIP uses to represent an array of wrapped C/C++ instances. (It can also present an array of a limited number of basic C/C++ types but such arrays cannot, at the moment, be created from Python.) Arrays can be indexed and elements can be modified in situ. Arrays cannot be resized. Arrays support the buffer protocol. .. py:method:: __init__(type, nr_elements) :param type: the type of an array element. :param nr_elements: the number of elements in the array. For a C++ class each element of the array is created by calling the class's argumentless constructor. For a C structure then the memory is simply allocated on the heap. .. py:method:: __getitem__(idx) This returns the element at a given index. It is not a copy of the element. If this is called a number of times for the same index then a different Python object will be returned each time but each will refer to the same C/C++ instance. :param idx: is the index which may either be an integer, an object that implements ``__index__()`` or a slice object. :return: the element. If the index is an integer then the item will be a single object of the type of the array. If the index is a slice object then the item will be a new :py:class:`~@SIP_MODULE_FQ_NAME@.array` object containing the chosen subset of the original array. .. py:method:: __len__() This returns the length of the array. :return: the number of elements in the array. .. py:method:: __setitem__(idx, item) This updates the array at a given index. :param idx: is the index which may either be an integer, an object that implements ``__index__()`` or a slice object. :param item: is the item that will be assigned to the element currently at the index. It must have the same type as the element it is being assigned to. .. py:function:: @SIP_MODULE_FQ_NAME@.assign(obj, other) This does the Python equivalent of invoking the assignment operator of a C++ instance (i.e. ``*obj = other``). :param obj: the Python object being assigned to. :param other: the Python object being assigned. .. py:function:: @SIP_MODULE_FQ_NAME@.cast(obj, type) This does the Python equivalent of casting a C++ instance to one of its sub or super-class types. :param obj: the Python object. :param type type: the type. :return: a new Python object is that wraps the same C++ instance as *obj*, but has the type *type*. .. py:function:: @SIP_MODULE_FQ_NAME@.delete(obj) For C++ instances this calls the C++ destructor. For C structures it returns the structure's memory to the heap. :param obj: the Python object. .. py:function:: @SIP_MODULE_FQ_NAME@.dump(obj) This displays various bits of useful information about the internal state of the Python object that wraps a C++ instance or C structure. Note that the reference count that is displayed has the same caveat as that of :py:func:`sys.getrefcount`. :param obj: the Python object. .. py:function:: @SIP_MODULE_FQ_NAME@.enableautoconversion(type, enable) Instances of some classes may be automatically converted to other Python objects even though the class has been wrapped. This allows that behaviour to be suppressed so that an instances of the wrapped class is returned instead. By default it is enabled. :param type type: the Python type object. :param bool enable: is ``True`` if auto-conversion should be enabled for the type. :return: ``True`` or ``False`` depending on whether or not auto-conversion was previously enabled for the type. This allows the previous state to be restored later on. .. py:function:: @SIP_MODULE_FQ_NAME@.isdeleted(obj) This checks if the C++ instance or C structure has been deleted and returned to the heap. :param obj: the Python object. :return: ``True`` if the C/C++ instance has been deleted. .. py:function:: @SIP_MODULE_FQ_NAME@.ispycreated(obj) This checks if the C++ instance or C structure was created by Python. If it was then it is possible to call a C++ instance's protected methods. :param obj: the Python object. :return: ``True`` if the C/C++ instance was created by Python. .. py:function:: @SIP_MODULE_FQ_NAME@.ispyowned(obj) This checks if the C++ instance or C structure is owned by Python. :param obj: the Python object. :return: ``True`` if the C/C++ instance is owned by Python. .. py:function:: @SIP_MODULE_FQ_NAME@.setdeleted(obj) This marks the C++ instance or C structure as having been deleted and returned to the heap so that future references to it raise an exception rather than cause a program crash. Normally SIP handles such things automatically, but there may be circumstances where this isn't possible. :param obj: the Python object. .. py:function:: @SIP_MODULE_FQ_NAME@.settracemask(mask) If the bindings have been created with tracing enabled then the generated code will include debugging statements that trace the execution of the code. (It is particularly useful when trying to understand the operation of a C++ library's virtual function calls.) :param int mask: the mask that determines which debugging statements are enabled. Debugging statements are generated at the following points: - in a C++ virtual function (*mask* is ``0x0001``) - in a C++ constructor (*mask* is ``0x0002``) - in a C++ destructor (*mask* is ``0x0004``) - in a Python type's __init__ method (*mask* is ``0x0008``) - in a Python type's __del__ method (*mask* is ``0x0010``) - in a Python type's ordinary method (*mask* is ``0x0020``). By default the trace mask is zero and all debugging statements are disabled. .. py:class:: @SIP_MODULE_FQ_NAME@.simplewrapper This is an alternative type object than can be used as the base type of an instance wrapped by SIP. Objects using this are smaller than those that use the default :py:class:`~@SIP_MODULE_FQ_NAME@.wrapper` type but do not support the concept of object ownership. .. py:method:: __dtor__ If the wrapped instance is a C++ class with a virtual destructor then this is called by the destructor. .. py:data:: @SIP_MODULE_FQ_NAME@.SIP_VERSION This is a Python integer object that represents the SIP version number as a 3 part hexadecimal number (e.g. v5.0.0 is represented as ``0x050000``). Note that it is not the version number of the :py:mod:`~@SIP_MODULE_FQ_NAME@` module. .. py:data:: @SIP_MODULE_FQ_NAME@.SIP_VERSION_STR This is a Python string object that defines the SIP version number as represented as a string. For development versions it will contain ``.dev``. Note that it is not the version number of the :py:mod:`~@SIP_MODULE_FQ_NAME@` module. .. py:function:: @SIP_MODULE_FQ_NAME@.transferback(obj) This transfers ownership of a C++ instance or C structure to Python. :param obj: the Python object. .. py:function:: @SIP_MODULE_FQ_NAME@.transferto(obj, owner) This transfers ownership of a C++ instance or C structure to C/C++. :param obj: the Python object. :param owner: an optional wrapped instance that *obj* becomes associated with with regard to the cyclic garbage collector. If *owner* is ``None`` then no such association is made. If *owner* is the same value as *obj* then any reference cycles involving *obj* can never be detected or broken by the cyclic garbage collector. Responsibility for destroying the C++ instance’s destructor or freeing the C structure is always transfered to C/C++. .. py:function:: @SIP_MODULE_FQ_NAME@.unwrapinstance(obj) This returns the address, as an integer, of a wrapped C/C++ structure or class instance. :param obj: the Python object. :return: an integer that is the address of the C/C++ instance. .. py:class:: @SIP_MODULE_FQ_NAME@.voidptr This is the type object for the type SIP uses to represent a C/C++ ``void *``. It may have a size associated with the address in which case the Python buffer interface is supported. The type has the following methods. .. py:method:: __init__(address, size=-1, writeable=True) :param address: the address, either another :py:class:`~@SIP_MODULE_FQ_NAME@.voidptr`, ``None``, a Python Capsule, an object that implements the buffer protocol or an integer. :param int size: the optional associated size of the block of memory and is negative if the size is not known. :param bool writeable: set if the memory is writeable. If it is not specified, and *address* is a :py:class:`~@SIP_MODULE_FQ_NAME@.voidptr` instance then its value will be used. .. py:method:: __getitem__(idx) This returns the item at a given index. An exception will be raised if the address does not have an associated size. In this way it behaves like a Python ``memoryview`` object. :param idx: is the index which may either be an integer, an object that implements ``__index__()`` or a slice object. :return: the item. If the index is an integer then the item will be a bytes object containing the single byte at that index. If the index is a slice object then the item will be a new :py:class:`~@SIP_MODULE_FQ_NAME@.voidptr` object defining the subset of the memory corresponding to the slice. .. py:method:: __int__() This returns the address as an integer. :return: the integer address. .. py:method:: __len__() This returns the size associated with the address. :return: the associated size. An exception will be raised if there is none. .. py:method:: __setitem__(idx, item) This updates the memory at a given index. An exception will be raised if the address does not have an associated size or is not writable. In this way it behaves like a Python ``memoryview`` object. :param idx: is the index which may either be an integer, an object that implements ``__index__()`` or a slice object. :param item: is the data that will update the memory defined by the index. It must implement the buffer interface and be the same size as the data that is being updated. .. py:method:: asarray(size=-1) This returns the block of memory as an :py:class:`~@SIP_MODULE_FQ_NAME@.array` object. The memory is **not** copied. :param int size: the size of the array. If it is negative then the size associated with the address is used. If there is no associated size then an exception is raised. :return: the :py:class:`~@SIP_MODULE_FQ_NAME@.array` object. .. py:method:: ascapsule() This returns the address as an unnamed Python Capsule. :return: the Capsule. .. py:method:: asstring(size=-1) This returns a copy of the block of memory as a bytes object. :param int size: the number of bytes to copy. If it is negative then the size associated with the address is used. If there is no associated size then an exception is raised. :return: the bytes object. .. py:method:: getsize() This returns the size associated with the address. :return: the associated size which will be negative if there is none. .. py:method:: setsize(size) This sets the size associated with the address. :param int size: the size to associate. If it is negative then no size is associated. .. py:method:: getwriteable() This returns the writeable state of the memory. :return: ``True`` if the memory is writeable. .. py:method:: setwriteable(writeable) This sets the writeable state of the memory. :param bool writeable: the writeable state to set. .. py:function:: @SIP_MODULE_FQ_NAME@.wrapinstance(addr, type) This wraps a C structure or C++ class instance in a Python object. If the instance has already been wrapped then a new reference to the existing object is returned. :param int addr: the address of the instance as a number. :param type type: the Python type of the instance. :return: the Python object that wraps the instance. .. py:class:: @SIP_MODULE_FQ_NAME@.wrapper This is the type object of the default base type of all instances wrapped by SIP. .. py:class:: @SIP_MODULE_FQ_NAME@.wrappertype This is the type object of the metatype of the :py:class:`~@SIP_MODULE_FQ_NAME@.wrapper` type. sip-6.8.6/sipbuild/module/source/13/sip_array.c000066400000000000000000000507151464421045000213160ustar00rootroot00000000000000/* SPDX-License-Identifier: BSD-2-Clause */ /* * This file implements the API for the array type. * * Copyright (c) 2024 Phil Thompson */ /* Remove when Python v3.12 is no longer supported. */ #define PY_SSIZE_T_CLEAN #include #include #include #include "sip_core.h" #include "sip_array.h" /* The object data structure. */ typedef struct { PyObject_HEAD void *data; const sipTypeDef *td; const char *format; size_t stride; Py_ssize_t len; int flags; PyObject *owner; } sipArrayObject; static void bad_key(PyObject *key); static int check_index(sipArrayObject *array, Py_ssize_t idx); static int check_writable(sipArrayObject *array); static PyObject *create_array(void *data, const sipTypeDef *td, const char *format, size_t stride, Py_ssize_t len, int flags, PyObject *owner); static void *element(sipArrayObject *array, Py_ssize_t idx); static void *get_slice(sipArrayObject *array, PyObject *value, Py_ssize_t len); static const char *get_type_name(sipArrayObject *array); static void *get_value(sipArrayObject *array, PyObject *value); static void init_array(sipArrayObject *array, void *data, const sipTypeDef *td, const char *format, size_t stride, Py_ssize_t len, int flags, PyObject *owner); /* * Implement len() for the type. */ static Py_ssize_t sipArray_length(PyObject *self) { return ((sipArrayObject *)self)->len; } /* * Implement sequence item sub-script for the type. */ static PyObject *sipArray_item(PyObject *self, Py_ssize_t idx) { sipArrayObject *array = (sipArrayObject *)self; PyObject *py_item; void *data; if (check_index(array, idx) < 0) return NULL; data = element(array, idx); if (array->td != NULL) { py_item = sip_api_convert_from_type(data, array->td, NULL); } else { switch (*array->format) { case 'b': py_item = PyLong_FromLong(*(char *)data); break; case 'B': py_item = PyLong_FromUnsignedLong(*(unsigned char *)data); break; case 'h': py_item = PyLong_FromLong(*(short *)data); break; case 'H': py_item = PyLong_FromUnsignedLong(*(unsigned short *)data); break; case 'i': py_item = PyLong_FromLong(*(int *)data); break; case 'I': py_item = PyLong_FromUnsignedLong(*(unsigned int *)data); break; case 'f': py_item = PyFloat_FromDouble(*(float *)data); break; case 'd': py_item = PyFloat_FromDouble(*(double *)data); break; default: py_item = NULL; } } return py_item; } /* The sequence methods data structure. */ static PySequenceMethods sipArray_SequenceMethods = { sipArray_length, /* sq_length */ 0, /* sq_concat */ 0, /* sq_repeat */ sipArray_item, /* sq_item */ 0, /* sq_slice */ 0, /* sq_ass_item */ 0, /* sq_ass_slice */ 0, /* sq_contains */ 0, /* sq_inplace_concat */ 0, /* sq_inplace_repeat */ }; /* * Implement mapping sub-script for the type. */ static PyObject *sipArray_subscript(PyObject *self, PyObject *key) { sipArrayObject *array = (sipArrayObject *)self; if (PyIndex_Check(key)) { Py_ssize_t idx = PyNumber_AsSsize_t(key, PyExc_IndexError); if (idx == -1 && PyErr_Occurred()) return NULL; if (idx < 0) idx += array->len; return sipArray_item(self, idx); } if (PySlice_Check(key)) { Py_ssize_t start, stop, step, slicelength; if (sip_api_convert_from_slice_object(key, array->len, &start, &stop, &step, &slicelength) < 0) return NULL; if (step != 1) { PyErr_SetNone(PyExc_NotImplementedError); return NULL; } return create_array(element(array, start), array->td, array->format, array->stride, slicelength, (array->flags & ~SIP_OWNS_MEMORY), array->owner); } bad_key(key); return NULL; } /* * Implement mapping assignment sub-script for the type. */ static int sipArray_ass_subscript(PyObject *self, PyObject *key, PyObject *value) { sipArrayObject *array = (sipArrayObject *)self; Py_ssize_t start, len; void *value_data; if (check_writable(array) < 0) return -1; if (PyIndex_Check(key)) { start = PyNumber_AsSsize_t(key, PyExc_IndexError); if (start == -1 && PyErr_Occurred()) return -1; if (start < 0) start += array->len; if (check_index(array, start) < 0) return -1; if ((value_data = get_value(array, value)) == NULL) return -1; len = 1; } else if (PySlice_Check(key)) { Py_ssize_t stop, step; if (sip_api_convert_from_slice_object(key, array->len, &start, &stop, &step, &len) < 0) return -1; if (step != 1) { PyErr_SetNone(PyExc_NotImplementedError); return -1; } if ((value_data = get_slice(array, value, len)) == NULL) return -1; } else { bad_key(key); return -1; } if (array->td != NULL) { const sipClassTypeDef *ctd = (const sipClassTypeDef *)(array->td); sipAssignFunc assign; Py_ssize_t i; if ((assign = ctd->ctd_assign) == NULL) { PyErr_Format(PyExc_TypeError, "a " _SIP_MODULE_FQ_NAME ".array cannot copy '%s'", Py_TYPE(self)->tp_name); return -1; } for (i = 0; i < len; ++i) { assign(array->data, start + i, value_data); value_data = (char *)value_data + array->stride; } } else { memmove(element(array, start), value_data, len * array->stride); } return 0; } /* The mapping methods data structure. */ static PyMappingMethods sipArray_MappingMethods = { sipArray_length, /* mp_length */ sipArray_subscript, /* mp_subscript */ sipArray_ass_subscript, /* mp_ass_subscript */ }; /* * The buffer implementation. */ static int sipArray_getbuffer(PyObject *self, Py_buffer *view, int flags) { sipArrayObject *array = (sipArrayObject *)self; const char *format; Py_ssize_t itemsize; if (view == NULL) return 0; if (((flags & PyBUF_WRITABLE) == PyBUF_WRITABLE) && (array->flags & SIP_READ_ONLY)) { PyErr_SetString(PyExc_BufferError, "object is not writable"); return -1; } view->obj = self; Py_INCREF(self); /* * If there is no format, ie. it is a wrapped type, then present it as * bytes. */ if ((format = array->format) == NULL) { format = "B"; itemsize = sizeof (unsigned char); } else { itemsize = array->stride; } view->buf = array->data; view->len = array->len * array->stride; view->readonly = (array->flags & SIP_READ_ONLY); view->itemsize = itemsize; view->format = NULL; if ((flags & PyBUF_FORMAT) == PyBUF_FORMAT) /* Note that the need for a cast is probably a Python bug. */ view->format = (char *)format; view->ndim = 1; view->shape = NULL; if ((flags & PyBUF_ND) == PyBUF_ND) view->shape = &view->len; view->strides = NULL; if ((flags & PyBUF_STRIDES) == PyBUF_STRIDES) view->strides = &view->itemsize; view->suboffsets = NULL; view->internal = NULL; return 0; } /* The buffer methods data structure. */ static PyBufferProcs sipArray_BufferProcs = { sipArray_getbuffer, /* bf_getbuffer */ 0 /* bf_releasebuffer */ }; /* * The instance deallocation function. */ static void sipArray_dealloc(PyObject *self) { sipArrayObject *array = (sipArrayObject *)self; if (array->flags & SIP_OWNS_MEMORY) { if (array->td != NULL) ((const sipClassTypeDef *)(array->td))->ctd_array_delete(array->data); else PyMem_Free(array->data); } else { Py_XDECREF(array->owner); } } /* * Implement __repr__ for the type. */ static PyObject *sipArray_repr(PyObject *self) { sipArrayObject *array = (sipArrayObject *)self; return PyUnicode_FromFormat(_SIP_MODULE_FQ_NAME ".array(%s, %zd)", get_type_name(array), array->len); } /* * Implement __new__ for the type. */ static PyObject *sipArray_new(PyTypeObject *cls, PyObject *args, PyObject *kw) { #if PY_VERSION_HEX >= 0x030d0000 static char * const kwlist[] = {"", "", NULL}; #else static char *kwlist[] = {"", "", NULL}; #endif Py_ssize_t length; PyObject *array, *type; const sipClassTypeDef *ctd; if (!PyArg_ParseTupleAndKeywords(args, kw, "O!n:array", kwlist, &sipWrapperType_Type, &type, &length)) return NULL; ctd = (const sipClassTypeDef *)((sipWrapperType *)type)->wt_td; /* We require the array delete helper which was added in ABI v13.4. */ if (ctd->ctd_base.td_module->em_api_minor < 4) { PyErr_SetString(PyExc_TypeError, "a " _SIP_MODULE_FQ_NAME ".array can only be created for types using ABI v13.4 or later"); return NULL; } if (ctd->ctd_array == NULL || ctd->ctd_sizeof == 0) { PyErr_Format(PyExc_TypeError, "a " _SIP_MODULE_FQ_NAME ".array cannot be created for '%s'", Py_TYPE(type)->tp_name); return NULL; } if (length < 0) { PyErr_SetString(PyExc_ValueError, "a " _SIP_MODULE_FQ_NAME ".array length cannot be negative"); return NULL; } /* Create the instance. */ if ((array = cls->tp_alloc(cls, 0)) == NULL) return NULL; init_array((sipArrayObject *)array, ctd->ctd_array(length), &ctd->ctd_base, NULL, ctd->ctd_sizeof, length, SIP_OWNS_MEMORY, NULL); return array; } /* The type data structure. */ PyTypeObject sipArray_Type = { PyVarObject_HEAD_INIT(NULL, 0) _SIP_MODULE_FQ_NAME ".array", /* tp_name */ sizeof (sipArrayObject), /* tp_basicsize */ 0, /* tp_itemsize */ sipArray_dealloc, /* tp_dealloc */ 0, /* tp_print */ 0, /* tp_getattr */ 0, /* tp_setattr */ 0, /* tp_reserved */ sipArray_repr, /* tp_repr */ 0, /* tp_as_number */ &sipArray_SequenceMethods, /* tp_as_sequence */ &sipArray_MappingMethods, /* tp_as_mapping */ 0, /* tp_hash */ 0, /* tp_call */ 0, /* tp_str */ 0, /* tp_getattro */ 0, /* tp_setattro */ &sipArray_BufferProcs, /* tp_as_buffer */ #if defined(Py_TPFLAGS_HAVE_NEWBUFFER) Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_NEWBUFFER, /* tp_flags */ #else Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /* tp_flags */ #endif 0, /* tp_doc */ 0, /* tp_traverse */ 0, /* tp_clear */ 0, /* tp_richcompare */ 0, /* tp_weaklistoffset */ 0, /* tp_iter */ 0, /* tp_iternext */ 0, /* tp_methods */ 0, /* tp_members */ 0, /* tp_getset */ 0, /* tp_base */ 0, /* tp_dict */ 0, /* tp_descr_get */ 0, /* tp_descr_set */ 0, /* tp_dictoffset */ 0, /* tp_init */ 0, /* tp_alloc */ sipArray_new, /* tp_new */ 0, /* tp_free */ 0, /* tp_is_gc */ 0, /* tp_bases */ 0, /* tp_mro */ 0, /* tp_cache */ 0, /* tp_subclasses */ 0, /* tp_weaklist */ 0, /* tp_del */ 0, /* tp_version_tag */ 0, /* tp_finalize */ 0, /* tp_vectorcall */ }; /* * Return TRUE if an object is a sip.array with elements of a given type. */ int sip_array_can_convert(PyObject *obj, const sipTypeDef *td) { if (!PyObject_TypeCheck(obj, &sipArray_Type)) return FALSE; return (((sipArrayObject *)obj)->td == td); } /* * Return the address and number of elements of a sip.array for which * sip_array_can_convert has already returned TRUE. */ void sip_array_convert(PyObject *obj, void **data, Py_ssize_t *size) { sipArrayObject *array = (sipArrayObject *)obj; *data = array->data; *size = array->len; } /* * Check that an array is writable. */ static int check_writable(sipArrayObject *array) { if (array->flags & SIP_READ_ONLY) { PyErr_SetString(PyExc_TypeError, _SIP_MODULE_FQ_NAME ".array object is read-only"); return -1; } return 0; } /* * Check that an index is valid for an array. */ static int check_index(sipArrayObject *array, Py_ssize_t idx) { if (idx >= 0 && idx < array->len) return 0; PyErr_SetString(PyExc_IndexError, "index out of bounds"); return -1; } /* * Raise an exception about a bad sub-script key. */ static void bad_key(PyObject *key) { PyErr_Format(PyExc_TypeError, "cannot index a " _SIP_MODULE_FQ_NAME ".array object using '%s'", Py_TYPE(key)->tp_name); } /* * Get the address of an element of an array. */ static void *element(sipArrayObject *array, Py_ssize_t idx) { return (unsigned char *)(array->data) + idx * array->stride; } /* * Get the address of a value that will be copied to an array. */ static void *get_value(sipArrayObject *array, PyObject *value) { static union { signed char s_char_t; unsigned char u_char_t; signed short s_short_t; unsigned short u_short_t; signed int s_int_t; unsigned int u_int_t; float float_t; double double_t; } static_data; void *data; if (array->td != NULL) { int iserr = FALSE; data = sip_api_force_convert_to_type_us(value, array->td, NULL, SIP_NOT_NONE|SIP_NO_CONVERTORS, NULL, NULL, &iserr); } else { PyErr_Clear(); switch (*array->format) { case 'b': static_data.s_char_t = sip_api_long_as_char(value); data = &static_data.s_char_t; break; case 'B': static_data.u_char_t = sip_api_long_as_unsigned_char(value); data = &static_data.u_char_t; break; case 'h': static_data.s_short_t = sip_api_long_as_short(value); data = &static_data.s_short_t; break; case 'H': static_data.u_short_t = sip_api_long_as_unsigned_short(value); data = &static_data.u_short_t; break; case 'i': static_data.s_int_t = sip_api_long_as_int(value); data = &static_data.s_int_t; break; case 'I': static_data.u_int_t = sip_api_long_as_unsigned_int(value); data = &static_data.u_int_t; break; case 'f': static_data.float_t = (float)PyFloat_AsDouble(value); data = &static_data.float_t; break; case 'd': static_data.double_t = PyFloat_AsDouble(value); data = &static_data.double_t; break; default: data = NULL; } if (PyErr_Occurred()) data = NULL; } return data; } /* * Get the address of an value that will be copied to an array slice. */ static void *get_slice(sipArrayObject *array, PyObject *value, Py_ssize_t len) { sipArrayObject *other = (sipArrayObject *)value; if (!PyObject_IsInstance(value, (PyObject *)&sipArray_Type) || array->td != other->td || strcmp(array->format, other->format) != 0) { PyErr_Format(PyExc_TypeError, "can only assign another array of %s to the slice", get_type_name(array)); return NULL; } if (other->len != len) { PyErr_Format(PyExc_TypeError, "the array being assigned must have length %zd", len); return NULL; } if (other->stride == array->stride) { PyErr_Format(PyExc_TypeError, "the array being assigned must have stride %zu", array->stride); return NULL; } return other->data; } /* * Get the name of the type of an element of an array. */ static const char *get_type_name(sipArrayObject *array) { const char *type_name; if (array->td != NULL) { type_name = sipTypeName(array->td); } else { switch (*array->format) { case 'b': type_name = "char"; break; case 'B': type_name = "unsigned char"; break; case 'h': type_name = "short"; break; case 'H': type_name = "unsigned short"; break; case 'i': type_name = "int"; break; case 'I': type_name = "unsigned int"; break; case 'f': type_name = "float"; break; case 'd': type_name = "double"; break; default: type_name = ""; } } return type_name; } /* * Create an array for the C API. */ static PyObject *create_array(void *data, const sipTypeDef *td, const char *format, size_t stride, Py_ssize_t len, int flags, PyObject *owner) { sipArrayObject *array; if ((array = PyObject_NEW(sipArrayObject, &sipArray_Type)) == NULL) return NULL; init_array(array, data, td, format, stride, len, flags, owner); return (PyObject *)array; } /* * Initialise an array. */ static void init_array(sipArrayObject *array, void *data, const sipTypeDef *td, const char *format, size_t stride, Py_ssize_t len, int flags, PyObject *owner) { array->data = data; array->td = td; array->format = format; array->stride = stride; array->len = len; array->flags = flags; if (flags & SIP_OWNS_MEMORY) { /* This is a borrowed reference to itself. */ array->owner = (PyObject *)array; } else { Py_XINCREF(owner); array->owner = owner; } } /* * Wrap an array of instances of a fundamental type. At the moment format must * be either "b" (char), "B" (unsigned char), "h" (short), "H" (unsigned * short), "i" (int), "I" (unsigned int), "f" (float) or "d" (double). */ PyObject *sip_api_convert_to_array(void *data, const char *format, Py_ssize_t len, int flags) { size_t stride; assert(len >= 0); if (data == NULL) { Py_INCREF(Py_None); return Py_None; } switch (*format) { case 'b': stride = sizeof (char); break; case 'B': stride = sizeof (unsigned char); break; case 'h': stride = sizeof (short); break; case 'H': stride = sizeof (unsigned short); break; case 'i': stride = sizeof (int); break; case 'I': stride = sizeof (unsigned int); break; case 'f': stride = sizeof (float); break; case 'd': stride = sizeof (double); break; default: PyErr_Format(PyExc_ValueError, "'%c' is not a supported format", format); return NULL; } return create_array(data, NULL, format, stride, len, flags, NULL); } /* * Wrap an array of instances of a defined type. */ PyObject *sip_api_convert_to_typed_array(void *data, const sipTypeDef *td, const char *format, size_t stride, Py_ssize_t len, int flags) { if (data == NULL) { Py_INCREF(Py_None); return Py_None; } assert(stride > 0); assert(len >= 0); return create_array(data, td, format, stride, len, flags, NULL); } sip-6.8.6/sipbuild/module/source/13/sip_array.h000066400000000000000000000015031464421045000213120ustar00rootroot00000000000000/* SPDX-License-Identifier: BSD-2-Clause */ /* * This file defines the API for the array type. * * Copyright (c) 2024 Phil Thompson */ #ifndef _SIP_ARRAY_H #define _SIP_ARRAY_H /* Remove when Python v3.12 is no longer supported. */ #define PY_SSIZE_T_CLEAN #include #include "sip.h" #ifdef __cplusplus extern "C" { #endif extern PyTypeObject sipArray_Type; PyObject *sip_api_convert_to_array(void *data, const char *format, Py_ssize_t len, int flags); PyObject *sip_api_convert_to_typed_array(void *data, const sipTypeDef *td, const char *format, size_t stride, Py_ssize_t len, int flags); int sip_array_can_convert(PyObject *obj, const sipTypeDef *td); void sip_array_convert(PyObject *obj, void **data, Py_ssize_t *size); #ifdef __cplusplus } #endif #endif sip-6.8.6/sipbuild/module/source/13/sip_core.c000066400000000000000000011434511464421045000211310ustar00rootroot00000000000000/* SPDX-License-Identifier: BSD-2-Clause */ /* * The core sip module code. * * Copyright (c) 2024 Phil Thompson */ /* Remove when Python v3.12 is no longer supported. */ #define PY_SSIZE_T_CLEAN #include #include #include #include #include #include #include #include "sip.h" #include "sip_array.h" #include "sip_enum.h" #include "sip_core.h" /* * The Python metatype for a C++ wrapper type. We inherit everything from the * standard Python metatype except the init and getattro methods and the size * of the type object created is increased to accomodate the extra information * we associate with a wrapped type. */ static PyObject *sipWrapperType_alloc(PyTypeObject *self, Py_ssize_t nitems); static PyObject *sipWrapperType_getattro(PyObject *self, PyObject *name); static int sipWrapperType_init(sipWrapperType *self, PyObject *args, PyObject *kwds); static int sipWrapperType_setattro(PyObject *self, PyObject *name, PyObject *value); PyTypeObject sipWrapperType_Type = { PyVarObject_HEAD_INIT(NULL, 0) _SIP_MODULE_FQ_NAME ".wrappertype", /* tp_name */ sizeof (sipWrapperType), /* tp_basicsize */ 0, /* tp_itemsize */ 0, /* tp_dealloc */ 0, /* tp_print */ 0, /* tp_getattr */ 0, /* tp_setattr */ 0, /* tp_as_async */ 0, /* tp_repr */ 0, /* tp_as_number */ 0, /* tp_as_sequence */ 0, /* tp_as_mapping */ 0, /* tp_hash */ 0, /* tp_call */ 0, /* tp_str */ sipWrapperType_getattro, /* tp_getattro */ sipWrapperType_setattro, /* tp_setattro */ 0, /* tp_as_buffer */ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /* tp_flags */ 0, /* tp_doc */ 0, /* tp_traverse */ 0, /* tp_clear */ 0, /* tp_richcompare */ 0, /* tp_weaklistoffset */ 0, /* tp_iter */ 0, /* tp_iternext */ 0, /* tp_methods */ 0, /* tp_members */ 0, /* tp_getset */ 0, /* tp_base */ 0, /* tp_dict */ 0, /* tp_descr_get */ 0, /* tp_descr_set */ 0, /* tp_dictoffset */ (initproc)sipWrapperType_init, /* tp_init */ sipWrapperType_alloc, /* tp_alloc */ 0, /* tp_new */ 0, /* tp_free */ 0, /* tp_is_gc */ 0, /* tp_bases */ 0, /* tp_mro */ 0, /* tp_cache */ 0, /* tp_subclasses */ 0, /* tp_weaklist */ 0, /* tp_del */ 0, /* tp_version_tag */ 0, /* tp_finalize */ 0, /* tp_vectorcall */ }; /* * The Python type that is the super-type for all C++ wrapper types that * support parent/child relationships. */ static int sipWrapper_clear(sipWrapper *self); static void sipWrapper_dealloc(sipWrapper *self); static int sipWrapper_traverse(sipWrapper *self, visitproc visit, void *arg); static sipWrapperType sipWrapper_Type = { #if !defined(STACKLESS) { #endif { PyVarObject_HEAD_INIT(&sipWrapperType_Type, 0) _SIP_MODULE_FQ_NAME ".wrapper", /* tp_name */ sizeof (sipWrapper), /* tp_basicsize */ 0, /* tp_itemsize */ (destructor)sipWrapper_dealloc, /* tp_dealloc */ 0, /* tp_print */ 0, /* tp_getattr */ 0, /* tp_setattr */ 0, /* tp_as_async */ 0, /* tp_repr */ 0, /* tp_as_number */ 0, /* tp_as_sequence */ 0, /* tp_as_mapping */ 0, /* tp_hash */ 0, /* tp_call */ 0, /* tp_str */ 0, /* tp_getattro */ 0, /* tp_setattro */ 0, /* tp_as_buffer */ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_GC, /* tp_flags */ 0, /* tp_doc */ (traverseproc)sipWrapper_traverse, /* tp_traverse */ (inquiry)sipWrapper_clear, /* tp_clear */ 0, /* tp_richcompare */ 0, /* tp_weaklistoffset */ 0, /* tp_iter */ 0, /* tp_iternext */ 0, /* tp_methods */ 0, /* tp_members */ 0, /* tp_getset */ 0, /* tp_base */ 0, /* tp_dict */ 0, /* tp_descr_get */ 0, /* tp_descr_set */ 0, /* tp_dictoffset */ 0, /* tp_init */ 0, /* tp_alloc */ 0, /* tp_new */ 0, /* tp_free */ 0, /* tp_is_gc */ 0, /* tp_bases */ 0, /* tp_mro */ 0, /* tp_cache */ 0, /* tp_subclasses */ 0, /* tp_weaklist */ 0, /* tp_del */ 0, /* tp_version_tag */ 0, /* tp_finalize */ 0, /* tp_vectorcall */ }, { 0, /* am_await */ 0, /* am_aiter */ 0, /* am_anext */ #if PY_VERSION_HEX >= 0x030a0000 0, /* am_send */ #endif }, { 0, /* nb_add */ 0, /* nb_subtract */ 0, /* nb_multiply */ 0, /* nb_remainder */ 0, /* nb_divmod */ 0, /* nb_power */ 0, /* nb_negative */ 0, /* nb_positive */ 0, /* nb_absolute */ 0, /* nb_bool */ 0, /* nb_invert */ 0, /* nb_lshift */ 0, /* nb_rshift */ 0, /* nb_and */ 0, /* nb_xor */ 0, /* nb_or */ 0, /* nb_int */ 0, /* nb_reserved */ 0, /* nb_float */ 0, /* nb_inplace_add */ 0, /* nb_inplace_subtract */ 0, /* nb_inplace_multiply */ 0, /* nb_inplace_remainder */ 0, /* nb_inplace_power */ 0, /* nb_inplace_lshift */ 0, /* nb_inplace_rshift */ 0, /* nb_inplace_and */ 0, /* nb_inplace_xor */ 0, /* nb_inplace_or */ 0, /* nb_floor_divide */ 0, /* nb_true_divide */ 0, /* nb_inplace_floor_divide */ 0, /* nb_inplace_true_divide */ 0, /* nb_index */ 0, /* nb_matrix_multiply */ 0, /* nb_inplace_matrix_multiply */ }, { 0, /* mp_length */ 0, /* mp_subscript */ 0, /* mp_ass_subscript */ }, { 0, /* sq_length */ 0, /* sq_concat */ 0, /* sq_repeat */ 0, /* sq_item */ 0, /* was_sq_slice */ 0, /* sq_ass_item */ 0, /* was_sq_ass_slice */ 0, /* sq_contains */ 0, /* sq_inplace_concat */ 0, /* sq_inplace_repeat */ }, { 0, /* bf_getbuffer */ 0, /* bf_releasebuffer */ }, 0, /* ht_name */ 0, /* ht_slots */ 0, /* ht_qualname */ 0, /* ht_cached_keys */ #if PY_VERSION_HEX >= 0x03090000 0, /* ht_module */ #endif #if !defined(STACKLESS) }, #endif 0, /* wt_user_type */ 0, /* wt_dict_complete */ 0, /* wt_unused */ 0, /* wt_td */ 0, /* wt_iextend */ 0, /* wt_user_data */ 0, /* wt_reserved */ }; static void sip_api_bad_catcher_result(PyObject *method); static void sip_api_bad_length_for_slice(Py_ssize_t seqlen, Py_ssize_t slicelen); static PyObject *sip_api_build_result(int *isErr, const char *fmt, ...); static PyObject *sip_api_call_method(int *isErr, PyObject *method, const char *fmt, ...); static void sip_api_call_procedure_method(sip_gilstate_t gil_state, sipVirtErrorHandlerFunc error_handler, sipSimpleWrapper *py_self, PyObject *method, const char *fmt, ...); static Py_ssize_t sip_api_convert_from_sequence_index(Py_ssize_t idx, Py_ssize_t len); static int sip_api_can_convert_to_type(PyObject *pyObj, const sipTypeDef *td, int flags); static void *sip_api_convert_to_type(PyObject *pyObj, const sipTypeDef *td, PyObject *transferObj, int flags, int *statep, int *iserrp); static void *sip_api_convert_to_type_us(PyObject *pyObj, const sipTypeDef *td, PyObject *transferObj, int flags, int *statep, void **user_statep, int *iserrp); static void *sip_api_force_convert_to_type(PyObject *pyObj, const sipTypeDef *td, PyObject *transferObj, int flags, int *statep, int *iserrp); static void sip_api_release_type(void *cpp, const sipTypeDef *td, int state); static void sip_api_release_type_us(void *cpp, const sipTypeDef *td, int state, void *user_state); static PyObject *sip_api_convert_from_new_type(void *cpp, const sipTypeDef *td, PyObject *transferObj); static PyObject *sip_api_convert_from_new_pytype(void *cpp, PyTypeObject *py_type, sipWrapper *owner, sipSimpleWrapper **selfp, const char *fmt, ...); static int sip_api_get_state(PyObject *transferObj); static PyObject *sip_api_get_pyobject(void *cppPtr, const sipTypeDef *td); static int sip_api_parse_result_ex(sip_gilstate_t gil_state, sipVirtErrorHandlerFunc error_handler, sipSimpleWrapper *py_self, PyObject *method, PyObject *res, const char *fmt, ...); static int sip_api_parse_result(int *isErr, PyObject *method, PyObject *res, const char *fmt, ...); static void sip_api_call_error_handler(sipVirtErrorHandlerFunc error_handler, sipSimpleWrapper *py_self, sip_gilstate_t gil_state); static void sip_api_trace(unsigned mask,const char *fmt,...); static void sip_api_transfer_back(PyObject *self); static void sip_api_transfer_to(PyObject *self, PyObject *owner); static int sip_api_export_module(sipExportedModuleDef *client, unsigned api_major, unsigned api_minor, void *unused); static int sip_api_init_module(sipExportedModuleDef *client, PyObject *mod_dict); static int sip_api_parse_args(PyObject **parseErrp, PyObject *sipArgs, const char *fmt, ...); static int sip_api_parse_kwd_args(PyObject **parseErrp, PyObject *sipArgs, PyObject *sipKwdArgs, const char **kwdlist, PyObject **unused, const char *fmt, ...); static int sip_api_parse_pair(PyObject **parseErrp, PyObject *sipArg0, PyObject *sipArg1, const char *fmt, ...); static void sip_api_no_function(PyObject *parseErr, const char *func, const char *doc); static void sip_api_no_method(PyObject *parseErr, const char *scope, const char *method, const char *doc); static void sip_api_abstract_method(const char *classname, const char *method); static void sip_api_bad_class(const char *classname); static void *sip_api_get_complex_cpp_ptr(sipSimpleWrapper *sw); static PyObject *sip_api_is_py_method(sip_gilstate_t *gil, char *pymc, sipSimpleWrapper *sipSelf, const char *cname, const char *mname); static PyObject *sip_api_is_py_method_12_8(sip_gilstate_t *gil, char *pymc, sipSimpleWrapper **sipSelfp, const char *cname, const char *mname); static void sip_api_call_hook(const char *hookname); static void sip_api_raise_unknown_exception(void); static void sip_api_raise_type_exception(const sipTypeDef *td, void *ptr); static int sip_api_add_type_instance(PyObject *dict, const char *name, void *cppPtr, const sipTypeDef *td); static sipErrorState sip_api_bad_callable_arg(int arg_nr, PyObject *arg); static void sip_api_bad_operator_arg(PyObject *self, PyObject *arg, sipPySlotType st); static PyObject *sip_api_pyslot_extend(sipExportedModuleDef *mod, sipPySlotType st, const sipTypeDef *td, PyObject *arg0, PyObject *arg1); static void sip_api_add_delayed_dtor(sipSimpleWrapper *w); static int sip_api_export_symbol(const char *name, void *sym); static void *sip_api_import_symbol(const char *name); static const sipTypeDef *sip_api_find_type(const char *type); static char sip_api_bytes_as_char(PyObject *obj); static const char *sip_api_bytes_as_string(PyObject *obj); static char sip_api_string_as_ascii_char(PyObject *obj); static const char *sip_api_string_as_ascii_string(PyObject **obj); static char sip_api_string_as_latin1_char(PyObject *obj); static const char *sip_api_string_as_latin1_string(PyObject **obj); static char sip_api_string_as_utf8_char(PyObject *obj); static const char *sip_api_string_as_utf8_string(PyObject **obj); #if defined(HAVE_WCHAR_H) static wchar_t sip_api_unicode_as_wchar(PyObject *obj); static wchar_t *sip_api_unicode_as_wstring(PyObject *obj); #else static int sip_api_unicode_as_wchar(PyObject *obj); static int *sip_api_unicode_as_wstring(PyObject *obj); #endif static int sip_api_register_py_type(PyTypeObject *supertype); static const sipTypeDef *sip_api_type_from_py_type_object(PyTypeObject *py_type); static const char *sip_api_resolve_typedef(const char *name); static int sip_api_register_attribute_getter(const sipTypeDef *td, sipAttrGetterFunc getter); static void sip_api_keep_reference(PyObject *self, int key, PyObject *obj); static PyObject *sip_api_get_reference(PyObject *self, int key); static int sip_api_is_owned_by_python(sipSimpleWrapper *sw); static int sip_api_is_derived_class(sipSimpleWrapper *sw); static void sip_api_add_exception(sipErrorState es, PyObject **parseErrp); static int sip_api_enable_autoconversion(const sipTypeDef *td, int enable); static int sip_api_init_mixin(PyObject *self, PyObject *args, PyObject *kwds, const sipClassTypeDef *ctd); static void *sip_api_get_mixin_address(sipSimpleWrapper *w, const sipTypeDef *td); static int sip_api_register_proxy_resolver(const sipTypeDef *td, sipProxyResolverFunc resolver); static PyInterpreterState *sip_api_get_interpreter(void); static void sip_api_set_type_user_data(sipWrapperType *wt, void *data); static void *sip_api_get_type_user_data(const sipWrapperType *wt); static PyObject *sip_api_py_type_dict(const PyTypeObject *py_type); static PyObject *sip_api_py_type_dict_ref(PyTypeObject *py_type); static const char *sip_api_py_type_name(const PyTypeObject *py_type); static int sip_api_get_method(PyObject *obj, sipMethodDef *method); static PyObject *sip_api_from_method(const sipMethodDef *method); static int sip_api_get_c_function(PyObject *obj, sipCFunctionDef *c_function); static int sip_api_get_date(PyObject *obj, sipDateDef *date); static PyObject *sip_api_from_date(const sipDateDef *date); static int sip_api_get_datetime(PyObject *obj, sipDateDef *date, sipTimeDef *time); static PyObject *sip_api_from_datetime(const sipDateDef *date, const sipTimeDef *time); static int sip_api_get_time(PyObject *obj, sipTimeDef *time); static PyObject *sip_api_from_time(const sipTimeDef *time); static int sip_api_is_user_type(const sipWrapperType *wt); static int sip_api_check_plugin_for_type(const sipTypeDef *td, const char *name); static PyObject *sip_api_unicode_new(Py_ssize_t len, unsigned maxchar, int *kind, void **data); static void sip_api_unicode_write(int kind, void *data, int index, unsigned value); static void *sip_api_unicode_data(PyObject *obj, int *char_size, Py_ssize_t *len); static int sip_api_get_buffer_info(PyObject *obj, sipBufferInfoDef *bi); static void sip_api_release_buffer_info(sipBufferInfoDef *bi); static PyObject *sip_api_get_user_object(const sipSimpleWrapper *sw); static void sip_api_set_user_object(sipSimpleWrapper *sw, PyObject *user); static int sip_api_enable_gc(int enable); static void sip_api_print_object(PyObject *o); static int sip_api_register_event_handler(sipEventType type, const sipTypeDef *td, void *handler); static void sip_api_instance_destroyed_ex(sipSimpleWrapper **sipSelfp); static void sip_api_visit_wrappers(sipWrapperVisitorFunc visitor, void *closure); static int sip_api_register_exit_notifier(PyMethodDef *md); static sipExceptionHandler sip_api_next_exception_handler(void **statep); /* * The data structure that represents the SIP API. */ static const sipAPIDef sip_api = { /* This must be first. */ sip_api_export_module, /* * The following are part of the public API. */ (PyTypeObject *)&sipSimpleWrapper_Type, (PyTypeObject *)&sipWrapper_Type, &sipWrapperType_Type, &sipVoidPtr_Type, sip_api_bad_catcher_result, sip_api_bad_length_for_slice, sip_api_build_result, sip_api_call_method, sip_api_call_procedure_method, sip_api_convert_from_sequence_index, sip_api_can_convert_to_type, sip_api_convert_to_type, sip_api_convert_to_type_us, sip_api_force_convert_to_type, sip_api_force_convert_to_type_us, sip_api_release_type, sip_api_release_type_us, sip_api_convert_from_type, sip_api_convert_from_new_type, sip_api_convert_from_enum, sip_api_get_state, sip_api_free, sip_api_get_pyobject, sip_api_malloc, sip_api_parse_result, sip_api_trace, sip_api_transfer_back, sip_api_transfer_to, sip_api_long_as_unsigned_long, sip_api_convert_from_void_ptr, sip_api_convert_from_const_void_ptr, sip_api_convert_from_void_ptr_and_size, sip_api_convert_from_const_void_ptr_and_size, sip_api_convert_to_void_ptr, sip_api_export_symbol, sip_api_import_symbol, sip_api_find_type, sip_api_register_py_type, sip_api_type_from_py_type_object, sip_api_type_scope, sip_api_resolve_typedef, sip_api_register_attribute_getter, sip_api_bad_callable_arg, sip_api_get_address, sip_api_enable_autoconversion, sip_api_get_mixin_address, sip_api_convert_from_new_pytype, sip_api_convert_to_typed_array, sip_api_convert_to_array, sip_api_register_proxy_resolver, sip_api_get_interpreter, sip_api_set_type_user_data, sip_api_get_type_user_data, sip_api_py_type_dict, sip_api_py_type_name, sip_api_get_method, sip_api_from_method, sip_api_get_c_function, sip_api_get_date, sip_api_from_date, sip_api_get_datetime, sip_api_from_datetime, sip_api_get_time, sip_api_from_time, sip_api_is_user_type, sip_api_check_plugin_for_type, sip_api_unicode_new, sip_api_unicode_write, sip_api_unicode_data, sip_api_get_buffer_info, sip_api_release_buffer_info, sip_api_get_user_object, sip_api_set_user_object, sip_api_instance_destroyed, sip_api_is_owned_by_python, sip_api_enable_gc, sip_api_print_object, sip_api_register_event_handler, sip_api_convert_to_enum, sip_api_convert_to_bool, sip_api_long_as_char, sip_api_long_as_signed_char, sip_api_long_as_unsigned_char, sip_api_long_as_short, sip_api_long_as_unsigned_short, sip_api_long_as_int, sip_api_long_as_unsigned_int, sip_api_long_as_long, sip_api_long_as_long_long, sip_api_long_as_unsigned_long_long, sip_api_convert_from_slice_object, sip_api_long_as_size_t, sip_api_visit_wrappers, sip_api_register_exit_notifier, sip_api_is_enum_flag, sip_api_py_type_dict_ref, NULL, NULL, NULL, NULL, /* * The following are not part of the public API. */ sip_api_init_module, sip_api_parse_args, sip_api_parse_pair, sip_api_no_function, sip_api_no_method, sip_api_abstract_method, sip_api_bad_class, sip_api_get_cpp_ptr, sip_api_get_complex_cpp_ptr, sip_api_is_py_method, sip_api_call_hook, sip_api_end_thread, sip_api_raise_unknown_exception, sip_api_raise_type_exception, sip_api_add_type_instance, sip_api_bad_operator_arg, sip_api_pyslot_extend, sip_api_add_delayed_dtor, sip_api_bytes_as_char, sip_api_bytes_as_string, sip_api_string_as_ascii_char, sip_api_string_as_ascii_string, sip_api_string_as_latin1_char, sip_api_string_as_latin1_string, sip_api_string_as_utf8_char, sip_api_string_as_utf8_string, sip_api_unicode_as_wchar, sip_api_unicode_as_wstring, sip_api_deprecated, sip_api_keep_reference, sip_api_parse_kwd_args, sip_api_add_exception, sip_api_parse_result_ex, sip_api_call_error_handler, sip_api_init_mixin, sip_api_get_reference, sip_api_is_derived_class, sip_api_instance_destroyed_ex, sip_api_is_py_method_12_8, sip_api_next_exception_handler, NULL, NULL, NULL, NULL, }; #define AUTO_DOCSTRING '\1' /* Marks an auto class docstring. */ /* * These are the format flags supported by argument parsers. */ #define FMT_AP_DEREF 0x01 /* The pointer will be dereferenced. */ #define FMT_AP_TRANSFER 0x02 /* Implement /Transfer/. */ #define FMT_AP_TRANSFER_BACK 0x04 /* Implement /TransferBack/. */ #define FMT_AP_NO_CONVERTORS 0x08 /* Suppress any convertors. */ #define FMT_AP_TRANSFER_THIS 0x10 /* Support for /TransferThis/. */ /* * These are the format flags supported by result parsers. Deprecated values * have a _DEPR suffix. */ #define FMT_RP_DEREF 0x01 /* The pointer will be dereferenced. */ #define FMT_RP_FACTORY 0x02 /* /Factory/ or /TransferBack/. */ #define FMT_RP_MAKE_COPY 0x04 /* Return a copy of the value. */ #define FMT_RP_NO_STATE_DEPR 0x04 /* Don't return the C/C++ state. */ /* * The different reasons for failing to parse an overload. These include * internal (i.e. non-user) errors. */ typedef enum { Ok, Unbound, TooFew, TooMany, UnknownKeyword, Duplicate, WrongType, Raised, KeywordNotString, Exception, Overflow } sipParseFailureReason; /* * The description of a failure to parse an overload because of a user error. */ typedef struct _sipParseFailure { sipParseFailureReason reason; /* The reason for the failure. */ const char *detail_str; /* The detail if a string. */ PyObject *detail_obj; /* The detail if a Python object. */ int arg_nr; /* The wrong positional argument. */ const char *arg_name; /* The wrong keyword argument. */ int overflow_arg_nr; /* The overflowed positional argument. */ const char *overflow_arg_name; /* The overflowed keyword argument. */ } sipParseFailure; /* * An entry in a linked list of name/symbol pairs. */ typedef struct _sipSymbol { const char *name; /* The name. */ void *symbol; /* The symbol. */ struct _sipSymbol *next; /* The next in the list. */ } sipSymbol; /* * An entry in a linked list of Python objects. */ typedef struct _sipPyObject { PyObject *object; /* The Python object. */ struct _sipPyObject *next; /* The next in the list. */ } sipPyObject; /* * An entry in the linked list of attribute getters. */ typedef struct _sipAttrGetter { PyTypeObject *type; /* The Python type being handled. */ sipAttrGetterFunc getter; /* The getter. */ struct _sipAttrGetter *next; /* The next in the list. */ } sipAttrGetter; /* * An entry in the linked list of proxy resolvers. */ typedef struct _sipProxyResolver { const sipTypeDef *td; /* The type the resolver handles. */ sipProxyResolverFunc resolver; /* The resolver. */ struct _sipProxyResolver *next; /* The next in the list. */ } sipProxyResolver; /* * An entry in the linked list of event handlers. */ typedef struct _sipEventHandler { const sipClassTypeDef *ctd; /* The type the handler handles. */ void *handler; /* The handler. */ struct _sipEventHandler *next; /* The next in the list. */ } sipEventHandler; /* * Various strings as Python objects created as and when needed. */ static PyObject *licenseName = NULL; static PyObject *licenseeName = NULL; static PyObject *typeName = NULL; static PyObject *timestampName = NULL; static PyObject *signatureName = NULL; static sipObjectMap cppPyMap; /* The C/C++ to Python map. */ static sipExportedModuleDef *moduleList = NULL; /* List of registered modules. */ static unsigned traceMask = 0; /* The current trace mask. */ static sipTypeDef *currentType = NULL; /* The type being created. */ static PyObject **unused_backdoor = NULL; /* For passing dict of unused arguments. */ static PyObject *init_name = NULL; /* '__init__'. */ static PyObject *empty_tuple; /* The empty tuple. */ static PyObject *type_unpickler; /* The type unpickler function. */ static sipSymbol *sipSymbolList = NULL; /* The list of published symbols. */ static sipAttrGetter *sipAttrGetters = NULL; /* The list of attribute getters. */ static sipProxyResolver *proxyResolvers = NULL; /* The list of proxy resolvers. */ static sipPyObject *sipRegisteredPyTypes = NULL; /* Registered Python types. */ static sipPyObject *sipDisabledAutoconversions = NULL; /* Python types whose auto-conversion is disabled. */ static PyInterpreterState *sipInterpreter = NULL; /* The interpreter. */ static sipEventHandler *event_handlers[sipEventNrEvents]; /* The event handler lists. */ static void addClassSlots(sipWrapperType *wt, const sipClassTypeDef *ctd); static void *findSlot(PyObject *self, sipPySlotType st); static void *findSlotInClass(const sipClassTypeDef *psd, sipPySlotType st); static void *findSlotInSlotList(sipPySlotDef *psd, sipPySlotType st); static int objobjargprocSlot(PyObject *self, PyObject *arg1, PyObject *arg2, sipPySlotType st); static int ssizeobjargprocSlot(PyObject *self, Py_ssize_t arg1, PyObject *arg2, sipPySlotType st); static PyObject *buildObject(PyObject *tup, const char *fmt, va_list va); static int parseKwdArgs(PyObject **parseErrp, PyObject *sipArgs, PyObject *sipKwdArgs, const char **kwdlist, PyObject **unused, const char *fmt, va_list va_orig); static int parsePass1(PyObject **parseErrp, PyObject **selfp, int *selfargp, PyObject *sipArgs, PyObject *sipKwdArgs, const char **kwdlist, PyObject **unused, const char *fmt, va_list va); static int parsePass2(PyObject *self, int selfarg, PyObject *sipArgs, PyObject *sipKwdArgs, const char **kwdlist, const char *fmt, va_list va); static int parseResult(PyObject *method, PyObject *res, sipSimpleWrapper *py_self, const char *fmt, va_list va); static PyObject *signature_FromDocstring(const char *doc, Py_ssize_t line); static PyObject *detail_FromFailure(PyObject *failure_obj); static int canConvertFromSequence(PyObject *seq, const sipTypeDef *td); static int convertFromSequence(PyObject *seq, const sipTypeDef *td, void **array, Py_ssize_t *nr_elem); static PyObject *convertToSequence(void *array, Py_ssize_t nr_elem, const sipTypeDef *td); static int getSelfFromArgs(sipTypeDef *td, PyObject *args, int argnr, PyObject **selfp); static int compareTypedefName(const void *key, const void *el); static int checkPointer(void *ptr, sipSimpleWrapper *sw); static void *cast_cpp_ptr(void *ptr, PyTypeObject *src_type, const sipTypeDef *dst_type); static void finalise(void); static PyObject *getDefaultBase(void); static PyObject *getDefaultSimpleBase(void); static PyObject *getScopeDict(sipTypeDef *td, PyObject *mod_dict, sipExportedModuleDef *client); static PyObject *createContainerType(sipContainerDef *cod, sipTypeDef *td, PyObject *bases, PyObject *metatype, PyObject *mod_dict, PyObject *type_dict, sipExportedModuleDef *client); static int createClassType(sipExportedModuleDef *client, sipClassTypeDef *ctd, PyObject *mod_dict); static int createMappedType(sipExportedModuleDef *client, sipMappedTypeDef *mtd, PyObject *mod_dict); static sipExportedModuleDef *getModule(PyObject *mname_obj); static PyObject *pickle_type(PyObject *obj, PyObject *args); static PyObject *unpickle_type(PyObject *obj, PyObject *args); static int setReduce(PyTypeObject *type, PyMethodDef *pickler); static PyObject *createTypeDict(sipExportedModuleDef *em); static sipTypeDef *getGeneratedType(const sipEncodedTypeDef *enc, sipExportedModuleDef *em); static const sipTypeDef *convertSubClass(const sipTypeDef *td, void **cppPtr); static int convertPass(const sipTypeDef **tdp, void **cppPtr); static void *getPtrTypeDef(sipSimpleWrapper *self, const sipClassTypeDef **ctd); static int addInstances(PyObject *dict, sipInstancesDef *id); static int addVoidPtrInstances(PyObject *dict, sipVoidPtrInstanceDef *vi); static int addCharInstances(PyObject *dict, sipCharInstanceDef *ci); static int addStringInstances(PyObject *dict, sipStringInstanceDef *si); static int addIntInstances(PyObject *dict, sipIntInstanceDef *ii); static int addLongInstances(PyObject *dict, sipLongInstanceDef *li); static int addUnsignedLongInstances(PyObject *dict, sipUnsignedLongInstanceDef *uli); static int addLongLongInstances(PyObject *dict, sipLongLongInstanceDef *lli); static int addUnsignedLongLongInstances(PyObject *dict, sipUnsignedLongLongInstanceDef *ulli); static int addDoubleInstances(PyObject *dict, sipDoubleInstanceDef *di); static int addTypeInstances(PyObject *dict, sipTypeInstanceDef *ti); static int addSingleTypeInstance(PyObject *dict, const char *name, void *cppPtr, const sipTypeDef *td, int initflags); static int addLicense(PyObject *dict, sipLicenseDef *lc); static PyObject *assign(PyObject *self, PyObject *args); static PyObject *cast(PyObject *self, PyObject *args); static PyObject *callDtor(PyObject *self, PyObject *args); static PyObject *dumpWrapper(PyObject *self, PyObject *arg); static PyObject *enableAutoconversion(PyObject *self, PyObject *args); static PyObject *isDeleted(PyObject *self, PyObject *args); static PyObject *isPyCreated(PyObject *self, PyObject *args); static PyObject *isPyOwned(PyObject *self, PyObject *args); static PyObject *setDeleted(PyObject *self, PyObject *args); static PyObject *setTraceMask(PyObject *self, PyObject *args); static PyObject *wrapInstance(PyObject *self, PyObject *args); static PyObject *unwrapInstance(PyObject *self, PyObject *args); static PyObject *transferBack(PyObject *self, PyObject *args); static PyObject *transferTo(PyObject *self, PyObject *args); static void clear_wrapper(sipSimpleWrapper *sw); static void print_object(const char *label, PyObject *obj); static void addToParent(sipWrapper *self, sipWrapper *owner); static void removeFromParent(sipWrapper *self); static void detachChildren(sipWrapper *self); static void release(void *addr, const sipTypeDef *td, int state, void *user_state); static void callPyDtor(sipSimpleWrapper *self); static int parseBytes_AsCharArray(PyObject *obj, const char **ap, Py_ssize_t *aszp); static int parseBytes_AsChar(PyObject *obj, char *ap); static int parseBytes_AsString(PyObject *obj, const char **ap); static int parseString_AsASCIIChar(PyObject *obj, char *ap); static PyObject *parseString_AsASCIIString(PyObject *obj, const char **ap); static int parseString_AsLatin1Char(PyObject *obj, char *ap); static PyObject *parseString_AsLatin1String(PyObject *obj, const char **ap); static int parseString_AsUTF8Char(PyObject *obj, char *ap); static PyObject *parseString_AsUTF8String(PyObject *obj, const char **ap); static int parseString_AsEncodedChar(PyObject *bytes, PyObject *obj, char *ap); static PyObject *parseString_AsEncodedString(PyObject *bytes, PyObject *obj, const char **ap); #if defined(HAVE_WCHAR_H) static int parseWCharArray(PyObject *obj, wchar_t **ap, Py_ssize_t *aszp); static int convertToWCharArray(PyObject *obj, wchar_t **ap, Py_ssize_t *aszp); static int parseWChar(PyObject *obj, wchar_t *ap); static int convertToWChar(PyObject *obj, wchar_t *ap); static int parseWCharString(PyObject *obj, wchar_t **ap); static int convertToWCharString(PyObject *obj, wchar_t **ap); #else static void raiseNoWChar(); #endif static void *getComplexCppPtr(sipSimpleWrapper *w, const sipTypeDef *td); static PyObject *findPyType(const char *name); static int addPyObjectToList(sipPyObject **head, PyObject *object); static PyObject *getDictFromObject(PyObject *obj); static void forgetObject(sipSimpleWrapper *sw); static int add_lazy_container_attrs(const sipTypeDef *td, sipContainerDef *cod, PyObject *dict); static int add_lazy_attrs(const sipTypeDef *td); static void add_failure(PyObject **parseErrp, sipParseFailure *failure); static PyObject *bad_type_str(int arg_nr, PyObject *arg); static void *explicit_access_func(sipSimpleWrapper *sw, AccessFuncOp op); static void *indirect_access_func(sipSimpleWrapper *sw, AccessFuncOp op); static void clear_access_func(sipSimpleWrapper *sw); static int check_encoded_string(PyObject *obj); static int isNonlazyMethod(PyMethodDef *pmd); static int addMethod(PyObject *dict, PyMethodDef *pmd); static PyObject *create_property(sipVariableDef *vd); static PyObject *create_function(PyMethodDef *ml); static PyObject *sip_exit(PyObject *self, PyObject *args); static sipConvertFromFunc get_from_convertor(const sipTypeDef *td); static sipPyObject **autoconversion_disabled(const sipTypeDef *td); static void fix_slots(PyTypeObject *py_type, sipPySlotDef *psd); static sipFinalFunc find_finalisation(sipClassTypeDef *ctd); static PyObject *next_in_mro(PyObject *self, PyObject *after); static int super_init(PyObject *self, PyObject *args, PyObject *kwds, PyObject *type); static sipSimpleWrapper *deref_mixin(sipSimpleWrapper *w); static PyObject *wrap_simple_instance(void *cpp, const sipTypeDef *td, sipWrapper *owner, int flags); static void *resolve_proxy(const sipTypeDef *td, void *proxy); static PyObject *call_method(PyObject *method, const char *fmt, va_list va); static int importTypes(sipExportedModuleDef *client, sipImportedModuleDef *im, sipExportedModuleDef *em); static int importErrorHandlers(sipExportedModuleDef *client, sipImportedModuleDef *im, sipExportedModuleDef *em); static int importExceptions(sipExportedModuleDef *client, sipImportedModuleDef *im, sipExportedModuleDef *em); static int is_subtype(const sipClassTypeDef *ctd, const sipClassTypeDef *base_ctd); static PyObject *import_module_attr(const char *module, const char *attr); static const sipContainerDef *get_container(const sipTypeDef *td); static void handle_failed_int_conversion(sipParseFailure *pf, PyObject *arg); static void handle_failed_type_conversion(sipParseFailure *pf, PyObject *arg); static void raise_no_convert_from(const sipTypeDef *td); static void raise_no_convert_to(PyObject *py, const sipTypeDef *td); static int user_state_is_valid(const sipTypeDef *td, void **user_statep); /* * Initialise the module as a library. */ const sipAPIDef *sip_init_library(PyObject *mod_dict) { static PyMethodDef methods[] = { /* The type unpickler must be first. */ {"_unpickle_type", unpickle_type, METH_VARARGS, NULL}, {"assign", assign, METH_VARARGS, NULL}, {"cast", cast, METH_VARARGS, NULL}, {"delete", callDtor, METH_VARARGS, NULL}, {"dump", dumpWrapper, METH_O, NULL}, {"enableautoconversion", enableAutoconversion, METH_VARARGS, NULL}, {"isdeleted", isDeleted, METH_VARARGS, NULL}, {"ispycreated", isPyCreated, METH_VARARGS, NULL}, {"ispyowned", isPyOwned, METH_VARARGS, NULL}, {"setdeleted", setDeleted, METH_VARARGS, NULL}, {"settracemask", setTraceMask, METH_VARARGS, NULL}, {"transferback", transferBack, METH_VARARGS, NULL}, {"transferto", transferTo, METH_VARARGS, NULL}, {"wrapinstance", wrapInstance, METH_VARARGS, NULL}, {"unwrapinstance", unwrapInstance, METH_VARARGS, NULL}, {NULL, NULL, 0, NULL} }; static PyMethodDef sip_exit_md = { "_sip_exit", sip_exit, METH_NOARGS, NULL }; PyObject *obj; PyMethodDef *md; if (sip_enum_init() < 0) return NULL; /* Add the SIP version number. */ obj = PyLong_FromLong(SIP_VERSION); if (sip_dict_set_and_discard(mod_dict, "SIP_VERSION", obj) < 0) return NULL; obj = PyUnicode_FromString(SIP_VERSION_STR); if (sip_dict_set_and_discard(mod_dict, "SIP_VERSION_STR", obj) < 0) return NULL; /* Add the methods. */ for (md = methods; md->ml_name != NULL; ++md) { PyObject *meth = PyCFunction_New(md, NULL); if (sip_dict_set_and_discard(mod_dict, md->ml_name, meth) < 0) return NULL; if (md == &methods[0]) { Py_INCREF(meth); type_unpickler = meth; } } /* Initialise the types. */ sipWrapperType_Type.tp_base = &PyType_Type; if (PyType_Ready(&sipWrapperType_Type) < 0) return NULL; if (PyType_Ready((PyTypeObject *)&sipSimpleWrapper_Type) < 0) return NULL; if (sip_api_register_py_type((PyTypeObject *)&sipSimpleWrapper_Type) < 0) return NULL; #if defined(STACKLESS) sipWrapper_Type.super.tp_base = (PyTypeObject *)&sipSimpleWrapper_Type; #else sipWrapper_Type.super.ht_type.tp_base = (PyTypeObject *)&sipSimpleWrapper_Type; #endif if (PyType_Ready((PyTypeObject *)&sipWrapper_Type) < 0) return NULL; if (PyType_Ready(&sipMethodDescr_Type) < 0) return NULL; if (PyType_Ready(&sipVariableDescr_Type) < 0) return NULL; if (PyType_Ready(&sipVoidPtr_Type) < 0) return NULL; if (PyType_Ready(&sipArray_Type) < 0) return NULL; /* Add the public types. */ if (PyDict_SetItemString(mod_dict, "wrappertype", (PyObject *)&sipWrapperType_Type) < 0) return NULL; if (PyDict_SetItemString(mod_dict, "simplewrapper", (PyObject *)&sipSimpleWrapper_Type) < 0) return NULL; if (PyDict_SetItemString(mod_dict, "wrapper", (PyObject *)&sipWrapper_Type) < 0) return NULL; if (PyDict_SetItemString(mod_dict, "voidptr", (PyObject *)&sipVoidPtr_Type) < 0) return NULL; if (PyDict_SetItemString(mod_dict, "array", (PyObject *)&sipArray_Type) < 0) return NULL; /* These will always be needed. */ if (sip_objectify("__init__", &init_name) < 0) return NULL; if ((empty_tuple = PyTuple_New(0)) == NULL) return NULL; /* Initialise the object map. */ sipOMInit(&cppPyMap); /* Make sure we are notified at the end of the exit process. */ if (Py_AtExit(finalise) < 0) return NULL; /* Make sure we are notified at the start of the exit process. */ if (sip_api_register_exit_notifier(&sip_exit_md) < 0) return NULL; /* * Get the current interpreter. This will be shared between all threads. */ sipInterpreter = PyThreadState_Get()->interp; return &sip_api; } /* * Set a dictionary item and discard the reference to the item even if there * was an error. */ int sip_dict_set_and_discard(PyObject *dict, const char *name, PyObject *obj) { int rc; if (obj == NULL) return -1; rc = PyDict_SetItemString(dict, name, obj); Py_DECREF(obj); return rc; } #if _SIP_MODULE_SHARED /* * The Python module initialisation function. */ #if defined(SIP_STATIC_MODULE) PyObject *_SIP_MODULE_ENTRY(void) #else PyMODINIT_FUNC _SIP_MODULE_ENTRY(void) #endif { static PyModuleDef module_def = { PyModuleDef_HEAD_INIT, _SIP_MODULE_FQ_NAME, /* m_name */ NULL, /* m_doc */ -1, /* m_size */ NULL, /* m_methods */ NULL, /* m_reload */ NULL, /* m_traverse */ NULL, /* m_clear */ NULL, /* m_free */ }; const sipAPIDef *api; PyObject *mod, *mod_dict, *api_obj; /* Create the module. */ if ((mod = PyModule_Create(&module_def)) == NULL) return NULL; mod_dict = PyModule_GetDict(mod); /* Initialise the module dictionary and static variables. */ if ((api = sip_init_library(mod_dict)) == NULL) return NULL; /* Publish the SIP API. */ api_obj = PyCapsule_New((void *)api, _SIP_MODULE_FQ_NAME "._C_API", NULL); if (sip_dict_set_and_discard(mod_dict, "_C_API", api_obj) < 0) { Py_DECREF(mod); return NULL; } return mod; } #endif /* * Return the current interpreter, if there is one. */ static PyInterpreterState *sip_api_get_interpreter(void) { return sipInterpreter; } /* * Display a printf() style message to stderr according to the current trace * mask. */ static void sip_api_trace(unsigned mask, const char *fmt, ...) { va_list ap; va_start(ap,fmt); if (mask & traceMask) vfprintf(stderr, fmt, ap); va_end(ap); } /* * Set the trace mask. */ static PyObject *setTraceMask(PyObject *self, PyObject *args) { unsigned new_mask; (void)self; if (PyArg_ParseTuple(args, "I:settracemask", &new_mask)) { traceMask = new_mask; Py_INCREF(Py_None); return Py_None; } return NULL; } /* * Dump various bits of potentially useful information to stdout. Note that we * use the same calling convention as sys.getrefcount() so that it has the * same caveat regarding the reference count. */ static PyObject *dumpWrapper(PyObject *self, PyObject *arg) { sipSimpleWrapper *sw; (void)self; if (!PyObject_TypeCheck(arg, (PyTypeObject *)&sipSimpleWrapper_Type)) { PyErr_Format(PyExc_TypeError, "dump() argument 1 must be " _SIP_MODULE_FQ_NAME ".simplewrapper, not %s", Py_TYPE(arg)->tp_name); return NULL; } sw = (sipSimpleWrapper *)arg; print_object(NULL, (PyObject *)sw); printf(" Reference count: %" PY_FORMAT_SIZE_T "d\n", Py_REFCNT(sw)); printf(" Address of wrapped object: %p\n", sip_api_get_address(sw)); printf(" Created by: %s\n", (sipIsDerived(sw) ? "Python" : "C/C++")); printf(" To be destroyed by: %s\n", (sipIsPyOwned(sw) ? "Python" : "C/C++")); if (PyObject_TypeCheck((PyObject *)sw, (PyTypeObject *)&sipWrapper_Type)) { sipWrapper *w = (sipWrapper *)sw; print_object("Parent wrapper", (PyObject *)w->parent); print_object("Next sibling wrapper", (PyObject *)w->sibling_next); print_object("Previous sibling wrapper", (PyObject *)w->sibling_prev); print_object("First child wrapper", (PyObject *)w->first_child); } Py_INCREF(Py_None); return Py_None; } /* * Write a reference to a wrapper to stdout. */ static void print_object(const char *label, PyObject *obj) { if (label != NULL) printf(" %s: ", label); if (obj != NULL) PyObject_Print(obj, stdout, 0); else printf("NULL"); printf("\n"); } /* * Transfer the ownership of an instance to C/C++. */ static PyObject *transferTo(PyObject *self, PyObject *args) { PyObject *w, *owner; (void)self; if (PyArg_ParseTuple(args, "O!O:transferto", &sipWrapper_Type, &w, &owner)) { if (owner == Py_None) { /* * Note that the Python API is different to the C API when the * owner is None. */ owner = NULL; } else if (!PyObject_TypeCheck(owner, (PyTypeObject *)&sipWrapper_Type)) { PyErr_Format(PyExc_TypeError, "transferto() argument 2 must be " _SIP_MODULE_FQ_NAME ".wrapper, not %s", Py_TYPE(owner)->tp_name); return NULL; } sip_api_transfer_to(w, owner); Py_INCREF(Py_None); return Py_None; } return NULL; } /* * Transfer the ownership of an instance to Python. */ static PyObject *transferBack(PyObject *self, PyObject *args) { PyObject *w; (void)self; if (PyArg_ParseTuple(args, "O!:transferback", &sipWrapper_Type, &w)) { sip_api_transfer_back(w); Py_INCREF(Py_None); return Py_None; } return NULL; } /* * Invoke the assignment operator for a C++ instance. */ static PyObject *assign(PyObject *self, PyObject *args) { sipSimpleWrapper *dst, *src; PyTypeObject *dst_type, *src_type; const sipTypeDef *td, *super_td; sipAssignFunc assign_helper; void *dst_addr, *src_addr; (void)self; if (!PyArg_ParseTuple(args, "O!O!:assign", &sipSimpleWrapper_Type, &dst, &sipSimpleWrapper_Type, &src)) return NULL; /* Get the assignment helper. */ dst_type = Py_TYPE(dst); td = ((sipWrapperType *)dst_type)->wt_td; if (sipTypeIsMapped(td)) assign_helper = ((const sipMappedTypeDef *)td)->mtd_assign; else assign_helper = ((const sipClassTypeDef *)td)->ctd_assign; if (assign_helper == NULL) { PyErr_SetString(PyExc_TypeError, "argument 1 of assign() does not support assignment"); return NULL; } /* Check the types are compatible. */ src_type = Py_TYPE(src); if (src_type == dst_type) { super_td = NULL; } else if (PyType_IsSubtype(src_type, dst_type)) { super_td = td; } else { PyErr_SetString(PyExc_TypeError, "type of argument 1 of assign() must be a super-type of type of argument 2"); return NULL; } /* Get the addresses. */ if ((dst_addr = sip_api_get_cpp_ptr(dst, NULL)) == NULL) return NULL; if ((src_addr = sip_api_get_cpp_ptr(src, super_td)) == NULL) return NULL; /* Do the assignment. */ assign_helper(dst_addr, 0, src_addr); Py_INCREF(Py_None); return Py_None; } /* * Cast an instance to one of it's sub or super-classes by returning a new * Python object with the superclass type wrapping the same C++ instance. */ static PyObject *cast(PyObject *self, PyObject *args) { sipSimpleWrapper *sw; sipWrapperType *wt; const sipTypeDef *td; void *addr; PyTypeObject *ft, *tt; (void)self; if (!PyArg_ParseTuple(args, "O!O!:cast", &sipSimpleWrapper_Type, &sw, &sipWrapperType_Type, &wt)) return NULL; ft = Py_TYPE(sw); tt = (PyTypeObject *)wt; if (ft == tt || PyType_IsSubtype(tt, ft)) td = NULL; else if (PyType_IsSubtype(ft, tt)) td = wt->wt_td; else { PyErr_SetString(PyExc_TypeError, "argument 1 of cast() must be an instance of a sub or super-type of argument 2"); return NULL; } if ((addr = sip_api_get_cpp_ptr(sw, td)) == NULL) return NULL; /* * We don't put this new object into the map so that the original object is * always found. It would also totally confuse the map logic. */ return wrap_simple_instance(addr, wt->wt_td, NULL, (sw->sw_flags | SIP_NOT_IN_MAP) & ~SIP_PY_OWNED); } /* * Call an instance's dtor. */ static PyObject *callDtor(PyObject *self, PyObject *args) { sipSimpleWrapper *sw; void *addr; const sipClassTypeDef *ctd; (void)self; if (!PyArg_ParseTuple(args, "O!:delete", &sipSimpleWrapper_Type, &sw)) return NULL; addr = getPtrTypeDef(sw, &ctd); if (checkPointer(addr, sw) < 0) return NULL; clear_wrapper(sw); release(addr, (const sipTypeDef *)ctd, sw->sw_flags, NULL); Py_INCREF(Py_None); return Py_None; } /* * Check if an instance still exists without raising an exception. */ static PyObject *isDeleted(PyObject *self, PyObject *args) { sipSimpleWrapper *sw; PyObject *res; (void)self; if (!PyArg_ParseTuple(args, "O!:isdeleted", &sipSimpleWrapper_Type, &sw)) return NULL; res = (sip_api_get_address(sw) == NULL ? Py_True : Py_False); Py_INCREF(res); return res; } /* * Check if an instance was created by Python. */ static PyObject *isPyCreated(PyObject *self, PyObject *args) { sipSimpleWrapper *sw; PyObject *res; (void)self; if (!PyArg_ParseTuple(args, "O!:ispycreated", &sipSimpleWrapper_Type, &sw)) return NULL; /* sipIsDerived() is a misnomer. */ res = (sipIsDerived(sw) ? Py_True : Py_False); Py_INCREF(res); return res; } /* * Check if an instance is owned by Python or C/C++. */ static PyObject *isPyOwned(PyObject *self, PyObject *args) { sipSimpleWrapper *sw; PyObject *res; (void)self; if (!PyArg_ParseTuple(args, "O!:ispyowned", &sipSimpleWrapper_Type, &sw)) return NULL; res = (sipIsPyOwned(sw) ? Py_True : Py_False); Py_INCREF(res); return res; } /* * Mark an instance as having been deleted. */ static PyObject *setDeleted(PyObject *self, PyObject *args) { sipSimpleWrapper *sw; (void)self; if (!PyArg_ParseTuple(args, "O!:setdeleted", &sipSimpleWrapper_Type, &sw)) return NULL; clear_wrapper(sw); Py_INCREF(Py_None); return Py_None; } /* * Unwrap an instance. */ static PyObject *unwrapInstance(PyObject *self, PyObject *args) { sipSimpleWrapper *sw; (void)self; if (PyArg_ParseTuple(args, "O!:unwrapinstance", &sipSimpleWrapper_Type, &sw)) { void *addr; /* * We just get the pointer but don't try and cast it (which isn't * needed and wouldn't work with the way casts are currently * implemented if we are unwrapping something derived from a wrapped * class). */ if ((addr = sip_api_get_cpp_ptr(sw, NULL)) == NULL) return NULL; return PyLong_FromVoidPtr(addr); } return NULL; } /* * Wrap an instance. */ static PyObject *wrapInstance(PyObject *self, PyObject *args) { unsigned long long addr; sipWrapperType *wt; (void)self; if (PyArg_ParseTuple(args, "KO!:wrapinstance", &addr, &sipWrapperType_Type, &wt)) return sip_api_convert_from_type((void *)addr, wt->wt_td, NULL); return NULL; } /* * Register a client module. A negative value is returned and an exception * raised if there was an error. */ static int sip_api_export_module(sipExportedModuleDef *client, unsigned abi_major, unsigned abi_minor, void *unused) { sipExportedModuleDef *em; const char *full_name = sipNameOfModule(client); (void)unused; /* Check that we can support it. */ if (abi_major != SIP_ABI_MAJOR_VERSION || abi_minor > SIP_ABI_MINOR_VERSION) { #if SIP_ABI_MINOR_VERSION > 0 PyErr_Format(PyExc_RuntimeError, "the sip module implements ABI v%d.0 to v%d.%d but the %s module requires ABI v%d.%d", SIP_ABI_MAJOR_VERSION, SIP_ABI_MAJOR_VERSION, SIP_ABI_MINOR_VERSION, full_name, abi_major, abi_minor); #else PyErr_Format(PyExc_RuntimeError, "the sip module implements ABI v%d.0 but the %s module requires ABI v%d.%d", SIP_ABI_MAJOR_VERSION, full_name, abi_major, abi_minor); #endif return -1; } /* Import any required modules. */ if (client->em_imports != NULL) { sipImportedModuleDef *im = client->em_imports; while (im->im_name != NULL) { PyObject *mod; if ((mod = PyImport_ImportModule(im->im_name)) == NULL) return -1; for (em = moduleList; em != NULL; em = em->em_next) if (strcmp(sipNameOfModule(em), im->im_name) == 0) break; if (em == NULL) { PyErr_Format(PyExc_RuntimeError, "the %s module failed to register with the sip module", im->im_name); return -1; } if (im->im_imported_types != NULL && importTypes(client, im, em) < 0) return -1; if (im->im_imported_veh != NULL && importErrorHandlers(client, im, em) < 0) return -1; if (im->im_imported_exceptions != NULL && importExceptions(client, im, em) < 0) return -1; ++im; } } for (em = moduleList; em != NULL; em = em->em_next) { /* SIP clients must have unique names. */ if (strcmp(sipNameOfModule(em), full_name) == 0) { PyErr_Format(PyExc_RuntimeError, "the sip module has already registered a module called %s", full_name); return -1; } } /* Convert the module name to an object. */ if ((client->em_nameobj = PyUnicode_FromString(full_name)) == NULL) return -1; /* Add it to the list of client modules. */ client->em_next = moduleList; moduleList = client; return 0; } /* * Initialise the contents of a client module. By this time anything that * this depends on should have been initialised. A negative value is returned * and an exception raised if there was an error. */ static int sip_api_init_module(sipExportedModuleDef *client, PyObject *mod_dict) { sipExportedModuleDef *em; sipIntInstanceDef *next_int; int i; /* Create the module's types. */ next_int = client->em_instances.id_int; for (i = 0; i < client->em_nrtypes; ++i) { sipTypeDef *td = client->em_types[i]; /* Skip external classes. */ if (td == NULL) continue; /* Skip if already initialised. */ if (td->td_module != NULL) continue; /* If it is a stub then just set the module so we can get its name. */ if (sipTypeIsStub(td)) { td->td_module = client; continue; } if (sipTypeIsEnum(td)) { sipEnumTypeDef *etd = (sipEnumTypeDef *)td; /* * Set this now to get access to the string pool. Unlike other * types we don't use it to determine if the type has been * initialised. */ td->td_module = client; if (etd->etd_scope < 0 && sip_enum_create(client, etd, &next_int, mod_dict) < 0) return -1; } else if (sipTypeIsMapped(td)) { sipMappedTypeDef *mtd = (sipMappedTypeDef *)td; /* If there is a name then we need a namespace. */ if (mtd->mtd_container.cod_name >= 0) { if (createMappedType(client, mtd, mod_dict) < 0) return -1; } else { td->td_module = client; } } else { sipClassTypeDef *ctd = (sipClassTypeDef *)td; /* See if this is a namespace extender. */ if (ctd->ctd_container.cod_name < 0) { sipTypeDef *real_nspace; sipClassTypeDef **last; ctd->ctd_base.td_module = client; real_nspace = getGeneratedType(&ctd->ctd_container.cod_scope, client); /* Append this type to the real one. */ last = &((sipClassTypeDef *)real_nspace)->ctd_nsextender; while (*last != NULL) last = &(*last)->ctd_nsextender; *last = ctd; /* * Save the real namespace type so that it is the correct scope * for any enums or classes defined in this module. */ client->em_types[i] = real_nspace; } else if (createClassType(client, ctd, mod_dict) < 0) return -1; } } /* Add any ints that aren't name enum members. */ if (next_int != NULL) if (addIntInstances(mod_dict, next_int) < 0) return -1; /* Append any initialiser extenders to the relevant classes. */ if (client->em_initextend != NULL) { sipInitExtenderDef *ie = client->em_initextend; while (ie->ie_extender != NULL) { sipTypeDef *td = getGeneratedType(&ie->ie_class, client); sipWrapperType *wt = (sipWrapperType *)sipTypeAsPyTypeObject(td); ie->ie_next = wt->wt_iextend; wt->wt_iextend = ie; ++ie; } } /* Set the base class object for any sub-class convertors. */ if (client->em_convertors != NULL) { sipSubClassConvertorDef *scc = client->em_convertors; while (scc->scc_convertor != NULL) { scc->scc_basetype = getGeneratedType(&scc->scc_base, client); ++scc; } } /* Add any global static instances. */ if (addInstances(mod_dict, &client->em_instances) < 0) return -1; /* Add any license. */ if (client->em_license != NULL && addLicense(mod_dict, client->em_license) < 0) return -1; /* See if the new module satisfies any outstanding external types. */ for (em = moduleList; em != NULL; em = em->em_next) { sipExternalTypeDef *etd; if (em == client || em->em_external == NULL) continue; for (etd = em->em_external; etd->et_nr >= 0; ++etd) { if (etd->et_name == NULL) continue; for (i = 0; i < client->em_nrtypes; ++i) { sipTypeDef *td = client->em_types[i]; if (td != NULL && !sipTypeIsStub(td) && sipTypeIsClass(td)) { const char *pyname = sipPyNameOfContainer( &((sipClassTypeDef *)td)->ctd_container, td); if (strcmp(etd->et_name, pyname) == 0) { em->em_types[etd->et_nr] = td; etd->et_name = NULL; break; } } } } } return 0; } /* * Called by the interpreter to do any final clearing up, just in case the * interpreter will re-start. */ static void finalise(void) { sipExportedModuleDef *em; /* * Mark the Python API as unavailable. This should already have been done, * but just in case... */ sipInterpreter = NULL; /* Handle any delayed dtors. */ for (em = moduleList; em != NULL; em = em->em_next) if (em->em_ddlist != NULL) { em->em_delayeddtors(em->em_ddlist); /* Free the list. */ do { sipDelayedDtor *dd = em->em_ddlist; em->em_ddlist = dd->dd_next; sip_api_free(dd); } while (em->em_ddlist != NULL); } licenseName = NULL; licenseeName = NULL; typeName = NULL; timestampName = NULL; signatureName = NULL; /* Release all memory we've allocated directly. */ sipOMFinalise(&cppPyMap); /* Re-initialise those globals that (might) need it. */ moduleList = NULL; } /* * Register the given Python type. */ static int sip_api_register_py_type(PyTypeObject *type) { return addPyObjectToList(&sipRegisteredPyTypes, (PyObject *)type); } /* * Find the registered type with the given name. Raise an exception if it * couldn't be found. */ static PyObject *findPyType(const char *name) { sipPyObject *po; for (po = sipRegisteredPyTypes; po != NULL; po = po->next) { PyObject *type = po->object; if (strcmp(((PyTypeObject *)type)->tp_name, name) == 0) return type; } PyErr_Format(PyExc_RuntimeError, "%s is not a registered type", name); return NULL; } /* * Add a wrapped C/C++ pointer to the list of delayed dtors. */ static void sip_api_add_delayed_dtor(sipSimpleWrapper *sw) { void *ptr; const sipClassTypeDef *ctd; sipExportedModuleDef *em; if ((ptr = getPtrTypeDef(sw, &ctd)) == NULL) return; /* Find the defining module. */ for (em = moduleList; em != NULL; em = em->em_next) { int i; for (i = 0; i < em->em_nrtypes; ++i) if (em->em_types[i] == (const sipTypeDef *)ctd) { sipDelayedDtor *dd; if ((dd = sip_api_malloc(sizeof (sipDelayedDtor))) == NULL) return; /* Add to the list. */ dd->dd_ptr = ptr; dd->dd_name = sipPyNameOfContainer(&ctd->ctd_container, (sipTypeDef *)ctd); dd->dd_isderived = sipIsDerived(sw); dd->dd_next = em->em_ddlist; em->em_ddlist = dd; return; } } } /* * A wrapper around the Python memory allocater that will raise an exception if * if the allocation fails. */ void *sip_api_malloc(size_t nbytes) { void *mem; if ((mem = PyMem_RawMalloc(nbytes)) == NULL) PyErr_NoMemory(); return mem; } /* * A wrapper around the Python memory de-allocater. */ void sip_api_free(void *mem) { PyMem_RawFree(mem); } /* * Extend a Python slot by looking in other modules to see if there is an * extender function that can handle the arguments. */ static PyObject *sip_api_pyslot_extend(sipExportedModuleDef *mod, sipPySlotType st, const sipTypeDef *td, PyObject *arg0, PyObject *arg1) { sipExportedModuleDef *em; /* Go through each module. */ for (em = moduleList; em != NULL; em = em->em_next) { sipPySlotExtenderDef *ex; /* Skip the module that couldn't handle the arguments. */ if (em == mod) continue; /* Skip if the module doesn't have any extenders. */ if (em->em_slotextend == NULL) continue; /* Go through each extender. */ for (ex = em->em_slotextend; ex->pse_func != NULL; ++ex) { PyObject *res; /* Skip if not the right slot type. */ if (ex->pse_type != st) continue; /* Check against the type if one was given. */ if (td != NULL && td != getGeneratedType(&ex->pse_class, NULL)) continue; PyErr_Clear(); res = ((binaryfunc)ex->pse_func)(arg0, arg1); if (res != Py_NotImplemented) return res; } } /* The arguments couldn't handled anywhere. */ PyErr_Clear(); Py_INCREF(Py_NotImplemented); return Py_NotImplemented; } /* * Convert a new C/C++ instance to a Python instance of a specific Python type. */ static PyObject *sip_api_convert_from_new_pytype(void *cpp, PyTypeObject *py_type, sipWrapper *owner, sipSimpleWrapper **selfp, const char *fmt, ...) { PyObject *args, *res; va_list va; va_start(va, fmt); if ((args = PyTuple_New(strlen(fmt))) != NULL && buildObject(args, fmt, va) != NULL) { res = sipWrapInstance(cpp, py_type, args, owner, (selfp != NULL ? SIP_DERIVED_CLASS : 0)); /* Initialise the rest of an instance of a derived class. */ if (selfp != NULL) *selfp = (sipSimpleWrapper *)res; } else { res = NULL; } Py_XDECREF(args); va_end(va); return res; } /* * Call a method and return the result. */ static PyObject *call_method(PyObject *method, const char *fmt, va_list va) { PyObject *args, *res; if ((args = PyTuple_New(strlen(fmt))) == NULL) return NULL; if (buildObject(args, fmt, va) != NULL) res = PyObject_CallObject(method, args); else res = NULL; Py_DECREF(args); return res; } /* * Call the Python re-implementation of a C++ virtual that does not return a * value and handle the result.. */ static void sip_api_call_procedure_method(sip_gilstate_t gil_state, sipVirtErrorHandlerFunc error_handler, sipSimpleWrapper *py_self, PyObject *method, const char *fmt, ...) { PyObject *res; va_list va; va_start(va, fmt); res = call_method(method, fmt, va); va_end(va); if (res != NULL) { Py_DECREF(res); if (res != Py_None) { sip_api_bad_catcher_result(method); res = NULL; } } Py_DECREF(method); if (res == NULL) sip_api_call_error_handler(error_handler, py_self, gil_state); SIP_RELEASE_GIL(gil_state); } /* * Call the Python re-implementation of a C++ virtual. */ static PyObject *sip_api_call_method(int *isErr, PyObject *method, const char *fmt, ...) { PyObject *res; va_list va; va_start(va, fmt); res = call_method(method, fmt, va); va_end(va); if (res == NULL && isErr != NULL) *isErr = TRUE; return res; } /* * Build a result object based on a format string. */ static PyObject *sip_api_build_result(int *isErr, const char *fmt, ...) { PyObject *res = NULL; int badfmt, tupsz; va_list va; va_start(va,fmt); /* Basic validation of the format string. */ badfmt = FALSE; if (*fmt == '(') { char *ep; if ((ep = strchr(fmt,')')) == NULL || ep[1] != '\0') badfmt = TRUE; else tupsz = (int)(ep - fmt - 1); } else if (strlen(fmt) == 1) tupsz = -1; else badfmt = TRUE; if (badfmt) PyErr_Format(PyExc_SystemError,"sipBuildResult(): invalid format string \"%s\"",fmt); else if (tupsz < 0 || (res = PyTuple_New(tupsz)) != NULL) res = buildObject(res,fmt,va); va_end(va); if (res == NULL && isErr != NULL) *isErr = TRUE; return res; } /* * Get the values off the stack and put them into an object. */ static PyObject *buildObject(PyObject *obj, const char *fmt, va_list va) { char ch, termch; int i; /* * The format string has already been checked that it is properly formed if * it is enclosed in parenthesis. */ if (*fmt == '(') { termch = ')'; ++fmt; } else termch = '\0'; i = 0; while ((ch = *fmt++) != termch) { PyObject *el; switch (ch) { case 'g': { char *s; Py_ssize_t l; s = va_arg(va, char *); l = va_arg(va, Py_ssize_t); if (s != NULL) { el = PyBytes_FromStringAndSize(s, l); } else { Py_INCREF(Py_None); el = Py_None; } } break; case 'G': #if defined(HAVE_WCHAR_H) { wchar_t *s; Py_ssize_t l; s = va_arg(va, wchar_t *); l = va_arg(va, Py_ssize_t); if (s != NULL) el = PyUnicode_FromWideChar(s, l); else { Py_INCREF(Py_None); el = Py_None; } } #else raiseNoWChar(); el = NULL; #endif break; case 'b': el = PyBool_FromLong(va_arg(va,int)); break; case 'c': { char c = va_arg(va, int); el = PyBytes_FromStringAndSize(&c, 1); } break; case 'a': { char c = va_arg(va, int); el = PyUnicode_FromStringAndSize(&c, 1); } break; case 'w': #if defined(HAVE_WCHAR_H) { wchar_t c = va_arg(va, int); el = PyUnicode_FromWideChar(&c, 1); } #else raiseNoWChar(); el = NULL; #endif break; case 'F': { int ev = va_arg(va, int); const sipTypeDef *td = va_arg(va, const sipTypeDef *); el = sip_api_convert_from_enum(ev, td); } break; case 'd': case 'f': el = PyFloat_FromDouble(va_arg(va, double)); break; case 'e': case 'h': case 'i': case 'L': el = PyLong_FromLong(va_arg(va, int)); break; case 'l': el = PyLong_FromLong(va_arg(va, long)); break; case 'm': el = PyLong_FromUnsignedLong(va_arg(va, unsigned long)); break; case 'n': el = PyLong_FromLongLong(va_arg(va, long long)); break; case 'o': el = PyLong_FromUnsignedLongLong(va_arg(va, unsigned long long)); break; case 's': { char *s = va_arg(va, char *); if (s != NULL) { el = PyBytes_FromString(s); } else { Py_INCREF(Py_None); el = Py_None; } } break; case 'A': { char *s = va_arg(va, char *); if (s != NULL) { el = PyUnicode_FromString(s); } else { Py_INCREF(Py_None); el = Py_None; } } break; case 'x': #if defined(HAVE_WCHAR_H) { wchar_t *s = va_arg(va, wchar_t *); if (s != NULL) el = PyUnicode_FromWideChar(s, (Py_ssize_t)wcslen(s)); else { Py_INCREF(Py_None); el = Py_None; } } #else raiseNoWChar(); el = NULL; #endif break; case 't': case 'u': case 'M': el = PyLong_FromUnsignedLong(va_arg(va, unsigned)); break; case '=': el = PyLong_FromSize_t(va_arg(va, size_t)); break; case 'N': { void *p = va_arg(va, void *); const sipTypeDef *td = va_arg(va, const sipTypeDef *); PyObject *xfer = va_arg(va, PyObject *); el = sip_api_convert_from_new_type(p, td, xfer); } break; case 'D': { void *p = va_arg(va, void *); const sipTypeDef *td = va_arg(va, const sipTypeDef *); PyObject *xfer = va_arg(va, PyObject *); el = sip_api_convert_from_type(p, td, xfer); } break; case 'r': { void *p = va_arg(va, void *); Py_ssize_t l = va_arg(va, Py_ssize_t); const sipTypeDef *td = va_arg(va, const sipTypeDef *); el = convertToSequence(p, l, td); } break; case 'R': el = va_arg(va,PyObject *); break; case 'S': el = va_arg(va,PyObject *); Py_INCREF(el); break; case 'V': el = sip_api_convert_from_void_ptr(va_arg(va, void *)); break; case 'z': { const char *name = va_arg(va, const char *); void *p = va_arg(va, void *); if (p == NULL) { el = Py_None; Py_INCREF(el); } else { el = PyCapsule_New(p, name, NULL); } } break; default: PyErr_Format(PyExc_SystemError,"buildObject(): invalid format character '%c'",ch); el = NULL; } if (el == NULL) { Py_XDECREF(obj); return NULL; } if (obj == NULL) return el; PyTuple_SET_ITEM(obj,i,el); ++i; } return obj; } /* * Parse a result object based on a format string. As of v9.0 of the API this * is only ever called by handwritten code. */ static int sip_api_parse_result(int *isErr, PyObject *method, PyObject *res, const char *fmt, ...) { int rc; va_list va; va_start(va, fmt); rc = parseResult(method, res, NULL, fmt, va); va_end(va); if (isErr != NULL && rc < 0) *isErr = TRUE; return rc; } /* * Parse a result object based on a format string. */ static int sip_api_parse_result_ex(sip_gilstate_t gil_state, sipVirtErrorHandlerFunc error_handler, sipSimpleWrapper *py_self, PyObject *method, PyObject *res, const char *fmt, ...) { int rc; if (res != NULL) { va_list va; va_start(va, fmt); rc = parseResult(method, res, deref_mixin(py_self), fmt, va); va_end(va); Py_DECREF(res); } else { rc = -1; } Py_DECREF(method); if (rc < 0) sip_api_call_error_handler(error_handler, py_self, gil_state); SIP_RELEASE_GIL(gil_state); return rc; } /* * Call a virtual error handler. This is called with the GIL and from the * thread that raised the error. */ static void sip_api_call_error_handler(sipVirtErrorHandlerFunc error_handler, sipSimpleWrapper *py_self, sip_gilstate_t sipGILState) { if (error_handler != NULL) error_handler(deref_mixin(py_self), sipGILState); else PyErr_Print(); } /* * Do the main work of parsing a result object based on a format string. */ static int parseResult(PyObject *method, PyObject *res, sipSimpleWrapper *py_self, const char *fmt, va_list va) { int tupsz, rc = 0; /* We rely on PyErr_Occurred(). */ PyErr_Clear(); /* Get self if it is provided as an argument. */ if (*fmt == 'S') { py_self = va_arg(va, sipSimpleWrapper *); ++fmt; } /* Basic validation of the format string. */ if (*fmt == '(') { char ch; const char *cp = ++fmt; int sub_format = FALSE; tupsz = 0; while ((ch = *cp++) != ')') { if (ch == '\0') { PyErr_Format(PyExc_SystemError, "sipParseResult(): invalid format string \"%s\"", fmt - 1); rc = -1; break; } if (sub_format) { sub_format = FALSE; } else { ++tupsz; /* Some format characters have a sub-format. */ if (strchr("aAHDC", ch) != NULL) sub_format = TRUE; } } if (rc == 0) if (!PyTuple_Check(res) || PyTuple_GET_SIZE(res) != tupsz) { sip_api_bad_catcher_result(method); rc = -1; } } else tupsz = -1; if (rc == 0) { char ch; int i = 0; while ((ch = *fmt++) != '\0' && ch != ')' && rc == 0) { PyObject *arg; int invalid = FALSE; if (tupsz > 0) { arg = PyTuple_GET_ITEM(res,i); ++i; } else arg = res; switch (ch) { case 'g': { const char **p = va_arg(va, const char **); Py_ssize_t *szp = va_arg(va, Py_ssize_t *); if (parseBytes_AsCharArray(arg, p, szp) < 0) invalid = TRUE; } break; case 'G': #if defined(HAVE_WCHAR_H) { wchar_t **p = va_arg(va, wchar_t **); Py_ssize_t *szp = va_arg(va, Py_ssize_t *); if (parseWCharArray(arg, p, szp) < 0) invalid = TRUE; } #else raiseNoWChar(); invalid = TRUE; #endif break; case 'b': { char *p = va_arg(va, void *); int v = sip_api_convert_to_bool(arg); if (v < 0) invalid = TRUE; else if (p != NULL) sip_set_bool(p, v); } break; case 'c': { char *p = va_arg(va, char *); if (parseBytes_AsChar(arg, p) < 0) invalid = TRUE; } break; case 'a': { char *p = va_arg(va, char *); int enc; switch (*fmt++) { case 'A': enc = parseString_AsASCIIChar(arg, p); break; case 'L': enc = parseString_AsLatin1Char(arg, p); break; case '8': enc = parseString_AsUTF8Char(arg, p); break; default: enc = -1; } if (enc < 0) invalid = TRUE; } break; case 'w': #if defined(HAVE_WCHAR_H) { wchar_t *p = va_arg(va, wchar_t *); if (parseWChar(arg, p) < 0) invalid = TRUE; } #else raiseNoWChar(); invalid = TRUE; #endif break; case 'd': { double *p = va_arg(va, double *); double v = PyFloat_AsDouble(arg); if (PyErr_Occurred()) invalid = TRUE; else if (p != NULL) *p = v; } break; case 'F': { sipTypeDef *td = va_arg(va, sipTypeDef *); int *p = va_arg(va, int *); int v = sip_api_convert_to_enum(arg, td); if (PyErr_Occurred()) invalid = TRUE; else if (p != NULL) *p = v; } break; case 'f': { float *p = va_arg(va, float *); float v = (float)PyFloat_AsDouble(arg); if (PyErr_Occurred()) invalid = TRUE; else if (p != NULL) *p = v; } break; case 'I': { char *p = va_arg(va, char *); char v = sip_api_long_as_char(arg); if (PyErr_Occurred()) invalid = TRUE; else if (p != NULL) *p = v; } break; case 'L': { signed char *p = va_arg(va, signed char *); signed char v = sip_api_long_as_signed_char(arg); if (PyErr_Occurred()) invalid = TRUE; else if (p != NULL) *p = v; } break; case 'M': { unsigned char *p = va_arg(va, unsigned char *); unsigned char v = sip_api_long_as_unsigned_char(arg); if (PyErr_Occurred()) invalid = TRUE; else if (p != NULL) *p = v; } break; case 'h': { signed short *p = va_arg(va, signed short *); signed short v = sip_api_long_as_short(arg); if (PyErr_Occurred()) invalid = TRUE; else if (p != NULL) *p = v; } break; case 't': { unsigned short *p = va_arg(va, unsigned short *); unsigned short v = sip_api_long_as_unsigned_short(arg); if (PyErr_Occurred()) invalid = TRUE; else if (p != NULL) *p = v; } break; case 'e': { int *p = va_arg(va, int *); int v = sip_api_long_as_int(arg); if (PyErr_Occurred()) invalid = TRUE; else if (p != NULL) *p = v; } break; case 'i': { int *p = va_arg(va, int *); int v = sip_api_long_as_int(arg); if (PyErr_Occurred()) invalid = TRUE; else if (p != NULL) *p = v; } break; case 'u': { unsigned *p = va_arg(va, unsigned *); unsigned v = sip_api_long_as_unsigned_int(arg); if (PyErr_Occurred()) invalid = TRUE; else if (p != NULL) *p = v; } break; case '=': { size_t *p = va_arg(va, size_t *); size_t v = sip_api_long_as_size_t(arg); if (PyErr_Occurred()) invalid = TRUE; else if (p != NULL) *p = v; } break; case 'l': { long *p = va_arg(va, long *); long v = sip_api_long_as_long(arg); if (PyErr_Occurred()) invalid = TRUE; else if (p != NULL) *p = v; } break; case 'm': { unsigned long *p = va_arg(va, unsigned long *); unsigned long v = sip_api_long_as_unsigned_long(arg); if (PyErr_Occurred()) invalid = TRUE; else if (p != NULL) *p = v; } break; case 'n': { long long *p = va_arg(va, long long *); long long v = sip_api_long_as_long_long(arg); if (PyErr_Occurred()) invalid = TRUE; else if (p != NULL) *p = v; } break; case 'o': { unsigned long long *p = va_arg(va, unsigned long long *); unsigned long long v = sip_api_long_as_unsigned_long_long(arg); if (PyErr_Occurred()) invalid = TRUE; else if (p != NULL) *p = v; } break; case 'A': { int key = va_arg(va, int); const char **p = va_arg(va, const char **); PyObject *keep; switch (*fmt++) { case 'A': keep = parseString_AsASCIIString(arg, p); break; case 'L': keep = parseString_AsLatin1String(arg, p); break; case '8': keep = parseString_AsUTF8String(arg, p); break; default: keep = NULL; } if (keep == NULL) invalid = TRUE; else sip_api_keep_reference((PyObject *)py_self, key, keep); } break; case 'B': { int key = va_arg(va, int); const char **p = va_arg(va, const char **); if (parseBytes_AsString(arg, p) < 0) invalid = TRUE; else sip_api_keep_reference((PyObject *)py_self, key, arg); } break; case 'x': #if defined(HAVE_WCHAR_H) { wchar_t **p = va_arg(va, wchar_t **); if (parseWCharString(arg, p) < 0) invalid = TRUE; } #else raiseNoWChar(); invalid = TRUE; #endif break; case 'H': { if (*fmt == '\0') { invalid = TRUE; } else { int flags = *fmt++ - '0'; int iserr = FALSE, state; const sipTypeDef *td; void *cpp, *val, *user_state; td = va_arg(va, const sipTypeDef *); cpp = va_arg(va, void **); val = sip_api_force_convert_to_type_us(arg, td, (flags & FMT_RP_FACTORY ? arg : NULL), (flags & FMT_RP_DEREF ? SIP_NOT_NONE : 0), &state, (flags & FMT_RP_MAKE_COPY ? &user_state : NULL), &iserr); if (iserr) { invalid = TRUE; } else if (flags & FMT_RP_MAKE_COPY) { sipAssignFunc assign_helper; if (sipTypeIsMapped(td)) assign_helper = ((const sipMappedTypeDef *)td)->mtd_assign; else assign_helper = ((const sipClassTypeDef *)td)->ctd_assign; assert(assign_helper != NULL); if (cpp != NULL) assign_helper(cpp, 0, val); sip_api_release_type_us(val, td, state, user_state); } else if (cpp != NULL) { *(void **)cpp = val; } } } break; case 'N': { PyTypeObject *type = va_arg(va, PyTypeObject *); PyObject **p = va_arg(va, PyObject **); if (arg == Py_None || PyObject_TypeCheck(arg, type)) { if (p != NULL) { Py_INCREF(arg); *p = arg; } } else { invalid = TRUE; } } break; case 'O': { PyObject **p = va_arg(va, PyObject **); if (p != NULL) { Py_INCREF(arg); *p = arg; } } break; case 'T': { PyTypeObject *type = va_arg(va, PyTypeObject *); PyObject **p = va_arg(va, PyObject **); if (PyObject_TypeCheck(arg, type)) { if (p != NULL) { Py_INCREF(arg); *p = arg; } } else { invalid = TRUE; } } break; case 'V': { void *v = sip_api_convert_to_void_ptr(arg); void **p = va_arg(va, void **); if (PyErr_Occurred()) invalid = TRUE; else if (p != NULL) *p = v; } break; case 'z': { const char *name = va_arg(va, const char *); void **p = va_arg(va, void **); if (arg == Py_None) { if (p != NULL) *p = NULL; } else { #if defined(SIP_USE_CAPSULE) void *v = PyCapsule_GetPointer(arg, name); #else void *v = sip_api_convert_to_void_ptr(arg); (void)name; #endif if (PyErr_Occurred()) invalid = TRUE; else if (p != NULL) *p = v; } } break; case 'Z': if (arg != Py_None) invalid = TRUE; break; case '!': { PyObject **p = va_arg(va, PyObject **); if (PyObject_CheckBuffer(arg)) { if (p != NULL) { Py_INCREF(arg); *p = arg; } } else { invalid = TRUE; } } break; case '$': { PyObject **p = va_arg(va, PyObject **); if (arg == Py_None || PyObject_CheckBuffer(arg)) { if (p != NULL) { Py_INCREF(arg); *p = arg; } } else { invalid = TRUE; } } break; case '&': { PyObject **p = va_arg(va, PyObject **); if (sip_enum_is_enum(arg)) { if (p != NULL) { Py_INCREF(arg); *p = arg; } } else { invalid = TRUE; } } break; case '^': { PyObject **p = va_arg(va, PyObject **); if (arg == Py_None || sip_enum_is_enum(arg)) { if (p != NULL) { Py_INCREF(arg); *p = arg; } } else { invalid = TRUE; } } break; default: PyErr_Format(PyExc_SystemError,"sipParseResult(): invalid format character '%c'",ch); rc = -1; } if (invalid) { sip_api_bad_catcher_result(method); rc = -1; break; } } } return rc; } /* * Parse the arguments to a C/C++ function without any side effects. */ static int sip_api_parse_args(PyObject **parseErrp, PyObject *sipArgs, const char *fmt, ...) { int ok; va_list va; va_start(va, fmt); ok = parseKwdArgs(parseErrp, sipArgs, NULL, NULL, NULL, fmt, va); va_end(va); return ok; } /* * Parse the positional and/or keyword arguments to a C/C++ function without * any side effects. */ static int sip_api_parse_kwd_args(PyObject **parseErrp, PyObject *sipArgs, PyObject *sipKwdArgs, const char **kwdlist, PyObject **unused, const char *fmt, ...) { int ok; va_list va; if (unused != NULL) { /* * Initialise the return of any unused keyword arguments. This is * used by any ctor overload. */ *unused = NULL; } va_start(va, fmt); ok = parseKwdArgs(parseErrp, sipArgs, sipKwdArgs, kwdlist, unused, fmt, va); va_end(va); /* Release any unused arguments if the parse failed. */ if (!ok && unused != NULL) { Py_XDECREF(*unused); } return ok; } /* * Parse the arguments to a C/C++ function without any side effects. */ static int parseKwdArgs(PyObject **parseErrp, PyObject *sipArgs, PyObject *sipKwdArgs, const char **kwdlist, PyObject **unused, const char *fmt, va_list va_orig) { int no_tmp_tuple, ok, selfarg; PyObject *self, *single_arg; va_list va; /* Previous second pass errors stop subsequent parses. */ if (*parseErrp != NULL && !PyList_Check(*parseErrp)) return FALSE; /* * See if we are parsing a single argument. In current versions we are * told explicitly by the first character of the format string. In earlier * versions we guessed (sometimes wrongly). */ if (*fmt == '1') { ++fmt; no_tmp_tuple = FALSE; } else no_tmp_tuple = PyTuple_Check(sipArgs); if (no_tmp_tuple) { Py_INCREF(sipArgs); } else if ((single_arg = PyTuple_New(1)) != NULL) { Py_INCREF(sipArgs); PyTuple_SET_ITEM(single_arg, 0, sipArgs); sipArgs = single_arg; } else { /* Stop all parsing and indicate an exception has been raised. */ Py_XDECREF(*parseErrp); *parseErrp = Py_None; Py_INCREF(Py_None); return FALSE; } /* * The first pass checks all the types and does conversions that are cheap * and have no side effects. */ va_copy(va, va_orig); ok = parsePass1(parseErrp, &self, &selfarg, sipArgs, sipKwdArgs, kwdlist, unused, fmt, va); va_end(va); if (ok) { /* * The second pass does any remaining conversions now that we know we * have the right signature. */ va_copy(va, va_orig); ok = parsePass2(self, selfarg, sipArgs, sipKwdArgs, kwdlist, fmt, va); va_end(va); /* Remove any previous failed parses. */ Py_XDECREF(*parseErrp); if (ok) { *parseErrp = NULL; } else { /* Indicate that an exception has been raised. */ *parseErrp = Py_None; Py_INCREF(Py_None); } } Py_DECREF(sipArgs); return ok; } /* * Return a string as a Python object that describes an argument with an * unexpected type. */ static PyObject *bad_type_str(int arg_nr, PyObject *arg) { return PyUnicode_FromFormat("argument %d has unexpected type '%s'", arg_nr, Py_TYPE(arg)->tp_name); } /* * Adds a failure about an argument with an incorrect type to the current list * of exceptions. */ static sipErrorState sip_api_bad_callable_arg(int arg_nr, PyObject *arg) { PyObject *detail = bad_type_str(arg_nr + 1, arg); if (detail == NULL) return sipErrorFail; PyErr_SetObject(PyExc_TypeError, detail); Py_DECREF(detail); return sipErrorContinue; } /* * Adds the current exception to the current list of exceptions (if it is a * user exception) or replace the current list of exceptions. */ static void sip_api_add_exception(sipErrorState es, PyObject **parseErrp) { assert(*parseErrp == NULL); if (es == sipErrorContinue) { sipParseFailure failure; PyObject *e_type, *e_traceback; /* Get the value of the exception. */ PyErr_Fetch(&e_type, &failure.detail_obj, &e_traceback); Py_XDECREF(e_type); Py_XDECREF(e_traceback); failure.reason = Exception; add_failure(parseErrp, &failure); if (failure.reason == Raised) { Py_XDECREF(failure.detail_obj); es = sipErrorFail; } } if (es == sipErrorFail) { Py_XDECREF(*parseErrp); *parseErrp = Py_None; Py_INCREF(Py_None); } } /* * The dtor for parse failure wrapped in a Python object. */ static void failure_dtor(PyObject *capsule) { sipParseFailure *failure = (sipParseFailure *)PyCapsule_GetPointer(capsule, NULL); Py_XDECREF(failure->detail_obj); sip_api_free(failure); } /* * Add a parse failure to the current list of exceptions. */ static void add_failure(PyObject **parseErrp, sipParseFailure *failure) { sipParseFailure *failure_copy; PyObject *failure_obj; /* Create the list if necessary. */ if (*parseErrp == NULL && (*parseErrp = PyList_New(0)) == NULL) { failure->reason = Raised; return; } /* * Make a copy of the failure, convert it to a Python object and add it to * the list. We do it this way to make it as lightweight as possible. */ if ((failure_copy = sip_api_malloc(sizeof (sipParseFailure))) == NULL) { failure->reason = Raised; return; } *failure_copy = *failure; if ((failure_obj = PyCapsule_New(failure_copy, NULL, failure_dtor)) == NULL) { sip_api_free(failure_copy); failure->reason = Raised; return; } /* Ownership of any detail object is now with the wrapped failure. */ failure->detail_obj = NULL; if (PyList_Append(*parseErrp, failure_obj) < 0) { Py_DECREF(failure_obj); failure->reason = Raised; return; } Py_DECREF(failure_obj); } /* * Parse one or a pair of arguments to a C/C++ function without any side * effects. */ static int sip_api_parse_pair(PyObject **parseErrp, PyObject *sipArg0, PyObject *sipArg1, const char *fmt, ...) { int ok, selfarg; PyObject *self, *args; va_list va; /* Previous second pass errors stop subsequent parses. */ if (*parseErrp != NULL && !PyList_Check(*parseErrp)) return FALSE; if ((args = PyTuple_New(sipArg1 != NULL ? 2 : 1)) == NULL) { /* Stop all parsing and indicate an exception has been raised. */ Py_XDECREF(*parseErrp); *parseErrp = Py_None; Py_INCREF(Py_None); return FALSE; } Py_INCREF(sipArg0); PyTuple_SET_ITEM(args, 0, sipArg0); if (sipArg1 != NULL) { Py_INCREF(sipArg1); PyTuple_SET_ITEM(args, 1, sipArg1); } /* * The first pass checks all the types and does conversions that are cheap * and have no side effects. */ va_start(va, fmt); ok = parsePass1(parseErrp, &self, &selfarg, args, NULL, NULL, NULL, fmt, va); va_end(va); if (ok) { /* * The second pass does any remaining conversions now that we know we * have the right signature. */ va_start(va, fmt); ok = parsePass2(self, selfarg, args, NULL, NULL, fmt, va); va_end(va); /* Remove any previous failed parses. */ Py_XDECREF(*parseErrp); if (ok) { *parseErrp = NULL; } else { /* Indicate that an exception has been raised. */ *parseErrp = Py_None; Py_INCREF(Py_None); } } Py_DECREF(args); return ok; } /* * First pass of the argument parse, converting those that can be done so * without any side effects. Return TRUE if the arguments matched. */ static int parsePass1(PyObject **parseErrp, PyObject **selfp, int *selfargp, PyObject *sipArgs, PyObject *sipKwdArgs, const char **kwdlist, PyObject **unused, const char *fmt, va_list va) { int compulsory, argnr, nr_args; Py_ssize_t nr_pos_args, nr_kwd_args, nr_kwd_args_used; sipParseFailure failure; failure.reason = Ok; failure.detail_obj = NULL; compulsory = TRUE; argnr = 0; nr_args = 0; nr_pos_args = PyTuple_GET_SIZE(sipArgs); nr_kwd_args = nr_kwd_args_used = 0; if (sipKwdArgs != NULL) { assert(PyDict_Check(sipKwdArgs)); nr_kwd_args = PyDict_Size(sipKwdArgs); } /* * Handle those format characters that deal with the "self" argument. They * will always be the first one. */ *selfp = NULL; *selfargp = FALSE; switch (*fmt++) { case '#': /* A ctor has an argument with the /Transfer/ annotation. */ *selfp = va_arg(va, PyObject *); break; case 'B': case 'p': { PyObject *self; sipTypeDef *td; self = *va_arg(va, PyObject **); td = va_arg(va, sipTypeDef *); va_arg(va, void **); if (PyObject_TypeCheck(self, (PyTypeObject *)&sipSimpleWrapper_Type)) { /* The call was self.method(...). */ *selfp = self; } else if (getSelfFromArgs(td, sipArgs, argnr, selfp)) { /* The call was cls.method(self, ...). */ *selfargp = TRUE; ++argnr; } else { failure.reason = Unbound; failure.detail_str = sipPyNameOfContainer( &((sipClassTypeDef *)td)->ctd_container, td); } break; } case 'C': { PyObject *self; self = *va_arg(va, PyObject **); /* * If the call was self.method(...) rather than cls.method(...) * then get cls from self. */ if (PyObject_TypeCheck(self, (PyTypeObject *)&sipWrapper_Type)) self = (PyObject *)Py_TYPE(self); *selfp = self; break; } default: --fmt; } /* * Now handle the remaining arguments. We continue to parse if we get an * overflow because that is, strictly speaking, a second pass error. */ while (failure.reason == Ok || failure.reason == Overflow) { char ch; PyObject *arg; PyErr_Clear(); /* See if the following arguments are optional. */ if ((ch = *fmt++) == '|') { compulsory = FALSE; ch = *fmt++; } /* See if we don't expect anything else. */ if (ch == '\0') { if (argnr < nr_pos_args) { /* There are still positional arguments. */ failure.reason = TooMany; } else if (nr_kwd_args_used != nr_kwd_args) { /* * Take a shortcut if no keyword arguments were used and we are * interested in them. */ if (nr_kwd_args_used == 0 && unused != NULL) { Py_INCREF(sipKwdArgs); *unused = sipKwdArgs; } else { PyObject *key, *value, *unused_dict = NULL; Py_ssize_t pos = 0; /* * Go through the keyword arguments to find any that were * duplicates of positional arguments. For the remaining * ones remember the unused ones if we are interested. */ while (PyDict_Next(sipKwdArgs, &pos, &key, &value)) { int a; if (!PyUnicode_Check(key)) { failure.reason = KeywordNotString; failure.detail_obj = key; Py_INCREF(key); break; } if (kwdlist != NULL) { /* Get the argument's index if it is one. */ for (a = 0; a < nr_args; ++a) { const char *name = kwdlist[a]; if (name == NULL) continue; if (PyUnicode_CompareWithASCIIString(key, name) == 0) break; } } else { a = nr_args; } if (a == nr_args) { /* * The name doesn't correspond to a keyword * argument. */ if (unused == NULL) { /* * It may correspond to a keyword argument of a * different overload. */ failure.reason = UnknownKeyword; failure.detail_obj = key; Py_INCREF(key); break; } /* * Add it to the dictionary of unused arguments * creating it if necessary. Note that if the * unused arguments are actually used by a later * overload then the parse will incorrectly * succeed. This should be picked up (perhaps with * a misleading exception) so long as the code that * handles the unused arguments checks that it can * handle them all. */ if (unused_dict == NULL && (*unused = unused_dict = PyDict_New()) == NULL) { failure.reason = Raised; break; } if (PyDict_SetItem(unused_dict, key, value) < 0) { failure.reason = Raised; break; } } else if (a < nr_pos_args - *selfargp) { /* * The argument has been given positionally and as * a keyword. */ failure.reason = Duplicate; failure.detail_obj = key; Py_INCREF(key); break; } } } } break; } /* Get the next argument. */ arg = NULL; failure.arg_nr = -1; failure.arg_name = NULL; if (argnr < nr_pos_args) { arg = PyTuple_GET_ITEM(sipArgs, argnr); failure.arg_nr = argnr + 1; } else if (nr_kwd_args != 0 && kwdlist != NULL) { const char *name = kwdlist[argnr - *selfargp]; if (name != NULL) { arg = PyDict_GetItemString(sipKwdArgs, name); if (arg != NULL) ++nr_kwd_args_used; failure.arg_name = name; } } ++argnr; ++nr_args; if (arg == NULL && compulsory) { if (ch == 'W') { /* * A variable number of arguments was allowed but none were * given. */ break; } /* An argument was required. */ failure.reason = TooFew; /* * Check if there were any unused keyword arguments so that we give * a (possibly) more accurate diagnostic in the case that a keyword * argument has been mis-spelled. */ if (unused == NULL && sipKwdArgs != NULL && nr_kwd_args_used != nr_kwd_args) { PyObject *key, *value; Py_ssize_t pos = 0; while (PyDict_Next(sipKwdArgs, &pos, &key, &value)) { int a; if (!PyUnicode_Check(key)) { failure.reason = KeywordNotString; failure.detail_obj = key; Py_INCREF(key); break; } if (kwdlist != NULL) { /* Get the argument's index if it is one. */ for (a = 0; a < nr_args; ++a) { const char *name = kwdlist[a]; if (name == NULL) continue; if (PyUnicode_CompareWithASCIIString(key, name) == 0) break; } } else { a = nr_args; } if (a == nr_args) { failure.reason = UnknownKeyword; failure.detail_obj = key; Py_INCREF(key); break; } } } break; } /* * Handle the format character even if we don't have an argument so * that we skip the right number of arguments. */ switch (ch) { case 'W': /* Ellipsis. */ break; case '@': { /* Implement /GetWrapper/. */ PyObject **p = va_arg(va, PyObject **); if (arg != NULL) *p = arg; /* Process the same argument next time round. */ --argnr; --nr_args; break; } case 's': { /* String from a Python bytes or None. */ const char **p = va_arg(va, const char **); if (arg != NULL && parseBytes_AsString(arg, p) < 0) handle_failed_type_conversion(&failure, arg); break; } case 'A': { /* String from a Python string or None. */ va_arg(va, PyObject **); va_arg(va, const char **); fmt++; if (arg != NULL && check_encoded_string(arg) < 0) handle_failed_type_conversion(&failure, arg); break; } case 'a': { /* Character from a Python string. */ va_arg(va, char *); fmt++; if (arg != NULL && check_encoded_string(arg) < 0) handle_failed_type_conversion(&failure, arg); break; } case 'x': #if defined(HAVE_WCHAR_H) { /* Wide string or None. */ wchar_t **p = va_arg(va, wchar_t **); if (arg != NULL && parseWCharString(arg, p) < 0) handle_failed_type_conversion(&failure, arg); break; } #else raiseNoWChar(); failure.reason = Raised; break; #endif case 'r': { /* * Sequence of mapped type instances. For ABI v13.3 and * earlier this is also used for class instances. */ const sipTypeDef *td; td = va_arg(va, const sipTypeDef *); va_arg(va, void **); va_arg(va, Py_ssize_t *); if (arg != NULL && !canConvertFromSequence(arg, td)) handle_failed_type_conversion(&failure, arg); break; } case '>': { /* * Sequence or sip.array of class instances. This is only used * by ABI v13.4 and later. */ const sipTypeDef *td; td = va_arg(va, const sipTypeDef *); va_arg(va, void **); va_arg(va, Py_ssize_t *); va_arg(va, int *); if (arg != NULL && !sip_array_can_convert(arg, td) && !canConvertFromSequence(arg, td)) handle_failed_type_conversion(&failure, arg); break; } case 'J': { /* Class or mapped type instance. */ char sub_fmt = *fmt++; const sipTypeDef *td; int flags = sub_fmt - '0'; int iflgs = 0; td = va_arg(va, const sipTypeDef *); va_arg(va, void **); if (flags & FMT_AP_DEREF) iflgs |= SIP_NOT_NONE; if (flags & FMT_AP_TRANSFER_THIS) va_arg(va, PyObject **); if (flags & FMT_AP_NO_CONVERTORS) iflgs |= SIP_NO_CONVERTORS; else va_arg(va, int *); if (sipTypeNeedsUserState(td)) va_arg(va, void **); if (arg != NULL && !sip_api_can_convert_to_type(arg, td, iflgs)) handle_failed_type_conversion(&failure, arg); break; } case 'N': { /* Python object of given type or None. */ PyTypeObject *type = va_arg(va,PyTypeObject *); PyObject **p = va_arg(va,PyObject **); if (arg != NULL) { if (arg == Py_None || PyObject_TypeCheck(arg,type)) *p = arg; else handle_failed_type_conversion(&failure, arg); } break; } case 'P': { /* Python object of any type with a sub-format. */ va_arg(va, PyObject **); /* Skip the sub-format. */ ++fmt; break; } case 'T': { /* Python object of given type. */ PyTypeObject *type = va_arg(va, PyTypeObject *); PyObject **p = va_arg(va, PyObject **); if (arg != NULL) { if (PyObject_TypeCheck(arg,type)) *p = arg; else handle_failed_type_conversion(&failure, arg); } break; } case 'F': { /* Python callable object. */ PyObject **p = va_arg(va, PyObject **); if (arg != NULL) { if (PyCallable_Check(arg)) *p = arg; else handle_failed_type_conversion(&failure, arg); } break; } case 'H': { /* Python callable object or None. */ PyObject **p = va_arg(va, PyObject **); if (arg != NULL) { if (arg == Py_None || PyCallable_Check(arg)) *p = arg; else handle_failed_type_conversion(&failure, arg); } break; } case '!': { /* Python object that implements the buffer protocol. */ PyObject **p = va_arg(va, PyObject **); if (arg != NULL) { if (PyObject_CheckBuffer(arg)) *p = arg; else handle_failed_type_conversion(&failure, arg); } break; } case '$': { /* * Python object that implements the buffer protocol or None. */ PyObject **p = va_arg(va, PyObject **); if (arg != NULL) { if (arg == Py_None || PyObject_CheckBuffer(arg)) *p = arg; else handle_failed_type_conversion(&failure, arg); } break; } case '&': { /* Python enum.Enum object. */ PyObject **p = va_arg(va, PyObject **); if (arg != NULL) { if (sip_enum_is_enum(arg)) *p = arg; else handle_failed_type_conversion(&failure, arg); } break; } case '^': { /* * Python enum.Enum object or None. */ PyObject **p = va_arg(va, PyObject **); if (arg != NULL) { if (arg == Py_None || sip_enum_is_enum(arg)) *p = arg; else handle_failed_type_conversion(&failure, arg); } break; } case 'k': { /* Char array or None. */ const char **p = va_arg(va, const char **); Py_ssize_t *szp = va_arg(va, Py_ssize_t *); if (arg != NULL && parseBytes_AsCharArray(arg, p, szp) < 0) handle_failed_type_conversion(&failure, arg); break; } case 'K': #if defined(HAVE_WCHAR_H) { /* Wide char array or None. */ wchar_t **p = va_arg(va, wchar_t **); Py_ssize_t *szp = va_arg(va, Py_ssize_t *); if (arg != NULL && parseWCharArray(arg, p, szp) < 0) handle_failed_type_conversion(&failure, arg); break; } #else raiseNoWChar(); failure.reason = Raised; break #endif case 'c': { /* Character from a Python bytes. */ char *p = va_arg(va, char *); if (arg != NULL && parseBytes_AsChar(arg, p) < 0) handle_failed_type_conversion(&failure, arg); break; } case 'w': #if defined(HAVE_WCHAR_H) { /* Wide character. */ wchar_t *p = va_arg(va, wchar_t *); if (arg != NULL && parseWChar(arg, p) < 0) handle_failed_type_conversion(&failure, arg); break; } #else raiseNoWChar(); failure.reason = Raised; break #endif case 'b': { /* Bool. */ void *p = va_arg(va, void *); if (arg != NULL) { int v = sip_api_convert_to_bool(arg); if (v < 0) handle_failed_type_conversion(&failure, arg); else sip_set_bool(p, v); } break; } case 'E': { /* Named or scoped enum. */ sipTypeDef *td = va_arg(va, sipTypeDef *); int *p = va_arg(va, int *); if (arg != NULL) { int v = sip_api_convert_to_enum(arg, td); if (PyErr_Occurred()) handle_failed_type_conversion(&failure, arg); else *p = v; } } break; case 'e': { /* Anonymous enum. */ int *p = va_arg(va, int *); if (arg != NULL) { int v = sip_api_long_as_int(arg); if (PyErr_Occurred()) handle_failed_int_conversion(&failure, arg); else *p = v; } } break; case 'i': { /* Integer. */ int *p = va_arg(va, int *); if (arg != NULL) { int v = sip_api_long_as_int(arg); if (PyErr_Occurred()) handle_failed_int_conversion(&failure, arg); else *p = v; } break; } case 'u': { /* Unsigned integer. */ unsigned *p = va_arg(va, unsigned *); if (arg != NULL) { unsigned v = sip_api_long_as_unsigned_int(arg); if (PyErr_Occurred()) handle_failed_int_conversion(&failure, arg); else *p = v; } break; } case '=': { /* size_t integer. */ size_t *p = va_arg(va, size_t *); if (arg != NULL) { size_t v = sip_api_long_as_size_t(arg); if (PyErr_Occurred()) handle_failed_int_conversion(&failure, arg); else *p = v; } break; } case 'I': { /* Char as an integer. */ char *p = va_arg(va, char *); if (arg != NULL) { char v = sip_api_long_as_char(arg); if (PyErr_Occurred()) handle_failed_int_conversion(&failure, arg); else *p = v; } break; } case 'L': { /* Signed char as an integer. */ signed char *p = va_arg(va, signed char *); if (arg != NULL) { signed char v = sip_api_long_as_signed_char(arg); if (PyErr_Occurred()) handle_failed_int_conversion(&failure, arg); else *p = v; } break; } case 'M': { /* Unsigned char as an integer. */ unsigned char *p = va_arg(va, unsigned char *); if (arg != NULL) { unsigned char v = sip_api_long_as_unsigned_char(arg); if (PyErr_Occurred()) handle_failed_int_conversion(&failure, arg); else *p = v; } break; } case 'h': { /* Short integer. */ signed short *p = va_arg(va, signed short *); if (arg != NULL) { signed short v = sip_api_long_as_short(arg); if (PyErr_Occurred()) handle_failed_int_conversion(&failure, arg); else *p = v; } break; } case 't': { /* Unsigned short integer. */ unsigned short *p = va_arg(va, unsigned short *); if (arg != NULL) { unsigned short v = sip_api_long_as_unsigned_short(arg); if (PyErr_Occurred()) handle_failed_int_conversion(&failure, arg); else *p = v; } break; } case 'l': { /* Long integer. */ long *p = va_arg(va, long *); if (arg != NULL) { long v = sip_api_long_as_long(arg); if (PyErr_Occurred()) handle_failed_int_conversion(&failure, arg); else *p = v; } break; } case 'm': { /* Unsigned long integer. */ unsigned long *p = va_arg(va, unsigned long *); if (arg != NULL) { unsigned long v = sip_api_long_as_unsigned_long(arg); if (PyErr_Occurred()) handle_failed_int_conversion(&failure, arg); else *p = v; } break; } case 'n': { /* Long long integer. */ long long *p = va_arg(va, long long *); if (arg != NULL) { long long v = sip_api_long_as_long_long(arg); if (PyErr_Occurred()) handle_failed_int_conversion(&failure, arg); else *p = v; } break; } case 'o': { /* Unsigned long long integer. */ unsigned long long *p = va_arg(va, unsigned long long *); if (arg != NULL) { unsigned long long v = sip_api_long_as_unsigned_long_long(arg); if (PyErr_Occurred()) handle_failed_int_conversion(&failure, arg); else *p = v; } break; } case 'f': { /* Float. */ float *p = va_arg(va, float *); if (arg != NULL) { double v = PyFloat_AsDouble(arg); if (PyErr_Occurred()) handle_failed_type_conversion(&failure, arg); else *p = (float)v; } break; } case 'X': { /* Constrained types. */ char sub_fmt = *fmt++; if (sub_fmt == 'E') { /* * Named or scoped enum. Note that in this version of the * ABI there is no difference between constrained and * unconstrained enums. */ sipTypeDef *td = va_arg(va, sipTypeDef *); int *p = va_arg(va, int *); if (arg != NULL) { *p = sip_api_convert_to_enum(arg, td); if (PyErr_Occurred()) handle_failed_type_conversion(&failure, arg); } } else { void *p = va_arg(va, void *); if (arg != NULL) { switch (sub_fmt) { case 'b': { /* Boolean. */ if (PyBool_Check(arg)) sip_set_bool(p, (arg == Py_True)); else handle_failed_type_conversion(&failure, arg); break; } case 'd': { /* Double float. */ if (PyFloat_Check(arg)) *(double *)p = PyFloat_AS_DOUBLE(arg); else handle_failed_type_conversion(&failure, arg); break; } case 'f': { /* Float. */ if (PyFloat_Check(arg)) *(float *)p = (float)PyFloat_AS_DOUBLE(arg); else handle_failed_type_conversion(&failure, arg); break; } case 'i': { /* Integer. */ if (PyLong_Check(arg)) { *(int *)p = sip_api_long_as_int(arg); if (PyErr_Occurred()) handle_failed_int_conversion(&failure, arg); } else { handle_failed_type_conversion(&failure, arg); } break; } } } } break; } case 'd': { /* Double float. */ double *p = va_arg(va,double *); if (arg != NULL) { double v = PyFloat_AsDouble(arg); if (PyErr_Occurred()) handle_failed_type_conversion(&failure, arg); else *p = v; } break; } case 'v': { /* Void pointer. */ void **p = va_arg(va, void **); if (arg != NULL) { void *v = sip_api_convert_to_void_ptr(arg); if (PyErr_Occurred()) handle_failed_type_conversion(&failure, arg); else *p = v; } break; } case 'z': { /* Void pointer as a capsule. */ const char *name = va_arg(va, const char *); void **p = va_arg(va, void **); if (arg == Py_None) { *p = NULL; } else if (arg != NULL) { void *v = PyCapsule_GetPointer(arg, name); if (PyErr_Occurred()) handle_failed_type_conversion(&failure, arg); else *p = v; } break; } } if ((failure.reason == Ok || failure.reason == Overflow) && ch == 'W') { /* An ellipsis matches everything and ends the parse. */ break; } } /* Handle parse failures appropriately. */ if (failure.reason == Ok) return TRUE; if (failure.reason == Overflow) { /* * We have successfully parsed the signature but one of the arguments * has been found to overflow. Raise an appropriate exception and * ensure we don't parse any subsequent overloads. */ if (failure.overflow_arg_nr >= 0) { PyErr_Format(PyExc_OverflowError, "argument %d overflowed: %S", failure.overflow_arg_nr, failure.detail_obj); } else { PyErr_Format(PyExc_OverflowError, "argument '%s' overflowed: %S", failure.overflow_arg_name, failure.detail_obj); } /* The overflow exception has now been raised. */ failure.reason = Raised; } if (failure.reason != Raised) add_failure(parseErrp, &failure); if (failure.reason == Raised) { Py_XDECREF(failure.detail_obj); /* * Discard any previous errors and flag that the exception we want the * user to see has been raised. */ Py_XDECREF(*parseErrp); *parseErrp = Py_None; Py_INCREF(Py_None); } return FALSE; } /* * Called after a failed conversion of an integer. */ static void handle_failed_int_conversion(sipParseFailure *pf, PyObject *arg) { PyObject *xtype, *xvalue, *xtb; assert(pf->reason == Ok || pf->reason == Overflow); PyErr_Fetch(&xtype, &xvalue, &xtb); if (PyErr_GivenExceptionMatches(xtype, PyExc_OverflowError) && xvalue != NULL) { /* Remove any previous overflow exception. */ Py_XDECREF(pf->detail_obj); pf->reason = Overflow; pf->overflow_arg_nr = pf->arg_nr; pf->overflow_arg_name = pf->arg_name; pf->detail_obj = xvalue; Py_INCREF(xvalue); } else { handle_failed_type_conversion(pf, arg); } PyErr_Restore(xtype, xvalue, xtb); } /* * Called after a failed conversion of a type. */ static void handle_failed_type_conversion(sipParseFailure *pf, PyObject *arg) { pf->reason = WrongType; pf->detail_obj = arg; Py_INCREF(arg); } /* * Second pass of the argument parse, converting the remaining ones that might * have side effects. Return TRUE if there was no error. */ static int parsePass2(PyObject *self, int selfarg, PyObject *sipArgs, PyObject *sipKwdArgs, const char **kwdlist, const char *fmt, va_list va) { int a, ok, isstatic = FALSE; Py_ssize_t nr_pos_args; /* Handle the conversions of "self" first. */ switch (*fmt++) { case '#': va_arg(va, PyObject *); break; case 'B': { /* * The address of a C++ instance when calling one of its public * methods. */ const sipTypeDef *td; void **p; *va_arg(va, PyObject **) = self; td = va_arg(va, const sipTypeDef *); p = va_arg(va, void **); if ((*p = sip_api_get_cpp_ptr((sipSimpleWrapper *)self, td)) == NULL) return FALSE; break; } case 'p': { /* * The address of a C++ instance when calling one of its protected * methods. */ const sipTypeDef *td; void **p; *va_arg(va, PyObject **) = self; td = va_arg(va, const sipTypeDef *); p = va_arg(va, void **); if ((*p = getComplexCppPtr((sipSimpleWrapper *)self, td)) == NULL) return FALSE; break; } case 'C': *va_arg(va, PyObject **) = self; isstatic = TRUE; break; default: --fmt; } ok = TRUE; nr_pos_args = PyTuple_GET_SIZE(sipArgs); for (a = (selfarg ? 1 : 0); *fmt != '\0' && *fmt != 'W' && ok; ++a) { char ch; PyObject *arg; /* Skip the optional character. */ if ((ch = *fmt++) == '|') ch = *fmt++; /* Get the next argument. */ arg = NULL; if (a < nr_pos_args) { arg = PyTuple_GET_ITEM(sipArgs, a); } else if (sipKwdArgs != NULL) { const char *name = kwdlist[a - selfarg]; if (name != NULL) arg = PyDict_GetItemString(sipKwdArgs, name); } /* * Do the outstanding conversions. For most types it has already been * done, so we are just skipping the parameters. */ switch (ch) { case '@': /* Implement /GetWrapper/. */ va_arg(va, PyObject **); /* Process the same argument next time round. */ --a; break; case 'r': { /* Sequence of mapped type instances. */ const sipTypeDef *td; void **array; Py_ssize_t *nr_elem; td = va_arg(va, const sipTypeDef *); array = va_arg(va, void **); nr_elem = va_arg(va, Py_ssize_t *); if (arg != NULL && !convertFromSequence(arg, td, array, nr_elem)) return FALSE; break; } case '>': { /* Sequence or sip.array of class instances. */ const sipTypeDef *td; void **array; Py_ssize_t *nr_elem; int *is_temp; td = va_arg(va, const sipTypeDef *); array = va_arg(va, void **); nr_elem = va_arg(va, Py_ssize_t *); is_temp = va_arg(va, int *); if (arg != NULL) { if (sip_array_can_convert(arg, td)) { sip_array_convert(arg, array, nr_elem); *is_temp = FALSE; } else if (convertFromSequence(arg, td, array, nr_elem)) { /* * Note that this will leak if there is a subsequent * error. */ *is_temp = TRUE; } else { return FALSE; } } break; } case 'J': { /* Class or mapped type instance. */ int flags = *fmt++ - '0'; const sipTypeDef *td; void **p; int iflgs = 0; int *statep; PyObject *xfer, **owner; void **user_statep; td = va_arg(va, const sipTypeDef *); p = va_arg(va, void **); if (flags & FMT_AP_TRANSFER) xfer = ((isstatic || self == NULL) ? arg : self); else if (flags & FMT_AP_TRANSFER_BACK) xfer = Py_None; else xfer = NULL; if (flags & FMT_AP_DEREF) iflgs |= SIP_NOT_NONE; if (flags & FMT_AP_TRANSFER_THIS) owner = va_arg(va, PyObject **); else owner = NULL; if (flags & FMT_AP_NO_CONVERTORS) { iflgs |= SIP_NO_CONVERTORS; statep = NULL; } else { statep = va_arg(va, int *); } if (sipTypeNeedsUserState(td)) user_statep = va_arg(va, void **); else user_statep = NULL; if (arg != NULL) { int iserr = FALSE; *p = sip_api_convert_to_type_us(arg, td, xfer, iflgs, statep, user_statep, &iserr); if (iserr) return FALSE; if (owner != NULL && *p != NULL) *owner = arg; } break; } case 'P': { /* Python object of any type with a sub-format. */ PyObject **p = va_arg(va, PyObject **); int flags = *fmt++ - '0'; if (arg != NULL) { if (flags & FMT_AP_TRANSFER) { Py_XINCREF(arg); } else if (flags & FMT_AP_TRANSFER_BACK) { Py_XDECREF(arg); } *p = arg; } break; } case 'X': { /* Constrained types. */ if (*fmt++ == 'E') va_arg(va, void *); va_arg(va, void *); break; } case 'A': { /* String from a Python string or None. */ PyObject **keep = va_arg(va, PyObject **); const char **p = va_arg(va, const char **); char sub_fmt = *fmt++; if (arg != NULL) { PyObject *s = NULL; switch (sub_fmt) { case 'A': s = parseString_AsASCIIString(arg, p); break; case 'L': s = parseString_AsLatin1String(arg, p); break; case '8': s = parseString_AsUTF8String(arg, p); break; } if (s == NULL) return FALSE; *keep = s; } break; } case 'a': { /* Character from a Python string. */ char *p = va_arg(va, char *); char sub_fmt = *fmt++; if (arg != NULL) { int enc; switch (sub_fmt) { case 'A': enc = parseString_AsASCIIChar(arg, p); break; case 'L': enc = parseString_AsLatin1Char(arg, p); break; case '8': enc = parseString_AsUTF8Char(arg, p); break; } if (enc < 0) return FALSE; } break; } /* * Every other argument is a pointer and only differ in how many there * are. */ case 'N': case 'T': case 'k': case 'K': case 'U': case 'E': va_arg(va, void *); /* Drop through. */ default: va_arg(va, void *); } } /* Handle any ellipsis argument. */ if (*fmt == 'W') { PyObject *al; int da = 0; /* Create a tuple for any remaining arguments. */ if ((al = PyTuple_New(nr_pos_args - a)) == NULL) return FALSE; while (a < nr_pos_args) { PyObject *arg = PyTuple_GET_ITEM(sipArgs, a); /* Add the remaining argument to the tuple. */ Py_INCREF(arg); PyTuple_SET_ITEM(al, da, arg); ++a; ++da; } /* Return the tuple. */ *va_arg(va, PyObject **) = al; } return TRUE; } /* * See if a Python object is a sequence of a particular type. */ static int canConvertFromSequence(PyObject *seq, const sipTypeDef *td) { Py_ssize_t i, size = PySequence_Size(seq); if (size < 0) return FALSE; /* * Check the type of each element. Note that this is inconsistent with how * similiar situations are handled elsewhere. We should instead just check * we have an iterator and assume (until the second pass) that the type is * correct. */ for (i = 0; i < size; ++i) { int ok; PyObject *val_obj; if ((val_obj = PySequence_GetItem(seq, i)) == NULL) return FALSE; ok = sip_api_can_convert_to_type(val_obj, td, SIP_NO_CONVERTORS|SIP_NOT_NONE); Py_DECREF(val_obj); if (!ok) return FALSE; } return TRUE; } /* * Convert a Python sequence to an array that has already "passed" * canConvertFromSequence(). Return TRUE if the conversion was successful. */ static int convertFromSequence(PyObject *seq, const sipTypeDef *td, void **array, Py_ssize_t *nr_elem) { int iserr = 0; Py_ssize_t i, size = PySequence_Size(seq); sipArrayFunc array_helper; sipAssignFunc assign_helper; void *array_mem; /* Get the type's helpers. */ if (sipTypeIsMapped(td)) { array_helper = ((const sipMappedTypeDef *)td)->mtd_array; assign_helper = ((const sipMappedTypeDef *)td)->mtd_assign; } else { array_helper = ((const sipClassTypeDef *)td)->ctd_array; assign_helper = ((const sipClassTypeDef *)td)->ctd_assign; } assert(array_helper != NULL); assert(assign_helper != NULL); /* * Create the memory for the array of values. Note that this will leak if * there is an error. */ array_mem = array_helper(size); for (i = 0; i < size; ++i) { PyObject *val_obj; void *val; if ((val_obj = PySequence_GetItem(seq, i)) == NULL) return FALSE; val = sip_api_convert_to_type_us(val_obj, td, NULL, SIP_NO_CONVERTORS|SIP_NOT_NONE, NULL, NULL, &iserr); Py_DECREF(val_obj); if (iserr) return FALSE; assign_helper(array_mem, i, val); } *array = array_mem; *nr_elem = size; return TRUE; } /* * Convert an array of a type to a Python sequence. */ static PyObject *convertToSequence(void *array, Py_ssize_t nr_elem, const sipTypeDef *td) { Py_ssize_t i; PyObject *seq; sipCopyFunc copy_helper; /* Get the type's copy helper. */ if (sipTypeIsMapped(td)) copy_helper = ((const sipMappedTypeDef *)td)->mtd_copy; else copy_helper = ((const sipClassTypeDef *)td)->ctd_copy; assert(copy_helper != NULL); if ((seq = PyTuple_New(nr_elem)) == NULL) return NULL; for (i = 0; i < nr_elem; ++i) { void *el = copy_helper(array, i); PyObject *el_obj = sip_api_convert_from_new_type(el, td, NULL); if (el_obj == NULL) { release(el, td, 0, NULL); Py_DECREF(seq); } PyTuple_SET_ITEM(seq, i, el_obj); } return seq; } /* * Perform housekeeping after a C++ instance has been destroyed. */ void sip_api_instance_destroyed(sipSimpleWrapper *sw) { sip_api_instance_destroyed_ex(&sw); } /* * Carry out actions common to all dtors. */ static void sip_api_instance_destroyed_ex(sipSimpleWrapper **sipSelfp) { /* If there is no interpreter just to the minimum and get out. */ if (sipInterpreter == NULL) { *sipSelfp = NULL; return; } SIP_BLOCK_THREADS sipSimpleWrapper *sipSelf = *sipSelfp; if (sipSelf != NULL) { PyObject *xtype, *xvalue, *xtb; /* We may be tidying up after an exception so preserve it. */ PyErr_Fetch(&xtype, &xvalue, &xtb); callPyDtor(sipSelf); PyErr_Restore(xtype, xvalue, xtb); sipOMRemoveObject(&cppPyMap, sipSelf); /* * This no longer points to anything useful. Actually it might do as * the partialy destroyed C++ instance may still be trying to invoke * reimplemented virtuals. */ clear_access_func(sipSelf); /* * If C/C++ has a reference (and therefore no parent) then remove it. * Otherwise remove the object from any parent. */ if (sipCppHasRef(sipSelf)) { sipResetCppHasRef(sipSelf); Py_DECREF(sipSelf); } else if (PyObject_TypeCheck((PyObject *)sipSelf, (PyTypeObject *)&sipWrapper_Type)) { removeFromParent((sipWrapper *)sipSelf); } /* * Normally this is done in the generated dealloc function. However * this is only called if the pointer/access function has not been * reset (which it has). It acts as a guard to prevent any further * invocations of reimplemented virtuals. */ *sipSelfp = NULL; } SIP_UNBLOCK_THREADS } /* * Clear any access function so that sip_api_get_address() will always return a * NULL pointer. */ static void clear_access_func(sipSimpleWrapper *sw) { if (sw->access_func != NULL) { sw->access_func(sw, ReleaseGuard); sw->access_func = NULL; } sw->data = NULL; } /* * Call self.__dtor__() if it is implemented. */ static void callPyDtor(sipSimpleWrapper *self) { sip_gilstate_t sipGILState; char pymc = 0; PyObject *meth; meth = sip_api_is_py_method_12_8(&sipGILState, &pymc, &self, NULL, "__dtor__"); if (meth != NULL) { PyObject *res = sip_api_call_method(0, meth, "", NULL); Py_DECREF(meth); /* Discard any result. */ Py_XDECREF(res); /* Handle any error the best we can. */ if (PyErr_Occurred()) PyErr_Print(); SIP_RELEASE_GIL(sipGILState); } } /* * Add a wrapper to it's parent owner. The wrapper must not currently have a * parent and, therefore, no siblings. */ static void addToParent(sipWrapper *self, sipWrapper *owner) { if (owner->first_child != NULL) { self->sibling_next = owner->first_child; owner->first_child->sibling_prev = self; } owner->first_child = self; self->parent = owner; /* * The owner holds a real reference so that the cyclic garbage collector * works properly. */ Py_INCREF((sipSimpleWrapper *)self); } /* * Remove a wrapper from it's parent if it has one. */ static void removeFromParent(sipWrapper *self) { if (self->parent != NULL) { if (self->parent->first_child == self) self->parent->first_child = self->sibling_next; if (self->sibling_next != NULL) self->sibling_next->sibling_prev = self->sibling_prev; if (self->sibling_prev != NULL) self->sibling_prev->sibling_next = self->sibling_next; self->parent = NULL; self->sibling_next = NULL; self->sibling_prev = NULL; /* * We must do this last, after all the pointers are correct, because * this is used by the clear slot. */ Py_DECREF((sipSimpleWrapper *)self); } } /* * Detach and children of a parent. */ static void detachChildren(sipWrapper *self) { while (self->first_child != NULL) removeFromParent(self->first_child); } /* * Convert a sequence index. Return the index or a negative value if there was * an error. */ static Py_ssize_t sip_api_convert_from_sequence_index(Py_ssize_t idx, Py_ssize_t len) { /* Negative indices start from the other end. */ if (idx < 0) idx = len + idx; if (idx < 0 || idx >= len) { PyErr_Format(PyExc_IndexError, "sequence index out of range"); return -1; } return idx; } /* * Return a tuple of the base class of a type that has no explicit super-type. */ static PyObject *getDefaultBase(void) { static PyObject *default_base = NULL; /* Only do this once. */ if (default_base == NULL) { if ((default_base = PyTuple_Pack(1, (PyObject *)&sipWrapper_Type)) == NULL) return NULL; } Py_INCREF(default_base); return default_base; } /* * Return a tuple of the base class of a simple type that has no explicit * super-type. */ static PyObject *getDefaultSimpleBase(void) { static PyObject *default_simple_base = NULL; /* Only do this once. */ if (default_simple_base == NULL) { if ((default_simple_base = PyTuple_Pack(1, (PyObject *)&sipSimpleWrapper_Type)) == NULL) return NULL; } Py_INCREF(default_simple_base); return default_simple_base; } /* * Return the dictionary of a type. */ static PyObject *getScopeDict(sipTypeDef *td, PyObject *mod_dict, sipExportedModuleDef *client) { /* * Initialise the scoping type if necessary. It will always be in the * same module if it needs doing. */ if (sipTypeIsMapped(td)) { if (createMappedType(client, (sipMappedTypeDef *)td, mod_dict) < 0) return NULL; /* Check that the mapped type can act as a container. */ assert(sipTypeAsPyTypeObject(td) != NULL); } else { if (createClassType(client, (sipClassTypeDef *)td, mod_dict) < 0) return NULL; } return (sipTypeAsPyTypeObject(td))->tp_dict; } /* * Create a container type and return a borrowed reference to it. */ static PyObject *createContainerType(sipContainerDef *cod, sipTypeDef *td, PyObject *bases, PyObject *metatype, PyObject *mod_dict, PyObject *type_dict, sipExportedModuleDef *client) { PyObject *py_type, *scope_dict, *name, *args; sipTypeDef *scope_td; /* Get the dictionary to place the type in. */ if (cod->cod_scope.sc_flag) { scope_td = NULL; scope_dict = mod_dict; } else { scope_td = getGeneratedType(&cod->cod_scope, client); scope_dict = getScopeDict(scope_td, mod_dict, client); if (scope_dict == NULL) goto reterr; } /* Create an object corresponding to the type name. */ if ((name = PyUnicode_FromString(sipPyNameOfContainer(cod, td))) == NULL) goto reterr; /* Create the type by calling the metatype. */ if ((args = PyTuple_Pack(3, name, bases, type_dict)) == NULL) goto relname; /* Pass the type via the back door. */ assert(currentType == NULL); currentType = td; py_type = PyObject_Call(metatype, args, NULL); currentType = NULL; if (py_type == NULL) goto relargs; /* Fix __qualname__ if there is a scope. */ if (scope_td != NULL) { PyHeapTypeObject *ht; PyObject *qualname = sip_get_qualname(scope_td, name); if (qualname == NULL) goto reltype; ht = (PyHeapTypeObject *)py_type; Py_CLEAR(ht->ht_qualname); ht->ht_qualname = qualname; } /* Add the type to the "parent" dictionary. */ if (PyDict_SetItem(scope_dict, name, py_type) < 0) goto reltype; Py_DECREF(args); Py_DECREF(name); return py_type; /* Unwind on error. */ reltype: Py_DECREF(py_type); relargs: Py_DECREF(args); relname: Py_DECREF(name); reterr: return NULL; } /* * Create a single class type object. */ static int createClassType(sipExportedModuleDef *client, sipClassTypeDef *ctd, PyObject *mod_dict) { PyObject *bases, *metatype, *py_type, *type_dict; sipEncodedTypeDef *sup; int i; /* Handle the trivial case where we have already been initialised. */ if (ctd->ctd_base.td_module != NULL) return 0; /* Set this up now to gain access to the string pool. */ ctd->ctd_base.td_module = client; /* Create the tuple of super-types. */ if ((sup = ctd->ctd_supers) == NULL) { if (ctd->ctd_supertype < 0) { bases = (sipTypeIsNamespace(&ctd->ctd_base) ? getDefaultSimpleBase() : getDefaultBase()); } else { PyObject *supertype; const char *supertype_name = sipNameFromPool(client, ctd->ctd_supertype); if ((supertype = findPyType(supertype_name)) == NULL) goto reterr; bases = PyTuple_Pack(1, supertype); } if (bases == NULL) goto reterr; } else { int nrsupers = 0; do ++nrsupers; while (!sup++->sc_flag); if ((bases = PyTuple_New(nrsupers)) == NULL) goto reterr; for (sup = ctd->ctd_supers, i = 0; i < nrsupers; ++i, ++sup) { PyObject *st; sipTypeDef *sup_td = getGeneratedType(sup, client); /* * Initialise the super-class if necessary. It will always be in * the same module if it needs doing. */ if (createClassType(client, (sipClassTypeDef *)sup_td, mod_dict) < 0) goto relbases; st = (PyObject *)sipTypeAsPyTypeObject(sup_td); Py_INCREF(st); PyTuple_SET_ITEM(bases, i, st); /* * Inherit any garbage collector code rather than look for it each * time it is needed. */ if (ctd->ctd_traverse == NULL) ctd->ctd_traverse = ((sipClassTypeDef *)sup_td)->ctd_traverse; if (ctd->ctd_clear == NULL) ctd->ctd_clear = ((sipClassTypeDef *)sup_td)->ctd_clear; } } /* * Use the explicit meta-type if there is one, otherwise use the meta-type * of the first super-type. */ if (ctd->ctd_metatype >= 0) { const char *metatype_name = sipNameFromPool(client, ctd->ctd_metatype); if ((metatype = findPyType(metatype_name)) == NULL) goto relbases; } else metatype = (PyObject *)Py_TYPE(PyTuple_GET_ITEM(bases, 0)); /* Create the type dictionary and populate it with any non-lazy methods. */ if ((type_dict = createTypeDict(client)) == NULL) goto relbases; if (sipTypeHasNonlazyMethod(&ctd->ctd_base)) { PyMethodDef *pmd = ctd->ctd_container.cod_methods; for (i = 0; i < ctd->ctd_container.cod_nrmethods; ++i) { if (isNonlazyMethod(pmd) && addMethod(type_dict, pmd) < 0) goto reldict; ++pmd; } } if ((py_type = createContainerType(&ctd->ctd_container, (sipTypeDef *)ctd, bases, metatype, mod_dict, type_dict, client)) == NULL) goto reldict; if (ctd->ctd_pyslots != NULL) fix_slots((PyTypeObject *)py_type, ctd->ctd_pyslots); /* Handle the pickle function. */ if (ctd->ctd_pickle != NULL) { static PyMethodDef md = { "_pickle_type", pickle_type, METH_NOARGS, NULL }; if (setReduce((PyTypeObject *)py_type, &md) < 0) goto reltype; } /* We can now release our references. */ Py_DECREF(bases); Py_DECREF(type_dict); return 0; /* Unwind after an error. */ reltype: Py_DECREF(py_type); reldict: Py_DECREF(type_dict); relbases: Py_DECREF(bases); reterr: ctd->ctd_base.td_module = NULL; return -1; } /* * Create a single mapped type object. */ static int createMappedType(sipExportedModuleDef *client, sipMappedTypeDef *mtd, PyObject *mod_dict) { PyObject *bases, *type_dict; /* Handle the trivial case where we have already been initialised. */ if (mtd->mtd_base.td_module != NULL) return 0; /* Set this up now to gain access to the string pool. */ mtd->mtd_base.td_module = client; /* Create the tuple of super-types. */ if ((bases = getDefaultBase()) == NULL) goto reterr; /* Create the type dictionary. */ if ((type_dict = createTypeDict(client)) == NULL) goto relbases; if (createContainerType(&mtd->mtd_container, (sipTypeDef *)mtd, bases, (PyObject *)&sipWrapperType_Type, mod_dict, type_dict, client) == NULL) goto reldict; /* We can now release our references. */ Py_DECREF(bases); Py_DECREF(type_dict); return 0; /* Unwind after an error. */ reldict: Py_DECREF(type_dict); relbases: Py_DECREF(bases); reterr: mtd->mtd_base.td_module = NULL; return -1; } /* * Return the module definition for a named module. */ static sipExportedModuleDef *getModule(PyObject *mname_obj) { PyObject *mod; sipExportedModuleDef *em; /* Make sure the module is imported. */ if ((mod = PyImport_Import(mname_obj)) == NULL) return NULL; /* Find the module definition. */ for (em = moduleList; em != NULL; em = em->em_next) if (PyUnicode_Compare(mname_obj, em->em_nameobj) == 0) break; Py_DECREF(mod); if (em == NULL) PyErr_Format(PyExc_SystemError, "unable to find to find module: %U", mname_obj); return em; } /* * The type unpickler. */ static PyObject *unpickle_type(PyObject *obj, PyObject *args) { PyObject *mname_obj, *init_args; const char *tname; sipExportedModuleDef *em; int i; (void)obj; if (!PyArg_ParseTuple(args, "UsO!:_unpickle_type", &mname_obj, &tname, &PyTuple_Type, &init_args)) return NULL; /* Get the module definition. */ if ((em = getModule(mname_obj)) == NULL) return NULL; /* Find the class type object. */ for (i = 0; i < em->em_nrtypes; ++i) { sipTypeDef *td = em->em_types[i]; if (td != NULL && !sipTypeIsStub(td) && sipTypeIsClass(td)) { const char *pyname = sipPyNameOfContainer( &((sipClassTypeDef *)td)->ctd_container, td); if (strcmp(pyname, tname) == 0) return PyObject_CallObject((PyObject *)sipTypeAsPyTypeObject(td), init_args); } } PyErr_Format(PyExc_SystemError, "unable to find to find type: %s", tname); return NULL; } /* * The type pickler. */ static PyObject *pickle_type(PyObject *obj, PyObject *args) { sipExportedModuleDef *em; (void)args; /* Find the type definition and defining module. */ for (em = moduleList; em != NULL; em = em->em_next) { int i; for (i = 0; i < em->em_nrtypes; ++i) { sipTypeDef *td = em->em_types[i]; if (td != NULL && !sipTypeIsStub(td) && sipTypeIsClass(td)) if (sipTypeAsPyTypeObject(td) == Py_TYPE(obj)) { PyObject *init_args; sipClassTypeDef *ctd = (sipClassTypeDef *)td; const char *pyname = sipPyNameOfContainer(&ctd->ctd_container, td); /* * Ask the handwritten pickle code for the tuple of * arguments that will recreate the object. */ init_args = ctd->ctd_pickle(sip_api_get_cpp_ptr((sipSimpleWrapper *)obj, NULL)); if (init_args == NULL) return NULL; if (!PyTuple_Check(init_args)) { PyErr_Format(PyExc_TypeError, "%%PickleCode for type %s.%s did not return a tuple", sipNameOfModule(em), pyname); return NULL; } return Py_BuildValue("O(OsN)", type_unpickler, em->em_nameobj, pyname, init_args); } } } /* We should never get here. */ PyErr_Format(PyExc_SystemError, "attempt to pickle unknown type '%s'", Py_TYPE(obj)->tp_name); return NULL; } /* * Set the __reduce__method for a type. */ static int setReduce(PyTypeObject *type, PyMethodDef *pickler) { static PyObject *rstr = NULL; PyObject *descr; int rc; if (sip_objectify("__reduce__", &rstr) < 0) return -1; /* Create the method descripter. */ if ((descr = PyDescr_NewMethod(type, pickler)) == NULL) return -1; /* * Save the method. Note that we don't use PyObject_SetAttr() as we want * to bypass any lazy attribute loading (which may not be safe yet). */ rc = PyType_Type.tp_setattro((PyObject *)type, rstr, descr); Py_DECREF(descr); return rc; } /* * Create a type dictionary for dynamic type being created in a module. */ static PyObject *createTypeDict(sipExportedModuleDef *em) { static PyObject *mstr = NULL; PyObject *dict; if (sip_objectify("__module__", &mstr) < 0) return NULL; /* Create the dictionary. */ if ((dict = PyDict_New()) == NULL) return NULL; /* We need to set the module name as an attribute for dynamic types. */ if (PyDict_SetItem(dict, mstr, em->em_nameobj) < 0) { Py_DECREF(dict); return NULL; } return dict; } /* * Convert an ASCII string to a Python object if it hasn't already been done. */ int sip_objectify(const char *s, PyObject **objp) { if (*objp == NULL) if ((*objp = PyUnicode_FromString(s)) == NULL) return -1; return 0; } /* * Add a set of static instances to a dictionary. Note that ints are handled * separately. */ static int addInstances(PyObject *dict, sipInstancesDef *id) { if (id->id_type != NULL && addTypeInstances(dict, id->id_type) < 0) return -1; if (id->id_voidp != NULL && addVoidPtrInstances(dict,id->id_voidp) < 0) return -1; if (id->id_char != NULL && addCharInstances(dict,id->id_char) < 0) return -1; if (id->id_string != NULL && addStringInstances(dict,id->id_string) < 0) return -1; if (id->id_long != NULL && addLongInstances(dict,id->id_long) < 0) return -1; if (id->id_ulong != NULL && addUnsignedLongInstances(dict, id->id_ulong) < 0) return -1; if (id->id_llong != NULL && addLongLongInstances(dict, id->id_llong) < 0) return -1; if (id->id_ullong != NULL && addUnsignedLongLongInstances(dict, id->id_ullong) < 0) return -1; if (id->id_double != NULL && addDoubleInstances(dict,id->id_double) < 0) return -1; return 0; } /* * Get "self" from the argument tuple for a method called as * Class.Method(self, ...) rather than self.Method(...). */ static int getSelfFromArgs(sipTypeDef *td, PyObject *args, int argnr, PyObject **selfp) { PyObject *self; /* Get self from the argument tuple. */ if (argnr >= PyTuple_GET_SIZE(args)) return FALSE; self = PyTuple_GET_ITEM(args, argnr); if (!PyObject_TypeCheck(self, sipTypeAsPyTypeObject(td))) return FALSE; *selfp = self; return TRUE; } /* * Return non-zero if a method is non-lazy, ie. it must be added to the type * when it is created. */ static int isNonlazyMethod(PyMethodDef *pmd) { static const char *lazy[] = { "__getattribute__", "__getattr__", "__enter__", "__exit__", "__aenter__", "__aexit__", NULL }; const char **l; for (l = lazy; *l != NULL; ++l) if (strcmp(pmd->ml_name, *l) == 0) return TRUE; return FALSE; } /* * Add a method to a dictionary. */ static int addMethod(PyObject *dict, PyMethodDef *pmd) { PyObject *descr = sipMethodDescr_New(pmd); return sip_dict_set_and_discard(dict, pmd->ml_name, descr); } /* * Populate a container's type dictionary. */ static int add_lazy_container_attrs(const sipTypeDef *td, sipContainerDef *cod, PyObject *dict) { int i; PyMethodDef *pmd; sipIntInstanceDef *next_int; sipVariableDef *vd; /* Do the methods. */ for (pmd = cod->cod_methods, i = 0; i < cod->cod_nrmethods; ++i, ++pmd) { /* Non-lazy methods will already have been handled. */ if (!sipTypeHasNonlazyMethod(td) || !isNonlazyMethod(pmd)) { if (addMethod(dict, pmd) < 0) return -1; } } /* Do the enums. */ next_int = cod->cod_instances.id_int; if (next_int != NULL) { sipExportedModuleDef *module = td->td_module; /* * Not ideal but we have to look through all types looking for enums * for which this container is the enclosing scope. */ for (i = 0; i < module->em_nrtypes; ++i) { sipTypeDef *enum_td = module->em_types[i]; if (enum_td != NULL && sipTypeIsEnum(enum_td)) { sipEnumTypeDef *etd = (sipEnumTypeDef *)enum_td; if (module->em_types[etd->etd_scope] == td) if (sip_enum_create(module, etd, &next_int, dict) < 0) return -1; } } /* Do any remaining ints. */ if (addIntInstances(dict, next_int) < 0) return -1; } /* Any non-int instances. */ if (addInstances(dict, &cod->cod_instances) < 0) return -1; /* Do the variables. */ for (vd = cod->cod_variables, i = 0; i < cod->cod_nrvariables; ++i, ++vd) { PyObject *descr; if (vd->vd_type == PropertyVariable) descr = create_property(vd); else descr = sipVariableDescr_New(vd, td, cod); if (sip_dict_set_and_discard(dict, vd->vd_name, descr) < 0) return -1; } return 0; } /* * Create a Python property object from the SIP generated structure. */ static PyObject *create_property(sipVariableDef *vd) { PyObject *descr, *fget, *fset, *fdel, *doc; descr = fget = fset = fdel = doc = NULL; if ((fget = create_function(vd->vd_getter)) == NULL) goto done; if ((fset = create_function(vd->vd_setter)) == NULL) goto done; if ((fdel = create_function(vd->vd_deleter)) == NULL) goto done; if (vd->vd_docstring == NULL) { doc = Py_None; Py_INCREF(doc); } else if ((doc = PyUnicode_FromString(vd->vd_docstring)) == NULL) { goto done; } descr = PyObject_CallFunctionObjArgs((PyObject *)&PyProperty_Type, fget, fset, fdel, doc, NULL); done: Py_XDECREF(fget); Py_XDECREF(fset); Py_XDECREF(fdel); Py_XDECREF(doc); return descr; } /* * Return a PyCFunction as an object or Py_None if there isn't one. */ static PyObject *create_function(PyMethodDef *ml) { if (ml != NULL) return PyCFunction_New(ml, NULL); Py_INCREF(Py_None); return Py_None; } /* * Populate a type dictionary with all lazy attributes if it hasn't already * been done. */ static int add_lazy_attrs(const sipTypeDef *td) { sipWrapperType *wt = (sipWrapperType *)sipTypeAsPyTypeObject(td); PyObject *dict; sipAttrGetter *ag; /* Handle the trivial case. */ if (wt->wt_dict_complete) return 0; dict = ((PyTypeObject *)wt)->tp_dict; if (sipTypeIsMapped(td)) { if (add_lazy_container_attrs(td, &((sipMappedTypeDef *)td)->mtd_container, dict) < 0) return -1; } else { sipClassTypeDef *nsx; /* Search the possible linked list of namespace extenders. */ for (nsx = (sipClassTypeDef *)td; nsx != NULL; nsx = nsx->ctd_nsextender) if (add_lazy_container_attrs((sipTypeDef *)nsx, &nsx->ctd_container, dict) < 0) return -1; } /* * Get any lazy attributes from registered getters. This must be done last * to allow any existing attributes to be replaced. */ /* * TODO: Deprecate this mechanism in favour of an event handler, or should * be be embedded code using a new directive? */ for (ag = sipAttrGetters; ag != NULL; ag = ag->next) if (ag->type == NULL || PyType_IsSubtype((PyTypeObject *)wt, ag->type)) if (ag->getter(td, dict) < 0) return -1; wt->wt_dict_complete = TRUE; PyType_Modified((PyTypeObject *)wt); return 0; } /* * Populate the type dictionary and all its super-types. */ int sip_add_all_lazy_attrs(const sipTypeDef *td) { if (td == NULL) return 0; if (add_lazy_attrs(td) < 0) return -1; if (sipTypeIsClass(td)) { sipClassTypeDef *ctd = (sipClassTypeDef *)td; sipEncodedTypeDef *sup; if ((sup = ctd->ctd_supers) != NULL) do { const sipTypeDef *sup_td = getGeneratedType(sup, td->td_module); if (sip_add_all_lazy_attrs(sup_td) < 0) return -1; } while (!sup++->sc_flag); } return 0; } /* * Return the generated type structure corresponding to the given Python type * object. */ static const sipTypeDef *sip_api_type_from_py_type_object(PyTypeObject *py_type) { if (PyObject_TypeCheck((PyObject *)py_type, &sipWrapperType_Type)) return ((sipWrapperType *)py_type)->wt_td; return sip_enum_get_generated_type((PyObject *)py_type); } /* * Return the generated type structure corresponding to the scope of the given * type. */ const sipTypeDef *sip_api_type_scope(const sipTypeDef *td) { if (sipTypeIsEnum(td)) { const sipEnumTypeDef *etd = (const sipEnumTypeDef *)td; if (etd->etd_scope >= 0) return td->td_module->em_types[etd->etd_scope]; } else { const sipContainerDef *cod; if (sipTypeIsMapped(td)) cod = &((const sipMappedTypeDef *)td)->mtd_container; else cod = &((const sipClassTypeDef *)td)->ctd_container; if (!cod->cod_scope.sc_flag) return getGeneratedType(&cod->cod_scope, td->td_module); } return NULL; } /* * Register a getter for unknown attributes. */ static int sip_api_register_attribute_getter(const sipTypeDef *td, sipAttrGetterFunc getter) { sipAttrGetter *ag = sip_api_malloc(sizeof (sipAttrGetter)); if (ag == NULL) return -1; ag->type = sipTypeAsPyTypeObject(td); ag->getter = getter; ag->next = sipAttrGetters; sipAttrGetters = ag; return 0; } /* * Register a proxy resolver. */ static int sip_api_register_proxy_resolver(const sipTypeDef *td, sipProxyResolverFunc resolver) { sipProxyResolver *pr = sip_api_malloc(sizeof (sipProxyResolver)); if (pr == NULL) return -1; pr->td = td; pr->resolver = resolver; pr->next = proxyResolvers; proxyResolvers = pr; return 0; } /* * Report a function with invalid argument types. */ static void sip_api_no_function(PyObject *parseErr, const char *func, const char *doc) { sip_api_no_method(parseErr, NULL, func, doc); } /* * Report a method/function/signal with invalid argument types. */ static void sip_api_no_method(PyObject *parseErr, const char *scope, const char *method, const char *doc) { const char *sep = "."; if (scope == NULL) scope = ++sep; if (parseErr == NULL) { /* * If we have got this far without trying a parse then there must be no * overloads. */ PyErr_Format(PyExc_TypeError, "%s%s%s() is a private method", scope, sep, method); } else if (PyList_Check(parseErr)) { PyObject *exc; /* There is an entry for each overload that was tried. */ if (PyList_GET_SIZE(parseErr) == 1) { PyObject *detail = detail_FromFailure( PyList_GET_ITEM(parseErr, 0)); if (detail != NULL) { if (doc != NULL) { PyObject *doc_obj = signature_FromDocstring(doc, 0); if (doc_obj != NULL) { exc = PyUnicode_FromFormat("%U: %U", doc_obj, detail); Py_DECREF(doc_obj); } else { exc = NULL; } } else { exc = PyUnicode_FromFormat("%s%s%s(): %U", scope, sep, method, detail); } Py_DECREF(detail); } else { exc = NULL; } } else { static const char *summary = "arguments did not match any overloaded call:"; Py_ssize_t i; if (doc != NULL) exc = PyUnicode_FromString(summary); else exc = PyUnicode_FromFormat("%s%s%s(): %s", scope, sep, method, summary); for (i = 0; i < PyList_GET_SIZE(parseErr); ++i) { PyObject *failure; PyObject *detail = detail_FromFailure( PyList_GET_ITEM(parseErr, i)); if (detail != NULL) { if (doc != NULL) { PyObject *doc_obj = signature_FromDocstring(doc, i); if (doc_obj != NULL) { failure = PyUnicode_FromFormat("\n %U: %U", doc_obj, detail); Py_DECREF(doc_obj); } else { Py_XDECREF(exc); exc = NULL; break; } } else { failure = PyUnicode_FromFormat("\n overload %zd: %U", i + 1, detail); } Py_DECREF(detail); PyUnicode_AppendAndDel(&exc, failure); } else { Py_XDECREF(exc); exc = NULL; break; } } } if (exc != NULL) { PyErr_SetObject(PyExc_TypeError, exc); Py_DECREF(exc); } } else { /* * None is used as a marker to say that an exception has already been * raised. */ assert(parseErr == Py_None); } Py_XDECREF(parseErr); } /* * Return a string/unicode object extracted from a particular line of a * docstring. */ static PyObject *signature_FromDocstring(const char *doc, Py_ssize_t line) { const char *eol; Py_ssize_t size = 0; /* * Find the start of the line. If there is a non-default versioned * overload that has been enabled then it won't have an entry in the * docstring. This means that the returned signature may be incorrect. */ while (line-- > 0) { const char *next = strchr(doc, '\n'); if (next == NULL) break; doc = next + 1; } /* Find the last closing parenthesis. */ for (eol = doc; *eol != '\n' && *eol != '\0'; ++eol) if (*eol == ')') size = eol - doc + 1; return PyUnicode_FromStringAndSize(doc, size); } /* * Return a string/unicode object that describes the given failure. */ static PyObject *detail_FromFailure(PyObject *failure_obj) { sipParseFailure *failure; PyObject *detail; failure = (sipParseFailure *)PyCapsule_GetPointer(failure_obj, NULL); switch (failure->reason) { case Unbound: detail = PyUnicode_FromFormat( "first argument of unbound method must have type '%s'", failure->detail_str); break; case TooFew: detail = PyUnicode_FromString("not enough arguments"); break; case TooMany: detail = PyUnicode_FromString("too many arguments"); break; case KeywordNotString: detail = PyUnicode_FromFormat( "%S keyword argument name is not a string", failure->detail_obj); break; case UnknownKeyword: detail = PyUnicode_FromFormat("'%U' is not a valid keyword argument", failure->detail_obj); break; case Duplicate: detail = PyUnicode_FromFormat( "'%U' has already been given as a positional argument", failure->detail_obj); break; case WrongType: if (failure->arg_nr >= 0) detail = bad_type_str(failure->arg_nr, failure->detail_obj); else detail = PyUnicode_FromFormat( "argument '%s' has unexpected type '%s'", failure->arg_name, Py_TYPE(failure->detail_obj)->tp_name); break; case Exception: detail = failure->detail_obj; if (detail) { Py_INCREF(detail); break; } /* Drop through. */ default: detail = PyUnicode_FromString("unknown reason"); } return detail; } /* * Report an abstract method called with an unbound self. */ static void sip_api_abstract_method(const char *classname, const char *method) { PyErr_Format(PyExc_TypeError, "%s.%s() is abstract and cannot be called as an unbound method", classname, method); } /* * Report a deprecated class or method. */ int sip_api_deprecated(const char *classname, const char *method) { char buf[100]; if (classname == NULL) PyOS_snprintf(buf, sizeof (buf), "%s() is deprecated", method); else if (method == NULL) PyOS_snprintf(buf, sizeof (buf), "%s constructor is deprecated", classname); else PyOS_snprintf(buf, sizeof (buf), "%s.%s() is deprecated", classname, method); return PyErr_WarnEx(PyExc_DeprecationWarning, buf, 1); } /* * Report a bad operator argument. Only a small subset of operators need to * be handled (those that don't return Py_NotImplemented). */ static void sip_api_bad_operator_arg(PyObject *self, PyObject *arg, sipPySlotType st) { const char *sn = NULL; /* Try and get the text to match a Python exception. */ switch (st) { case concat_slot: case iconcat_slot: PyErr_Format(PyExc_TypeError, "cannot concatenate '%s' and '%s' objects", Py_TYPE(self)->tp_name, Py_TYPE(arg)->tp_name); break; case repeat_slot: sn = "*"; break; case irepeat_slot: sn = "*="; break; default: sn = "unknown"; } if (sn != NULL) PyErr_Format(PyExc_TypeError, "unsupported operand type(s) for %s: '%s' and '%s'", sn, Py_TYPE(self)->tp_name, Py_TYPE(arg)->tp_name); } /* * Report a sequence length that does not match the length of a slice. */ static void sip_api_bad_length_for_slice(Py_ssize_t seqlen, Py_ssize_t slicelen) { PyErr_Format(PyExc_ValueError, "attempt to assign sequence of size %zd to slice of size %zd", seqlen, slicelen); } /* * Report a Python object that cannot be converted to a particular class. */ static void sip_api_bad_class(const char *classname) { PyErr_Format(PyExc_TypeError, "cannot convert Python object to an instance of %s", classname); } /* * Report a Python member function with an unexpected result. */ static void sip_api_bad_catcher_result(PyObject *method) { PyObject *mname, *etype, *evalue, *etraceback; /* * Get the current exception object if there is one. Its string * representation will be used as the detail of a new exception. */ PyErr_Fetch(&etype, &evalue, &etraceback); PyErr_NormalizeException(&etype, &evalue, &etraceback); Py_XDECREF(etraceback); /* * This is part of the public API so we make no assumptions about the * method object. */ if (!PyMethod_Check(method) || PyMethod_GET_FUNCTION(method) == NULL || !PyFunction_Check(PyMethod_GET_FUNCTION(method)) || PyMethod_GET_SELF(method) == NULL) { PyErr_Format(PyExc_TypeError, "invalid argument to sipBadCatcherResult()"); return; } mname = ((PyFunctionObject *)PyMethod_GET_FUNCTION(method))->func_name; if (evalue != NULL) { PyErr_Format(etype, "invalid result from %s.%U(), %S", Py_TYPE(PyMethod_GET_SELF(method))->tp_name, mname, evalue); Py_DECREF(evalue); } else { PyErr_Format(PyExc_TypeError, "invalid result from %s.%U()", Py_TYPE(PyMethod_GET_SELF(method))->tp_name, mname); } Py_XDECREF(etype); } /* * Transfer ownership of a class instance to Python from C/C++. */ static void sip_api_transfer_back(PyObject *self) { if (self != NULL && PyObject_TypeCheck(self, (PyTypeObject *)&sipWrapper_Type)) { sipSimpleWrapper *sw = (sipSimpleWrapper *)self; if (sipCppHasRef(sw)) { sipResetCppHasRef(sw); Py_DECREF(sw); } else { removeFromParent((sipWrapper *)sw); } sipSetPyOwned(sw); } } /* * Transfer ownership of a class instance to C/C++ from Python. */ static void sip_api_transfer_to(PyObject *self, PyObject *owner) { /* * There is a legitimate case where we try to transfer a PyObject that * may not be a SIP generated class. The virtual handler code calls * this function to keep the C/C++ instance alive when it gets rid of * the Python object returned by the Python method. A class may have * handwritten code that converts a regular Python type - so we can't * assume that we can simply cast to sipWrapper. */ if (self != NULL && PyObject_TypeCheck(self, (PyTypeObject *)&sipWrapper_Type)) { sipSimpleWrapper *sw = (sipSimpleWrapper *)self; if (owner == NULL) { /* There is no owner. */ if (sipCppHasRef(sw)) { sipResetCppHasRef(sw); } else { Py_INCREF(sw); removeFromParent((sipWrapper *)sw); sipResetPyOwned(sw); } Py_DECREF(sw); } else if (owner == Py_None) { /* * The owner is a C++ instance and not a Python object (ie. there * is no parent) so there is an explicit extra reference to keep * this Python object alive. Note that there is no way to * specify this from a .sip file - it is useful when embedding in * C/C++ applications. */ if (!sipCppHasRef(sw)) { Py_INCREF(sw); removeFromParent((sipWrapper *)sw); sipResetPyOwned(sw); sipSetCppHasRef(sw); } } else if (PyObject_TypeCheck(owner, (PyTypeObject *)&sipWrapper_Type)) { /* * The owner is a Python object (ie. the C++ instance that the * Python object wraps). */ if (sipCppHasRef(sw)) { sipResetCppHasRef(sw); } else { Py_INCREF(sw); removeFromParent((sipWrapper *)sw); sipResetPyOwned(sw); } addToParent((sipWrapper *)sw, (sipWrapper *)owner); Py_DECREF(sw); } } } /* * Add a license to a dictionary. */ static int addLicense(PyObject *dict,sipLicenseDef *lc) { int rc; PyObject *ldict, *proxy, *o; /* Convert the strings we use to objects if not already done. */ if (sip_objectify("__license__", &licenseName) < 0) return -1; if (sip_objectify("Licensee", &licenseeName) < 0) return -1; if (sip_objectify("Type", &typeName) < 0) return -1; if (sip_objectify("Timestamp", ×tampName) < 0) return -1; if (sip_objectify("Signature", &signatureName) < 0) return -1; /* We use a dictionary to hold the license information. */ if ((ldict = PyDict_New()) == NULL) return -1; /* The license type is compulsory, the rest are optional. */ if (lc->lc_type == NULL) goto deldict; if ((o = PyUnicode_FromString(lc->lc_type)) == NULL) goto deldict; rc = PyDict_SetItem(ldict,typeName,o); Py_DECREF(o); if (rc < 0) goto deldict; if (lc->lc_licensee != NULL) { if ((o = PyUnicode_FromString(lc->lc_licensee)) == NULL) goto deldict; rc = PyDict_SetItem(ldict,licenseeName,o); Py_DECREF(o); if (rc < 0) goto deldict; } if (lc->lc_timestamp != NULL) { if ((o = PyUnicode_FromString(lc->lc_timestamp)) == NULL) goto deldict; rc = PyDict_SetItem(ldict,timestampName,o); Py_DECREF(o); if (rc < 0) goto deldict; } if (lc->lc_signature != NULL) { if ((o = PyUnicode_FromString(lc->lc_signature)) == NULL) goto deldict; rc = PyDict_SetItem(ldict,signatureName,o); Py_DECREF(o); if (rc < 0) goto deldict; } /* Create a read-only proxy. */ if ((proxy = PyDictProxy_New(ldict)) == NULL) goto deldict; Py_DECREF(ldict); rc = PyDict_SetItem(dict, licenseName, proxy); Py_DECREF(proxy); return rc; deldict: Py_DECREF(ldict); return -1; } /* * Add the void pointer instances to a dictionary. */ static int addVoidPtrInstances(PyObject *dict,sipVoidPtrInstanceDef *vi) { while (vi->vi_name != NULL) { PyObject *w = sip_api_convert_from_void_ptr(vi->vi_val); if (sip_dict_set_and_discard(dict, vi->vi_name, w) < 0) return -1; ++vi; } return 0; } /* * Add the char instances to a dictionary. */ static int addCharInstances(PyObject *dict, sipCharInstanceDef *ci) { while (ci->ci_name != NULL) { PyObject *w; switch (ci->ci_encoding) { case 'A': w = PyUnicode_DecodeASCII(&ci->ci_val, 1, NULL); break; case 'L': w = PyUnicode_DecodeLatin1(&ci->ci_val, 1, NULL); break; case '8': w = PyUnicode_FromStringAndSize(&ci->ci_val, 1); break; default: w = PyBytes_FromStringAndSize(&ci->ci_val, 1); } if (sip_dict_set_and_discard(dict, ci->ci_name, w) < 0) return -1; ++ci; } return 0; } /* * Add the string instances to a dictionary. */ static int addStringInstances(PyObject *dict, sipStringInstanceDef *si) { while (si->si_name != NULL) { PyObject *w; switch (si->si_encoding) { case 'A': w = PyUnicode_DecodeASCII(si->si_val, strlen(si->si_val), NULL); break; case 'L': w = PyUnicode_DecodeLatin1(si->si_val, strlen(si->si_val), NULL); break; case '8': w = PyUnicode_FromString(si->si_val); break; case 'w': /* The hack for wchar_t. */ #if defined(HAVE_WCHAR_H) w = PyUnicode_FromWideChar((const wchar_t *)si->si_val, 1); break; #else raiseNoWChar(); return -1; #endif case 'W': /* The hack for wchar_t*. */ #if defined(HAVE_WCHAR_H) w = PyUnicode_FromWideChar((const wchar_t *)si->si_val, wcslen((const wchar_t *)si->si_val)); break; #else raiseNoWChar(); return -1; #endif default: w = PyBytes_FromString(si->si_val); } if (sip_dict_set_and_discard(dict, si->si_name, w) < 0) return -1; ++si; } return 0; } /* * Add the int instances to a dictionary. */ static int addIntInstances(PyObject *dict, sipIntInstanceDef *ii) { while (ii->ii_name != NULL) { PyObject *w = PyLong_FromLong(ii->ii_val); if (sip_dict_set_and_discard(dict, ii->ii_name, w) < 0) return -1; ++ii; } return 0; } /* * Add the long instances to a dictionary. */ static int addLongInstances(PyObject *dict,sipLongInstanceDef *li) { while (li->li_name != NULL) { PyObject *w = PyLong_FromLong(li->li_val); if (sip_dict_set_and_discard(dict, li->li_name, w) < 0) return -1; ++li; } return 0; } /* * Add the unsigned long instances to a dictionary. */ static int addUnsignedLongInstances(PyObject *dict, sipUnsignedLongInstanceDef *uli) { while (uli->uli_name != NULL) { PyObject *w = PyLong_FromUnsignedLong(uli->uli_val); if (sip_dict_set_and_discard(dict, uli->uli_name, w) < 0) return -1; ++uli; } return 0; } /* * Add the long long instances to a dictionary. */ static int addLongLongInstances(PyObject *dict, sipLongLongInstanceDef *lli) { while (lli->lli_name != NULL) { PyObject *w = PyLong_FromLongLong(lli->lli_val); if (sip_dict_set_and_discard(dict, lli->lli_name, w) < 0) return -1; ++lli; } return 0; } /* * Add the unsigned long long instances to a dictionary. */ static int addUnsignedLongLongInstances(PyObject *dict, sipUnsignedLongLongInstanceDef *ulli) { while (ulli->ulli_name != NULL) { PyObject *w = PyLong_FromUnsignedLongLong(ulli->ulli_val); if (sip_dict_set_and_discard(dict, ulli->ulli_name, w) < 0) return -1; ++ulli; } return 0; } /* * Add the double instances to a dictionary. */ static int addDoubleInstances(PyObject *dict,sipDoubleInstanceDef *di) { while (di->di_name != NULL) { PyObject *w = PyFloat_FromDouble(di->di_val); if (sip_dict_set_and_discard(dict, di->di_name, w) < 0) return -1; ++di; } return 0; } /* * Wrap a set of type instances and add them to a dictionary. */ static int addTypeInstances(PyObject *dict, sipTypeInstanceDef *ti) { while (ti->ti_name != NULL) { if (addSingleTypeInstance(dict, ti->ti_name, ti->ti_ptr, *ti->ti_type, ti->ti_flags) < 0) return -1; ++ti; } return 0; } /* * Wrap a single type instance and add it to a dictionary. */ static int addSingleTypeInstance(PyObject *dict, const char *name, void *cppPtr, const sipTypeDef *td, int initflags) { PyObject *obj; if (sipTypeIsEnum(td)) { obj = sip_api_convert_from_enum(*(int *)cppPtr, td); } else { sipConvertFromFunc cfrom; cppPtr = resolve_proxy(td, cppPtr); cfrom = get_from_convertor(td); if (cfrom != NULL) { obj = cfrom(cppPtr, NULL); } else if (sipTypeIsMapped(td)) { raise_no_convert_from(td); return -1; } else { obj = wrap_simple_instance(cppPtr, td, NULL, initflags); } } return sip_dict_set_and_discard(dict, name, obj); } /* * Convert a type instance and add it to a dictionary. */ static int sip_api_add_type_instance(PyObject *dict, const char *name, void *cppPtr, const sipTypeDef *td) { return addSingleTypeInstance(getDictFromObject(dict), name, cppPtr, td, 0); } /* * Return the instance dictionary for an object if it is a wrapped type. * Otherwise assume that it is a module dictionary. */ static PyObject *getDictFromObject(PyObject *obj) { if (PyObject_TypeCheck(obj, (PyTypeObject *)&sipWrapperType_Type)) obj = ((PyTypeObject *)obj)->tp_dict; return obj; } /* * Return a Python reimplementation corresponding to a C/C++ virtual function, * if any. If one was found then the GIL is acquired. This is deprecated, use * sip_api_is_python_method_12_8() instead. */ static PyObject *sip_api_is_py_method(sip_gilstate_t *gil, char *pymc, sipSimpleWrapper *sipSelf, const char *cname, const char *mname) { return sip_api_is_py_method_12_8(gil, pymc, &sipSelf, cname, mname); } /* * Return a Python reimplementation corresponding to a C/C++ virtual function, * if any. If one was found then the GIL is acquired. */ static PyObject *sip_api_is_py_method_12_8(sip_gilstate_t *gil, char *pymc, sipSimpleWrapper **sipSelfp, const char *cname, const char *mname) { sipSimpleWrapper *sipSelf; PyObject *mname_obj, *reimp, *mro, *cls; Py_ssize_t i; /* * This is the most common case (where there is no Python reimplementation) * so we take a fast shortcut without acquiring the GIL. */ if (*pymc != 0) return NULL; /* We might still have C++ going after the interpreter has gone. */ if (sipInterpreter == NULL) return NULL; #ifdef WITH_THREAD *gil = PyGILState_Ensure(); #endif /* Only read this when we have the GIL. */ sipSelf = *sipSelfp; /* * It's possible that the Python object has been deleted but the underlying * C++ instance is still working and trying to handle virtual functions. * Alternatively, an instance has started handling virtual functions before * its ctor has returned. In either case say there is no Python * reimplementation. */ if (sipSelf != NULL) sipSelf = deref_mixin(sipSelf); if (sipSelf == NULL) goto release_gil; /* * It's possible that the object's type's tp_mro is NULL. A possible * circumstance is when a type has been created dynamically and the only * reference to it is the single instance of the type which is in the * process of being garbage collected. */ cls = (PyObject *)Py_TYPE(sipSelf); mro = ((PyTypeObject *)cls)->tp_mro; if (mro == NULL) goto release_gil; /* Get any reimplementation. */ if ((mname_obj = PyUnicode_FromString(mname)) == NULL) goto release_gil; /* * We don't use PyObject_GetAttr() because that might find the generated * C function before a reimplementation defined in a mixin (ie. later in * the MRO). However that means we must explicitly check that the class * hierarchy is fully initialised. */ if (sip_add_all_lazy_attrs(((sipWrapperType *)Py_TYPE(sipSelf))->wt_td) < 0) { Py_DECREF(mname_obj); goto release_gil; } if (sipSelf->dict != NULL) { /* Check the instance dictionary in case it has been monkey patched. */ if ((reimp = PyDict_GetItem(sipSelf->dict, mname_obj)) != NULL && PyCallable_Check(reimp)) { Py_DECREF(mname_obj); Py_INCREF(reimp); return reimp; } } assert(PyTuple_Check(mro)); reimp = NULL; for (i = 0; i < PyTuple_GET_SIZE(mro); ++i) { PyObject *cls_dict, *cls_attr; cls = PyTuple_GET_ITEM(mro, i); cls_dict = ((PyTypeObject *)cls)->tp_dict; /* * Check any possible reimplementation is not the wrapped C++ method or * a default special method implementation. */ if (cls_dict != NULL && (cls_attr = PyDict_GetItem(cls_dict, mname_obj)) != NULL && Py_TYPE(cls_attr) != &sipMethodDescr_Type && Py_TYPE(cls_attr) != &PyWrapperDescr_Type) { reimp = cls_attr; break; } } Py_DECREF(mname_obj); if (reimp != NULL) { /* * Emulate the behaviour of a descriptor to make sure we return a bound * method. */ if (PyMethod_Check(reimp)) { /* It's already a method but make sure it is bound. */ if (PyMethod_GET_SELF(reimp) != NULL) Py_INCREF(reimp); else reimp = PyMethod_New(PyMethod_GET_FUNCTION(reimp), (PyObject *)sipSelf); } else if (PyFunction_Check(reimp)) { reimp = PyMethod_New(reimp, (PyObject *)sipSelf); } else if (Py_TYPE(reimp)->tp_descr_get) { /* It is a descriptor, so assume it will do the right thing. */ reimp = Py_TYPE(reimp)->tp_descr_get(reimp, (PyObject *)sipSelf, cls); } else { /* * We don't know what it is so just return and assume that an * appropriate exception will be raised later on. */ Py_INCREF(reimp); } } else { /* Use the fast track in future. */ *pymc = 1; if (cname != NULL) { /* Note that this will only be raised once per method. */ PyErr_Format(PyExc_NotImplementedError, "%s.%s() is abstract and must be overridden", cname, mname); PyErr_Print(); } #ifdef WITH_THREAD PyGILState_Release(*gil); #endif } return reimp; release_gil: #ifdef WITH_THREAD PyGILState_Release(*gil); #endif return NULL; } /* * Convert a C/C++ pointer to the object that wraps it. */ static PyObject *sip_api_get_pyobject(void *cppPtr, const sipTypeDef *td) { return (PyObject *)sipOMFindObject(&cppPyMap, cppPtr, td); } /* * The default access function. */ void *sip_api_get_address(sipSimpleWrapper *w) { return (w->access_func != NULL) ? w->access_func(w, GuardedPointer) : w->data; } /* * The access function for handwritten access functions. */ static void *explicit_access_func(sipSimpleWrapper *sw, AccessFuncOp op) { typedef void *(*explicitAccessFunc)(void); if (op == ReleaseGuard) return NULL; return ((explicitAccessFunc)(sw->data))(); } /* * The access function for indirect access. */ static void *indirect_access_func(sipSimpleWrapper *sw, AccessFuncOp op) { void *addr; switch (op) { case UnguardedPointer: addr = sw->data; break; case GuardedPointer: addr = *((void **)sw->data); break; default: addr = NULL; } return addr; } /* * Get the C/C++ pointer for a complex object. Note that not casting the C++ * pointer is a bug. However this would only ever be called by PyQt3 signal * emitter code and PyQt doesn't contain anything that multiply inherits from * QObject. */ static void *sip_api_get_complex_cpp_ptr(sipSimpleWrapper *sw) { return getComplexCppPtr(sw, NULL); } /* * Get the C/C++ pointer for a complex object and optionally cast it to the * required type. */ static void *getComplexCppPtr(sipSimpleWrapper *sw, const sipTypeDef *td) { if (!sipIsDerived(sw)) { PyErr_SetString(PyExc_RuntimeError, "no access to protected functions or signals for objects not created from Python"); return NULL; } return sip_api_get_cpp_ptr(sw, td); } /* * Get the C/C++ pointer from a wrapper and optionally cast it to the required * type. */ void *sip_api_get_cpp_ptr(sipSimpleWrapper *sw, const sipTypeDef *td) { void *ptr = sip_api_get_address(sw); if (checkPointer(ptr, sw) < 0) return NULL; if (td != NULL) { if (PyObject_TypeCheck((PyObject *)sw, sipTypeAsPyTypeObject(td))) ptr = cast_cpp_ptr(ptr, Py_TYPE(sw), td); else ptr = NULL; if (ptr == NULL) PyErr_Format(PyExc_TypeError, "could not convert '%s' to '%s'", Py_TYPE(sw)->tp_name, sipPyNameOfContainer(&((const sipClassTypeDef *)td)->ctd_container, td)); } return ptr; } /* * Cast a C/C++ pointer from a source type to a destination type. */ static void *cast_cpp_ptr(void *ptr, PyTypeObject *src_type, const sipTypeDef *dst_type) { sipCastFunc cast = ((const sipClassTypeDef *)((sipWrapperType *)src_type)->wt_td)->ctd_cast; /* C structures and base classes don't have cast functions. */ if (cast != NULL) ptr = (*cast)(ptr, dst_type); return ptr; } /* * Check that a pointer is non-NULL. */ static int checkPointer(void *ptr, sipSimpleWrapper *sw) { if (ptr == NULL) { PyErr_Format(PyExc_RuntimeError, (sipWasCreated(sw) ? "wrapped C/C++ object of type %s has been deleted" : "super-class __init__() of type %s was never called"), Py_TYPE(sw)->tp_name); return -1; } return 0; } /* * Keep an extra reference to an object. */ static void sip_api_keep_reference(PyObject *self, int key, PyObject *obj) { PyObject *dict, *key_obj; /* * If there isn't a "self" to keep the extra reference for later garbage * collection then just take a reference and let it leak. */ if (self == NULL) { Py_XINCREF(obj); return; } /* Create the extra references dictionary if needed. */ if ((dict = ((sipSimpleWrapper *)self)->extra_refs) == NULL) { if ((dict = PyDict_New()) == NULL) return; ((sipSimpleWrapper *)self)->extra_refs = dict; } if ((key_obj = PyLong_FromLong(key)) != NULL) { /* This can happen if the argument was optional. */ if (obj == NULL) obj = Py_None; PyDict_SetItem(dict, key_obj, obj); Py_DECREF(key_obj); } } /* * Get an object that has an extra reference. */ static PyObject *sip_api_get_reference(PyObject *self, int key) { PyObject *dict, *key_obj, *obj; /* Get the extra references dictionary if there is one. */ if ((dict = ((sipSimpleWrapper *)self)->extra_refs) == NULL) return NULL; if ((key_obj = PyLong_FromLong(key)) == NULL) return NULL; obj = PyDict_GetItem(dict, key_obj); Py_DECREF(key_obj); Py_XINCREF(obj); return obj; } /* * Return TRUE if an object is owned by Python. Note that this isn't * implemented as a macro in sip.h because the position of the sw_flags field * is dependent on the version of Python. */ static int sip_api_is_owned_by_python(sipSimpleWrapper *sw) { return sipIsPyOwned(sw); } /* * Return TRUE if the type of a C++ instance is a derived class. Note that * this isn't implemented as a macro in sip.h because the position of the * sw_flags field is dependent on the version of Python. */ static int sip_api_is_derived_class(sipSimpleWrapper *sw) { return sipIsDerived(sw); } /* * Get the user defined object from a wrapped object. Note that this isn't * implemented as a macro in sip.h because the position of the user field is * dependent on the version of Python. */ static PyObject *sip_api_get_user_object(const sipSimpleWrapper *sw) { return sw->user; } /* * Set the user defined object in a wrapped object. Note that this isn't * implemented as a macro in sip.h because the position of the user field is * dependent on the version of Python. */ static void sip_api_set_user_object(sipSimpleWrapper *sw, PyObject *user) { sw->user = user; } /* * Check to see if a Python object can be converted to a type. */ static int sip_api_can_convert_to_type(PyObject *pyObj, const sipTypeDef *td, int flags) { int ok; assert(td == NULL || sipTypeIsClass(td) || sipTypeIsMapped(td)); if (td == NULL) { /* * The type must be /External/ and the module that contains the * implementation hasn't been imported. */ ok = FALSE; } else if (pyObj == Py_None) { /* If the type explicitly handles None then ignore the flags. */ if (sipTypeAllowNone(td)) ok = TRUE; else ok = ((flags & SIP_NOT_NONE) == 0); } else { sipConvertToFunc cto; if (sipTypeIsClass(td)) { cto = ((const sipClassTypeDef *)td)->ctd_cto; if (cto == NULL || (flags & SIP_NO_CONVERTORS) != 0) ok = PyObject_TypeCheck(pyObj, sipTypeAsPyTypeObject(td)); else ok = cto(pyObj, NULL, NULL, NULL, NULL); } else { if ((cto = ((const sipMappedTypeDef *)td)->mtd_cto) != NULL) ok = cto(pyObj, NULL, NULL, NULL, NULL); else ok = FALSE; } } return ok; } /* * sip_api_convert_to_type_us() without user state support. */ static void *sip_api_convert_to_type(PyObject *pyObj, const sipTypeDef *td, PyObject *transferObj, int flags, int *statep, int *iserrp) { return sip_api_convert_to_type_us(pyObj, td, transferObj, flags, statep, NULL, iserrp); } /* * Convert a Python object to a C/C++ pointer, assuming a previous call to * sip_api_can_convert_to_type() has been successful. Allow ownership to be * transferred and any type convertors to be disabled. */ static void *sip_api_convert_to_type_us(PyObject *pyObj, const sipTypeDef *td, PyObject *transferObj, int flags, int *statep, void **user_statep, int *iserrp) { void *cpp = NULL; int state = 0; assert(sipTypeIsClass(td) || sipTypeIsMapped(td)); /* Don't convert if there has already been an error. */ if (!*iserrp) { /* Do the conversion. */ if (pyObj == Py_None && !sipTypeAllowNone(td)) { cpp = NULL; } else { sipConvertToFunc cto; if (sipTypeIsClass(td)) { cto = ((const sipClassTypeDef *)td)->ctd_cto; if (cto == NULL || (flags & SIP_NO_CONVERTORS) != 0) { if ((cpp = sip_api_get_cpp_ptr((sipSimpleWrapper *)pyObj, td)) == NULL) { *iserrp = TRUE; } else if (transferObj != NULL) { if (transferObj == Py_None) sip_api_transfer_back(pyObj); else sip_api_transfer_to(pyObj, transferObj); } } else if (user_state_is_valid(td, user_statep)) { state = cto(pyObj, &cpp, iserrp, transferObj, user_statep); } } else if ((cto = ((const sipMappedTypeDef *)td)->mtd_cto) != NULL) { if (user_state_is_valid(td, user_statep)) state = cto(pyObj, &cpp, iserrp, transferObj, user_statep); } else { raise_no_convert_to(pyObj, td); } } } if (statep != NULL) *statep = state; return cpp; } /* * sip_api_force_convert_to_type_us() without user state support. */ static void *sip_api_force_convert_to_type(PyObject *pyObj, const sipTypeDef *td, PyObject *transferObj, int flags, int *statep, int *iserrp) { return sip_api_force_convert_to_type_us(pyObj, td, transferObj, flags, statep, NULL, iserrp); } /* * Convert a Python object to a C/C++ pointer and raise an exception if it * can't be done. */ void *sip_api_force_convert_to_type_us(PyObject *pyObj, const sipTypeDef *td, PyObject *transferObj, int flags, int *statep, void **user_statep, int *iserrp) { /* Don't even try if there has already been an error. */ if (*iserrp) return NULL; /* See if the object's type can be converted. */ if (!sip_api_can_convert_to_type(pyObj, td, flags)) { if (sipTypeIsMapped(td)) raise_no_convert_to(pyObj, td); else PyErr_Format(PyExc_TypeError, "%s cannot be converted to %s.%s", Py_TYPE(pyObj)->tp_name, sipNameOfModule(td->td_module), sipPyNameOfContainer(&((const sipClassTypeDef *)td)->ctd_container, td)); if (statep != NULL) *statep = 0; *iserrp = TRUE; return NULL; } /* Do the conversion. */ return sip_api_convert_to_type_us(pyObj, td, transferObj, flags, statep, user_statep, iserrp); } /* * sip_api_release_type_us() without user state support. */ static void sip_api_release_type(void *cpp, const sipTypeDef *td, int state) { sip_api_release_type_us(cpp, td, state, NULL); } /* * Release a possibly temporary C/C++ instance created by a type convertor. */ static void sip_api_release_type_us(void *cpp, const sipTypeDef *td, int state, void *user_state) { /* See if there is something to release. */ if (state & SIP_TEMPORARY) release(cpp, td, state, user_state); } /* * Release an instance. */ static void release(void *addr, const sipTypeDef *td, int state, void *user_state) { if (sipTypeIsClass(td)) { sipReleaseFunc rel = ((const sipClassTypeDef *)td)->ctd_release; /* * If there is no release function then it must be a C structure and we * can just free it. */ if (rel == NULL) sip_api_free(addr); else rel(addr, state); } else if (sipTypeIsMapped(td)) { sipReleaseUSFunc rel = ((const sipMappedTypeDef *)td)->mtd_release; if (rel != NULL) rel(addr, state, user_state); } } /* * Convert a C/C++ instance to a Python instance. */ PyObject *sip_api_convert_from_type(void *cpp, const sipTypeDef *td, PyObject *transferObj) { PyObject *py; sipConvertFromFunc cfrom; assert(sipTypeIsClass(td) || sipTypeIsMapped(td)); /* Handle None. */ if (cpp == NULL) { Py_INCREF(Py_None); return Py_None; } cpp = resolve_proxy(td, cpp); cfrom = get_from_convertor(td); if (cfrom != NULL) return cfrom(cpp, transferObj); if (sipTypeIsMapped(td)) { raise_no_convert_from(td); return NULL; } /* * See if we have already wrapped it. Invoking sub-class code can be * expensive so we check the cache first, even though the sub-class code * might perform a down-cast. */ if ((py = sip_api_get_pyobject(cpp, td)) == NULL && sipTypeHasSCC(td)) { void *orig_cpp = cpp; const sipTypeDef *orig_td = td; /* Apply the sub-class convertor. */ td = convertSubClass(td, &cpp); /* * If the sub-class convertor has done something then check the cache * again using the modified values. */ if (cpp != orig_cpp || td != orig_td) py = sip_api_get_pyobject(cpp, td); } if (py != NULL) Py_INCREF(py); else if ((py = wrap_simple_instance(cpp, td, NULL, SIP_SHARE_MAP)) == NULL) return NULL; /* Handle any ownership transfer. */ if (transferObj != NULL) { if (transferObj == Py_None) sip_api_transfer_back(py); else sip_api_transfer_to(py, transferObj); } return py; } /* * Convert a new C/C++ instance to a Python instance. */ static PyObject *sip_api_convert_from_new_type(void *cpp, const sipTypeDef *td, PyObject *transferObj) { sipWrapper *owner; sipConvertFromFunc cfrom; /* Handle None. */ if (cpp == NULL) { Py_INCREF(Py_None); return Py_None; } cpp = resolve_proxy(td, cpp); cfrom = get_from_convertor(td); if (cfrom != NULL) { PyObject *res = cfrom(cpp, transferObj); if (res != NULL) { /* * We no longer need the C/C++ instance so we release it (unless * its ownership is transferred). This means this call is * semantically equivalent to the case where we are wrapping a * class. */ if (transferObj == NULL || transferObj == Py_None) release(cpp, td, 0, NULL); } return res; } if (sipTypeIsMapped(td)) { raise_no_convert_from(td); return NULL; } /* Apply any sub-class convertor. */ if (sipTypeHasSCC(td)) td = convertSubClass(td, &cpp); /* Handle any ownership transfer. */ if (transferObj == NULL || transferObj == Py_None) owner = NULL; else owner = (sipWrapper *)transferObj; return wrap_simple_instance(cpp, td, owner, (owner == NULL ? SIP_PY_OWNED : 0)); } /* * Implement the normal transfer policy for the result of %ConvertToTypeCode, * ie. it is temporary unless it is being transferred from Python. */ int sip_api_get_state(PyObject *transferObj) { return (transferObj == NULL || transferObj == Py_None) ? SIP_TEMPORARY : 0; } /* * This is set by sip_api_find_type() before calling bsearch() on the types * table for the module. This is a hack that works around the problem of * unresolved externally defined types. */ static sipExportedModuleDef *module_searched; /* * The bsearch() helper function for searching the types table. */ static int compareTypeDef(const void *key, const void *el) { const char *s1 = (const char *)key; const char *s2 = NULL; const sipTypeDef *td; char ch1, ch2; /* Allow for unresolved externally defined types. */ td = *(const sipTypeDef **)el; if (td != NULL) { s2 = sipTypeName(td); } else { sipExternalTypeDef *etd = module_searched->em_external; assert(etd != NULL); /* Find which external type it is. */ while (etd->et_nr >= 0) { const void *tdp = &module_searched->em_types[etd->et_nr]; if (tdp == el) { s2 = etd->et_name; break; } ++etd; } assert(s2 != NULL); } /* * Compare while ignoring spaces so that we don't impose a rigorous naming * standard. This only really affects template-based mapped types. */ do { while ((ch1 = *s1++) == ' ') ; while ((ch2 = *s2++) == ' ') ; /* We might be looking for a pointer or a reference. */ if ((ch1 == '*' || ch1 == '&' || ch1 == '\0') && ch2 == '\0') return 0; } while (ch1 == ch2); return (ch1 < ch2 ? -1 : 1); } /* * Return the type structure for a particular type. */ static const sipTypeDef *sip_api_find_type(const char *type) { sipExportedModuleDef *em; for (em = moduleList; em != NULL; em = em->em_next) { sipTypeDef **tdp; /* The backdoor to the comparison helper. */ module_searched = em; tdp = (sipTypeDef **)bsearch((const void *)type, (const void *)em->em_types, em->em_nrtypes, sizeof (sipTypeDef *), compareTypeDef); if (tdp != NULL) { /* * Note that this will be NULL for unresolved externally defined * types. */ return *tdp; } } return NULL; } /* * Call a hook. */ static void sip_api_call_hook(const char *hookname) { PyObject *dictofmods, *mod, *dict, *hook, *res; /* Get the dictionary of modules. */ if ((dictofmods = PyImport_GetModuleDict()) == NULL) return; /* Get the builtins module. */ if ((mod = PyDict_GetItemString(dictofmods, "builtins")) == NULL) return; /* Get it's dictionary. */ if ((dict = PyModule_GetDict(mod)) == NULL) return; /* Get the function hook. */ if ((hook = PyDict_GetItemString(dict, hookname)) == NULL) return; /* Call the hook and discard any result. */ res = PyObject_Call(hook, empty_tuple, NULL); Py_XDECREF(res); } /* * Call any sub-class convertors for a given type returning a pointer to the * sub-type object, and possibly modifying the C++ address (in the case of * multiple inheritence). */ static const sipTypeDef *convertSubClass(const sipTypeDef *td, void **cppPtr) { /* Handle the trivial case. */ if (*cppPtr == NULL) return NULL; /* Try the conversions until told to stop. */ while (convertPass(&td, cppPtr)) ; return td; } /* * Do a single pass through the available convertors. */ static int convertPass(const sipTypeDef **tdp, void **cppPtr) { PyTypeObject *py_type = sipTypeAsPyTypeObject(*tdp); sipExportedModuleDef *em; /* * Note that this code depends on the fact that a module appears in the * list of modules before any module it imports, ie. sub-class convertors * will be invoked for more specific types first. */ for (em = moduleList; em != NULL; em = em->em_next) { sipSubClassConvertorDef *scc; if ((scc = em->em_convertors) == NULL) continue; while (scc->scc_convertor != NULL) { PyTypeObject *base_type = sipTypeAsPyTypeObject(scc->scc_basetype); /* * The base type is the "root" class that may have a number of * convertors each handling a "branch" of the derived tree of * classes. The "root" normally implements the base function that * provides the RTTI used by the convertors and is re-implemented * by derived classes. We therefore see if the target type is a * sub-class of the root, ie. see if the convertor might be able to * convert the target type to something more specific. */ if (PyType_IsSubtype(py_type, base_type)) { void *ptr; const sipTypeDef *sub_td; ptr = cast_cpp_ptr(*cppPtr, py_type, scc->scc_basetype); if ((sub_td = (*scc->scc_convertor)(&ptr)) != NULL) { PyTypeObject *sub_type = sipTypeAsPyTypeObject(sub_td); /* * We are only interested in types that are not * super-classes of the target. This happens either * because it is in an earlier convertor than the one that * handles the type or it is in a later convertor that * handles a different branch of the hierarchy. Either * way, the ordering of the modules ensures that there will * be no more than one and that it will be the right one. */ if (!PyType_IsSubtype(py_type, sub_type)) { *tdp = sub_td; *cppPtr = ptr; /* * Finally we allow the convertor to return a type that * is apparently unrelated to the current convertor. * This causes the whole process to be restarted with * the new values. The use case is PyQt's QLayoutItem. */ return !PyType_IsSubtype(sub_type, base_type); } } } ++scc; } } /* * We haven't found the exact type, so return the most specific type that * it must be. This can happen legitimately if the wrapped library is * returning an internal class that is down-cast to a more generic class. * Also we want this function to be safe when a class doesn't have any * convertors. */ return FALSE; } /* * Raise an unknown exception. Make no assumptions about the GIL. */ static void sip_api_raise_unknown_exception(void) { static PyObject *mobj = NULL; SIP_BLOCK_THREADS sip_objectify("unknown", &mobj); PyErr_SetObject(PyExc_Exception, mobj); SIP_UNBLOCK_THREADS } /* * Raise an exception implemented as a type. Make no assumptions about the * GIL. */ static void sip_api_raise_type_exception(const sipTypeDef *td, void *ptr) { PyObject *self; assert(sipTypeIsClass(td)); SIP_BLOCK_THREADS self = wrap_simple_instance(ptr, td, NULL, SIP_PY_OWNED); PyErr_SetObject((PyObject *)sipTypeAsPyTypeObject(td), self); Py_XDECREF(self); SIP_UNBLOCK_THREADS } /* * Return the generated type structure of an encoded type. */ static sipTypeDef *getGeneratedType(const sipEncodedTypeDef *enc, sipExportedModuleDef *em) { if (enc->sc_module == 255) return em->em_types[enc->sc_type]; return em->em_imports[enc->sc_module].im_imported_types[enc->sc_type].it_td; } /* * Return the generated class type structure of a class's super-class. */ sipClassTypeDef *sipGetGeneratedClassType(const sipEncodedTypeDef *enc, const sipClassTypeDef *ctd) { return (sipClassTypeDef *)getGeneratedType(enc, ctd->ctd_base.td_module); } /* * Find a particular slot function for a type. */ static void *findSlot(PyObject *self, sipPySlotType st) { void *slot = NULL; PyTypeObject *py_type = Py_TYPE(self); /* See if it is a wrapper. */ /* TODO: will this always be TRUE? */ if (PyObject_TypeCheck((PyObject *)py_type, &sipWrapperType_Type)) { const sipClassTypeDef *ctd; ctd = (sipClassTypeDef *)((sipWrapperType *)(py_type))->wt_td; slot = findSlotInClass(ctd, st); } return slot; } /* * Find a particular slot function in a class hierarchy. */ static void *findSlotInClass(const sipClassTypeDef *ctd, sipPySlotType st) { void *slot; if (ctd->ctd_pyslots != NULL) slot = findSlotInSlotList(ctd->ctd_pyslots, st); else slot = NULL; if (slot == NULL) { sipEncodedTypeDef *sup; /* Search any super-types. */ if ((sup = ctd->ctd_supers) != NULL) { do { const sipClassTypeDef *sup_ctd = sipGetGeneratedClassType( sup, ctd); slot = findSlotInClass(sup_ctd, st); } while (slot == NULL && !sup++->sc_flag); } } return slot; } /* * Find a particular slot function in a particular type. */ static void *findSlotInSlotList(sipPySlotDef *psd, sipPySlotType st) { while (psd->psd_func != NULL) { if (psd->psd_type == st) return psd->psd_func; ++psd; } return NULL; } /* * Return the C/C++ address and the generated class structure for a wrapper. */ static void *getPtrTypeDef(sipSimpleWrapper *self, const sipClassTypeDef **ctd) { *ctd = (const sipClassTypeDef *)((sipWrapperType *)Py_TYPE(self))->wt_td; return (sipNotInMap(self) ? NULL : sip_api_get_address(self)); } /* * Handle an objobjargproc slot. */ static int objobjargprocSlot(PyObject *self, PyObject *arg1, PyObject *arg2, sipPySlotType st) { int (*f)(PyObject *, PyObject *); int res; f = (int (*)(PyObject *, PyObject *))findSlot(self, st); if (f != NULL) { PyObject *args; /* * Slot handlers require a single PyObject *. The second argument is * optional. */ if (arg2 == NULL) { args = arg1; Py_INCREF(args); } else if ((args = PyTuple_Pack(2, arg1, arg2)) == NULL) { return -1; } res = f(self, args); Py_DECREF(args); } else { PyErr_SetNone(PyExc_NotImplementedError); res = -1; } return res; } /* * Handle an ssizeobjargproc slot. */ static int ssizeobjargprocSlot(PyObject *self, Py_ssize_t arg1, PyObject *arg2, sipPySlotType st) { int (*f)(PyObject *, PyObject *); int res; f = (int (*)(PyObject *, PyObject *))findSlot(self, st); if (f != NULL) { PyObject *args; /* * Slot handlers require a single PyObject *. The second argument is * optional. */ if (arg2 == NULL) args = PyLong_FromSsize_t(arg1); else args = Py_BuildValue("(nO)", arg1, arg2); if (args == NULL) return -1; res = f(self, args); Py_DECREF(args); } else { PyErr_SetNone(PyExc_NotImplementedError); res = -1; } return res; } /* * The metatype alloc slot. */ static PyObject *sipWrapperType_alloc(PyTypeObject *self, Py_ssize_t nitems) { PyObject *o; /* Call the standard super-metatype alloc. */ if ((o = PyType_Type.tp_alloc(self, nitems)) == NULL) return NULL; /* * Consume any extra type specific information and use it to initialise the * slots. This only happens for directly wrapped classes (and not * programmer written sub-classes). This must be done in the alloc * function because it is the only place we can break out of the default * new() function before PyType_Ready() is called. */ if (currentType != NULL) { assert(!sipTypeIsEnum(currentType)); ((sipWrapperType *)o)->wt_td = currentType; if (sipTypeIsClass(currentType)) { const sipClassTypeDef *ctd = (const sipClassTypeDef *)currentType; const char *docstring = ctd->ctd_docstring; /* * Skip the marker that identifies the docstring as being * automatically generated. */ if (docstring != NULL && *docstring == AUTO_DOCSTRING) ++docstring; ((PyTypeObject *)o)->tp_doc = docstring; addClassSlots((sipWrapperType *)o, ctd); /* Patch any mixin initialiser. */ if (ctd->ctd_init_mixin != NULL) ((PyTypeObject *)o)->tp_init = ctd->ctd_init_mixin; } } return o; } /* * The metatype init slot. */ static int sipWrapperType_init(sipWrapperType *self, PyObject *args, PyObject *kwds) { /* Call the standard super-metatype init. */ if (PyType_Type.tp_init((PyObject *)self, args, kwds) < 0) return -1; /* * If we don't yet have any extra type specific information (because we are * a programmer defined sub-class) then get it from the (first) super-type. */ if (self->wt_td == NULL) { PyTypeObject *base = ((PyTypeObject *)self)->tp_base; self->wt_user_type = TRUE; /* * We allow the class to use this as a meta-type without being derived * from a class that uses it. This allows mixin classes that need * their own meta-type to work so long as their meta-type is derived * from this meta-type. This condition is indicated by the pointer to * the generated type structure being NULL. */ if (base != NULL && PyObject_TypeCheck((PyObject *)base, (PyTypeObject *)&sipWrapperType_Type)) self->wt_td = ((sipWrapperType *)base)->wt_td; } else { /* * We must be a generated type so remember the type object in the * generated type structure. */ assert(self->wt_td->td_py_type == NULL); self->wt_td->td_py_type = (PyTypeObject *)self; } return 0; } /* * The metatype getattro slot. */ static PyObject *sipWrapperType_getattro(PyObject *self, PyObject *name) { if (sip_add_all_lazy_attrs(((sipWrapperType *)self)->wt_td) < 0) return NULL; return PyType_Type.tp_getattro(self, name); } /* * The metatype setattro slot. */ static int sipWrapperType_setattro(PyObject *self, PyObject *name, PyObject *value) { if (sip_add_all_lazy_attrs(((sipWrapperType *)self)->wt_td) < 0) return -1; return PyType_Type.tp_setattro(self, name, value); } /* * The instance new slot. */ static PyObject *sipSimpleWrapper_new(sipWrapperType *wt, PyObject *args, PyObject *kwds) { sipTypeDef *td = wt->wt_td; (void)args; (void)kwds; /* Check the base types are not being used directly. */ if (wt == &sipSimpleWrapper_Type || wt == &sipWrapper_Type) { PyErr_Format(PyExc_TypeError, "the %s type cannot be instantiated or sub-classed", ((PyTypeObject *)wt)->tp_name); return NULL; } if (sip_add_all_lazy_attrs(td) < 0) return NULL; /* See if it is a mapped type. */ if (sipTypeIsMapped(td)) { PyErr_Format(PyExc_TypeError, "%s.%s represents a mapped type and cannot be instantiated", sipNameOfModule(td->td_module), sipPyNameOfContainer(get_container(td), td)); return NULL; } /* See if it is a namespace. */ if (sipTypeIsNamespace(td)) { PyErr_Format(PyExc_TypeError, "%s.%s represents a C++ namespace and cannot be instantiated", sipNameOfModule(td->td_module), sipPyNameOfContainer(get_container(td), td)); return NULL; } /* * See if the object is being created explicitly rather than being wrapped. */ if (!sipIsPending()) { /* * See if it cannot be instantiated or sub-classed from Python, eg. * it's an opaque class. Some restrictions might be overcome with * better SIP support. */ if (((sipClassTypeDef *)td)->ctd_init == NULL) { PyErr_Format(PyExc_TypeError, "%s.%s cannot be instantiated or sub-classed", sipNameOfModule(td->td_module), sipPyNameOfContainer(get_container(td), td)); return NULL; } /* See if it is an abstract type. */ if (sipTypeIsAbstract(td) && !wt->wt_user_type && ((sipClassTypeDef *)td)->ctd_init_mixin == NULL) { PyErr_Format(PyExc_TypeError, "%s.%s represents a C++ abstract class and cannot be instantiated", sipNameOfModule(td->td_module), sipPyNameOfContainer(get_container(td), td)); return NULL; } } /* Call the standard super-type new. */ return PyBaseObject_Type.tp_new((PyTypeObject *)wt, empty_tuple, NULL); } /* * The instance init slot. */ static int sipSimpleWrapper_init(sipSimpleWrapper *self, PyObject *args, PyObject *kwds) { void *sipNew; int sipFlags, from_cpp = TRUE; sipWrapper *owner; sipWrapperType *wt = (sipWrapperType *)Py_TYPE(self); sipTypeDef *td = wt->wt_td; sipClassTypeDef *ctd = (sipClassTypeDef *)td; PyObject *unused = NULL; sipFinalFunc final_func = find_finalisation(ctd); /* Check for an existing C++ instance waiting to be wrapped. */ if (sipGetPending(&sipNew, &owner, &sipFlags) < 0) return -1; if (sipNew == NULL) { PyObject *parseErr = NULL, **unused_p = NULL; /* See if we are interested in any unused keyword arguments. */ if (sipTypeCallSuperInit(&ctd->ctd_base) || final_func != NULL) unused_p = &unused; /* Call the C++ ctor. */ owner = NULL; sipNew = ctd->ctd_init(self, args, kwds, unused_p, (PyObject **)&owner, &parseErr); if (sipNew != NULL) { sipFlags = SIP_DERIVED_CLASS; } else if (parseErr == NULL) { /* * The C++ ctor must have raised an exception which has been * translated to a Python exception. */ return -1; } else { sipInitExtenderDef *ie = wt->wt_iextend; /* * If we have not found an appropriate overload then try any * extenders. */ while (PyList_Check(parseErr) && ie != NULL) { sipNew = ie->ie_extender(self, args, kwds, &unused, (PyObject **)&owner, &parseErr); if (sipNew != NULL) break; ie = ie->ie_next; } if (sipNew == NULL) { const char *docstring = ctd->ctd_docstring; /* * Use the docstring for errors if it was automatically * generated. */ if (docstring != NULL) { if (*docstring == AUTO_DOCSTRING) ++docstring; else docstring = NULL; } sip_api_no_function(parseErr, sipPyNameOfContainer(&ctd->ctd_container, td), docstring); return -1; } sipFlags = 0; } if (owner == NULL) sipFlags |= SIP_PY_OWNED; else if ((PyObject *)owner == Py_None) { /* This is the hack that means that C++ owns the new instance. */ sipFlags |= SIP_CPP_HAS_REF; Py_INCREF(self); owner = NULL; } /* The instance was created from Python. */ from_cpp = FALSE; } /* Handler any owner if the type supports the concept. */ if (PyObject_TypeCheck((PyObject *)self, (PyTypeObject *)&sipWrapper_Type)) { /* * The application may be doing something very unadvisable (like * calling __init__() for a second time), so make sure we don't already * have a parent. */ removeFromParent((sipWrapper *)self); if (owner != NULL) { assert(PyObject_TypeCheck((PyObject *)owner, (PyTypeObject *)&sipWrapper_Type)); addToParent((sipWrapper *)self, (sipWrapper *)owner); } } self->data = sipNew; self->sw_flags = sipFlags | SIP_CREATED; /* Set the access function. */ if (sipIsAccessFunc(self)) self->access_func = explicit_access_func; else if (sipIsIndirect(self)) self->access_func = indirect_access_func; else self->access_func = NULL; if (!sipNotInMap(self)) sipOMAddObject(&cppPyMap, self); /* If we are wrapping an instance returned from C/C++ then we are done. */ if (from_cpp) { /* * Invoke any event handlers for instances that are accessed directly. */ if (self->access_func == NULL) { sipEventHandler *eh; for (eh = event_handlers[sipEventWrappedInstance]; eh != NULL; eh = eh->next) { if (is_subtype(ctd, eh->ctd)) { sipWrappedInstanceEventHandler handler = (sipWrappedInstanceEventHandler)eh->handler; handler(sipNew); } } } return 0; } /* Call any finalisation code. */ if (final_func != NULL) { PyObject *new_unused = NULL, **new_unused_p; if (unused == NULL || unused != kwds) { /* * There are no unused arguments or we have already created a dict * containing the unused sub-set, so there is no need to create * another. */ new_unused_p = NULL; } else { /* * All of the keyword arguments are unused, so if some of them are * now going to be used then a new dict will be needed. */ new_unused_p = &new_unused; } if (final_func((PyObject *)self, sipNew, unused, new_unused_p) < 0) { Py_XDECREF(unused); return -1; } if (new_unused != NULL) { Py_DECREF(unused); unused = new_unused; } } /* See if we should call the equivalent of super().__init__(). */ if (sipTypeCallSuperInit(&ctd->ctd_base)) { PyObject *next; /* Find the next type in the MRO. */ next = next_in_mro((PyObject *)self, (PyObject *)&sipSimpleWrapper_Type); /* * If the next type in the MRO is object then take a shortcut by not * calling super().__init__() but emulating object.__init__() instead. * This will be the most common case and also allows us to generate a * better exception message if there are unused keyword arguments. The * disadvantage is that the exception message will be different if * there is a mixin. */ if (next != (PyObject *)&PyBaseObject_Type) { int rc = super_init((PyObject *)self, empty_tuple, unused, next); Py_XDECREF(unused); return rc; } } if (unused_backdoor != NULL) { /* * We are being called by a mixin's __init__ so save any unused * arguments for it to pass on to the main class's __init__. */ *unused_backdoor = unused; } else if (unused != NULL) { /* We shouldn't have any unused keyword arguments. */ if (PyDict_Size(unused) != 0) { PyObject *key, *value; Py_ssize_t pos = 0; /* Just report one of the unused arguments. */ PyDict_Next(unused, &pos, &key, &value); PyErr_Format(PyExc_TypeError, "'%S' is an unknown keyword argument", key); Py_DECREF(unused); return -1; } Py_DECREF(unused); } return 0; } /* * Get the C++ address of a mixin. */ static void *sip_api_get_mixin_address(sipSimpleWrapper *w, const sipTypeDef *td) { PyObject *mixin; void *cpp; if ((mixin = PyObject_GetAttrString((PyObject *)w, sipTypeName(td))) == NULL) { PyErr_Clear(); return NULL; } cpp = sip_api_get_address((sipSimpleWrapper *)mixin); Py_DECREF(mixin); return cpp; } /* * Initialise a mixin. */ static int sip_api_init_mixin(PyObject *self, PyObject *args, PyObject *kwds, const sipClassTypeDef *ctd) { int rc; Py_ssize_t pos; PyObject *unused, *mixin, *mixin_name, *key, *value; PyTypeObject *self_wt = sipTypeAsPyTypeObject(((sipWrapperType *)Py_TYPE(self))->wt_td); PyTypeObject *wt = sipTypeAsPyTypeObject(&ctd->ctd_base); static PyObject *double_us = NULL; if (sip_objectify("__", &double_us) < 0) return -1; /* If we are not a mixin to another wrapped class then behave as normal. */ if (PyType_IsSubtype(self_wt, wt)) return super_init(self, args, kwds, next_in_mro(self, (PyObject *)wt)); /* * Create the mixin instance. Retain the positional arguments for the * super-class. Remember that, even though the mixin appears after the * main class in the MRO, it appears before sipWrapperType where the main * class's arguments are actually parsed. */ unused = NULL; unused_backdoor = &unused; mixin = PyObject_Call((PyObject *)wt, empty_tuple, kwds); unused_backdoor = NULL; if (mixin == NULL) goto gc_unused; /* Make sure the mixin can find the main instance. */ ((sipSimpleWrapper *)mixin)->mixin_main = self; Py_INCREF(self); if ((mixin_name = PyUnicode_FromString(sipTypeName(&ctd->ctd_base))) == NULL) { Py_DECREF(mixin); goto gc_unused; } rc = PyObject_SetAttr(self, mixin_name, mixin); Py_DECREF(mixin); if (rc < 0) goto gc_mixin_name; /* Add the mixin's useful attributes to the main class. */ pos = 0; while (PyDict_Next(wt->tp_dict, &pos, &key, &value)) { /* Don't replace existing values. */ if (PyDict_Contains(Py_TYPE(self)->tp_dict, key) != 0) continue; /* Skip values with names that start with double underscore. */ if (!PyUnicode_Check(key)) continue; /* * Despite what the docs say this returns a Py_ssize_t - although the * docs are probably right. */ rc = (int)PyUnicode_Tailmatch(key, double_us, 0, 2, -1); if (rc < 0) goto gc_mixin_name; if (rc > 0) continue; if (PyObject_IsInstance(value, (PyObject *)&sipMethodDescr_Type)) { if ((value = sipMethodDescr_Copy(value, mixin_name)) == NULL) goto gc_mixin_name; } else if (PyObject_IsInstance(value, (PyObject *)&sipVariableDescr_Type)) { if ((value = sipVariableDescr_Copy(value, mixin_name)) == NULL) goto gc_mixin_name; } else { Py_INCREF(value); } rc = PyDict_SetItem(Py_TYPE(self)->tp_dict, key, value); Py_DECREF(value); if (rc < 0) goto gc_mixin_name; } Py_DECREF(mixin_name); /* Call the super-class's __init__ with any remaining arguments. */ rc = super_init(self, args, unused, next_in_mro(self, (PyObject *)wt)); Py_XDECREF(unused); return rc; gc_mixin_name: Py_DECREF(mixin_name); gc_unused: Py_XDECREF(unused); return -1; } /* * Return the next in the MRO of an instance after a given type. */ static PyObject *next_in_mro(PyObject *self, PyObject *after) { Py_ssize_t i; PyObject *mro; mro = Py_TYPE(self)->tp_mro; assert(PyTuple_Check(mro)); for (i = 0; i < PyTuple_GET_SIZE(mro); ++i) if (PyTuple_GET_ITEM(mro, i) == after) break; /* Assert that we have found ourself and that we are not the last. */ assert(i + 1 < PyTuple_GET_SIZE(mro)); return PyTuple_GET_ITEM(mro, i + 1); } /* * Call the equivalent of super()__init__() of an instance. */ static int super_init(PyObject *self, PyObject *args, PyObject *kwds, PyObject *type) { int i; PyObject *init, *init_args, *init_res; if ((init = PyObject_GetAttr(type, init_name)) == NULL) return -1; if ((init_args = PyTuple_New(1 + PyTuple_GET_SIZE(args))) == NULL) { Py_DECREF(init); return -1; } PyTuple_SET_ITEM(init_args, 0, self); Py_INCREF(self); for (i = 0; i < PyTuple_GET_SIZE(args); ++i) { PyObject *arg = PyTuple_GET_ITEM(args, i); PyTuple_SET_ITEM(init_args, 1 + i, arg); Py_INCREF(arg); } init_res = PyObject_Call(init, init_args, kwds); Py_DECREF(init_args); Py_DECREF(init); Py_XDECREF(init_res); return (init_res != NULL) ? 0 : -1; } /* * Find any finalisation function for a class, searching its super-classes if * necessary. */ static sipFinalFunc find_finalisation(sipClassTypeDef *ctd) { sipEncodedTypeDef *sup; if (ctd->ctd_final != NULL) return ctd->ctd_final; if ((sup = ctd->ctd_supers) != NULL) do { sipClassTypeDef *sup_ctd = sipGetGeneratedClassType(sup, ctd); sipFinalFunc func; if ((func = find_finalisation(sup_ctd)) != NULL) return func; } while (!sup++->sc_flag); return NULL; } /* * The instance traverse slot. */ static int sipSimpleWrapper_traverse(sipSimpleWrapper *self, visitproc visit, void *arg) { int vret; void *ptr; const sipClassTypeDef *ctd; /* Call any handwritten traverse code. */ if ((ptr = getPtrTypeDef(self, &ctd)) != NULL) if (ctd->ctd_traverse != NULL) if ((vret = ctd->ctd_traverse(ptr, visit, arg)) != 0) return vret; if (self->dict != NULL) if ((vret = visit(self->dict, arg)) != 0) return vret; if (self->extra_refs != NULL) if ((vret = visit(self->extra_refs, arg)) != 0) return vret; if (self->user != NULL) if ((vret = visit(self->user, arg)) != 0) return vret; if (self->mixin_main != NULL) if ((vret = visit(self->mixin_main, arg)) != 0) return vret; return 0; } /* * The instance clear slot. */ static int sipSimpleWrapper_clear(sipSimpleWrapper *self) { int vret = 0; void *ptr; const sipClassTypeDef *ctd; PyObject *tmp; /* Call any handwritten clear code. */ if ((ptr = getPtrTypeDef(self, &ctd)) != NULL) if (ctd->ctd_clear != NULL) vret = ctd->ctd_clear(ptr); /* Remove the instance dictionary. */ tmp = self->dict; self->dict = NULL; Py_XDECREF(tmp); /* Remove any extra references dictionary. */ tmp = self->extra_refs; self->extra_refs = NULL; Py_XDECREF(tmp); /* Remove any user object. */ tmp = self->user; self->user = NULL; Py_XDECREF(tmp); /* Remove any mixin main. */ tmp = self->mixin_main; self->mixin_main = NULL; Py_XDECREF(tmp); return vret; } /* * The instance get buffer slot. */ static int sipSimpleWrapper_getbuffer(sipSimpleWrapper *self, Py_buffer *buf, int flags) { void *ptr; const sipClassTypeDef *ctd; if ((ptr = getPtrTypeDef(self, &ctd)) == NULL) return -1; if (sipTypeUseLimitedAPI(&ctd->ctd_base)) { sipGetBufferFuncLimited getbuffer = (sipGetBufferFuncLimited)ctd->ctd_getbuffer; sipBufferDef bd; /* * Ensure all fields have a default value. This means that extra * fields can be appended in the future that older handwritten code * doesn't know about. */ memset(&bd, 0, sizeof(sipBufferDef)); if (getbuffer((PyObject *)self, ptr, &bd) < 0) return -1; return PyBuffer_FillInfo(buf, (PyObject *)self, bd.bd_buffer, bd.bd_length, bd.bd_readonly, flags); } return ctd->ctd_getbuffer((PyObject *)self, ptr, buf, flags); } /* * The instance release buffer slot. */ static void sipSimpleWrapper_releasebuffer(sipSimpleWrapper *self, Py_buffer *buf) { void *ptr; const sipClassTypeDef *ctd; if ((ptr = getPtrTypeDef(self, &ctd)) == NULL) return; if (sipTypeUseLimitedAPI(&ctd->ctd_base)) { sipReleaseBufferFuncLimited releasebuffer = (sipReleaseBufferFuncLimited)ctd->ctd_releasebuffer; releasebuffer((PyObject *)self, ptr); return; } ctd->ctd_releasebuffer((PyObject *)self, ptr, buf); } /* * The instance dealloc slot. */ static void sipSimpleWrapper_dealloc(sipSimpleWrapper *self) { PyObject *error_type, *error_value, *error_traceback; /* Save the current exception, if any. */ PyErr_Fetch(&error_type, &error_value, &error_traceback); forgetObject(self); /* * Now that the C++ object no longer exists we can tidy up the Python * object. We used to do this first but that meant lambda slots were * removed too soon (if they were connected to QObject.destroyed()). */ sipSimpleWrapper_clear(self); /* Call the standard super-type dealloc. */ PyBaseObject_Type.tp_dealloc((PyObject *)self); /* Restore the saved exception. */ PyErr_Restore(error_type, error_value, error_traceback); } /* * The type call slot. */ static PyObject *slot_call(PyObject *self, PyObject *args, PyObject *kw) { PyObject *(*f)(PyObject *, PyObject *, PyObject *); f = (PyObject *(*)(PyObject *, PyObject *, PyObject *))findSlot(self, call_slot); assert(f != NULL); return f(self, args, kw); } /* * The sequence type item slot. */ static PyObject *slot_sq_item(PyObject *self, Py_ssize_t n) { PyObject *(*f)(PyObject *,PyObject *); PyObject *arg, *res; if ((arg = PyLong_FromSsize_t(n)) == NULL) return NULL; f = (PyObject *(*)(PyObject *,PyObject *))findSlot(self, getitem_slot); assert(f != NULL); res = f(self,arg); Py_DECREF(arg); return res; } /* * The mapping type assign subscript slot. */ static int slot_mp_ass_subscript(PyObject *self, PyObject *key, PyObject *value) { return objobjargprocSlot(self, key, value, (value != NULL ? setitem_slot : delitem_slot)); } /* * The sequence type assign item slot. */ static int slot_sq_ass_item(PyObject *self, Py_ssize_t i, PyObject *o) { return ssizeobjargprocSlot(self, i, o, (o != NULL ? setitem_slot : delitem_slot)); } /* * The type rich compare slot. */ static PyObject *slot_richcompare(PyObject *self, PyObject *arg, int op) { PyObject *(*f)(PyObject *,PyObject *); sipPySlotType st; /* Convert the operation to a slot type. */ switch (op) { case Py_LT: st = lt_slot; break; case Py_LE: st = le_slot; break; case Py_EQ: st = eq_slot; break; case Py_NE: st = ne_slot; break; case Py_GT: st = gt_slot; break; case Py_GE: st = ge_slot; break; default: /* Suppress a compiler warning. */ st = -1; } /* It might not exist if not all the above have been implemented. */ if ((f = (PyObject *(*)(PyObject *,PyObject *))findSlot(self, st)) == NULL) { Py_INCREF(Py_NotImplemented); return Py_NotImplemented; } return f(self, arg); } /* * The __dict__ getter. */ static PyObject *sipSimpleWrapper_get_dict(sipSimpleWrapper *sw, void *closure) { (void)closure; /* Create the dictionary if needed. */ if (sw->dict == NULL) { sw->dict = PyDict_New(); if (sw->dict == NULL) return NULL; } Py_INCREF(sw->dict); return sw->dict; } /* * The __dict__ setter. */ static int sipSimpleWrapper_set_dict(sipSimpleWrapper *sw, PyObject *value, void *closure) { (void)closure; /* Check that any new value really is a dictionary. */ if (value != NULL && !PyDict_Check(value)) { PyErr_Format(PyExc_TypeError, "__dict__ must be set to a dictionary, not a '%s'", Py_TYPE(value)->tp_name); return -1; } Py_XDECREF(sw->dict); Py_XINCREF(value); sw->dict = value; return 0; } /* * The table of getters and setters. */ static PyGetSetDef sipSimpleWrapper_getset[] = { {(char *)"__dict__", (getter)sipSimpleWrapper_get_dict, (setter)sipSimpleWrapper_set_dict, NULL, NULL}, {NULL, NULL, NULL, NULL, NULL} }; /* * The type data structure. Note that we pretend to be a mapping object and a * sequence object at the same time. Python will choose one over another, * depending on the context, but we implement as much as we can and don't make * assumptions about which Python will choose. */ sipWrapperType sipSimpleWrapper_Type = { #if !defined(STACKLESS) { #endif { PyVarObject_HEAD_INIT(&sipWrapperType_Type, 0) _SIP_MODULE_FQ_NAME ".simplewrapper", /* tp_name */ sizeof (sipSimpleWrapper), /* tp_basicsize */ 0, /* tp_itemsize */ (destructor)sipSimpleWrapper_dealloc, /* tp_dealloc */ 0, /* tp_print */ 0, /* tp_getattr */ 0, /* tp_setattr */ 0, /* tp_as_async */ 0, /* tp_repr */ 0, /* tp_as_number */ 0, /* tp_as_sequence */ 0, /* tp_as_mapping */ 0, /* tp_hash */ 0, /* tp_call */ 0, /* tp_str */ 0, /* tp_getattro */ 0, /* tp_setattro */ 0, /* tp_as_buffer */ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_GC, /* tp_flags */ 0, /* tp_doc */ (traverseproc)sipSimpleWrapper_traverse, /* tp_traverse */ (inquiry)sipSimpleWrapper_clear, /* tp_clear */ 0, /* tp_richcompare */ 0, /* tp_weaklistoffset */ 0, /* tp_iter */ 0, /* tp_iternext */ 0, /* tp_methods */ 0, /* tp_members */ sipSimpleWrapper_getset, /* tp_getset */ 0, /* tp_base */ 0, /* tp_dict */ 0, /* tp_descr_get */ 0, /* tp_descr_set */ offsetof(sipSimpleWrapper, dict), /* tp_dictoffset */ (initproc)sipSimpleWrapper_init, /* tp_init */ 0, /* tp_alloc */ (newfunc)sipSimpleWrapper_new, /* tp_new */ 0, /* tp_free */ 0, /* tp_is_gc */ 0, /* tp_bases */ 0, /* tp_mro */ 0, /* tp_cache */ 0, /* tp_subclasses */ 0, /* tp_weaklist */ 0, /* tp_del */ 0, /* tp_version_tag */ 0, /* tp_finalize */ 0, /* tp_vectorcall */ }, { 0, /* am_await */ 0, /* am_aiter */ 0, /* am_anext */ #if PY_VERSION_HEX >= 0x030a0000 0, /* am_send */ #endif }, { 0, /* nb_add */ 0, /* nb_subtract */ 0, /* nb_multiply */ 0, /* nb_remainder */ 0, /* nb_divmod */ 0, /* nb_power */ 0, /* nb_negative */ 0, /* nb_positive */ 0, /* nb_absolute */ 0, /* nb_bool */ 0, /* nb_invert */ 0, /* nb_lshift */ 0, /* nb_rshift */ 0, /* nb_and */ 0, /* nb_xor */ 0, /* nb_or */ 0, /* nb_int */ 0, /* nb_reserved */ 0, /* nb_float */ 0, /* nb_inplace_add */ 0, /* nb_inplace_subtract */ 0, /* nb_inplace_multiply */ 0, /* nb_inplace_remainder */ 0, /* nb_inplace_power */ 0, /* nb_inplace_lshift */ 0, /* nb_inplace_rshift */ 0, /* nb_inplace_and */ 0, /* nb_inplace_xor */ 0, /* nb_inplace_or */ 0, /* nb_floor_divide */ 0, /* nb_true_divide */ 0, /* nb_inplace_floor_divide */ 0, /* nb_inplace_true_divide */ 0, /* nb_index */ 0, /* nb_matrix_multiply */ 0, /* nb_inplace_matrix_multiply */ }, { 0, /* mp_length */ 0, /* mp_subscript */ 0, /* mp_ass_subscript */ }, { 0, /* sq_length */ 0, /* sq_concat */ 0, /* sq_repeat */ 0, /* sq_item */ 0, /* was_sq_slice */ 0, /* sq_ass_item */ 0, /* was_sq_ass_slice */ 0, /* sq_contains */ 0, /* sq_inplace_concat */ 0, /* sq_inplace_repeat */ }, { 0, /* bf_getbuffer */ 0, /* bf_releasebuffer */ }, 0, /* ht_name */ 0, /* ht_slots */ 0, /* ht_qualname */ 0, /* ht_cached_keys */ #if PY_VERSION_HEX >= 0x03090000 0, /* ht_module */ #endif #if !defined(STACKLESS) }, #endif 0, /* wt_user_type */ 0, /* wt_dict_complete */ 0, /* wt_unused */ 0, /* wt_td */ 0, /* wt_iextend */ 0, /* wt_user_data */ 0, /* wt_reserved */ }; /* * The wrapper clear slot. */ static int sipWrapper_clear(sipWrapper *self) { int vret; sipSimpleWrapper *sw = (sipSimpleWrapper *)self; vret = sipSimpleWrapper_clear(sw); /* Detach any children (which will be owned by C/C++). */ detachChildren(self); return vret; } /* * The wrapper dealloc slot. */ static void sipWrapper_dealloc(sipWrapper *self) { PyObject *error_type, *error_value, *error_traceback; /* Save the current exception, if any. */ PyErr_Fetch(&error_type, &error_value, &error_traceback); /* * We can't simply call the super-type because things have to be done in a * certain order. The first thing is to get rid of the wrapped instance. */ forgetObject((sipSimpleWrapper *)self); sipWrapper_clear(self); /* Skip the super-type's dealloc. */ PyBaseObject_Type.tp_dealloc((PyObject *)self); /* Restore the saved exception. */ PyErr_Restore(error_type, error_value, error_traceback); } /* * The wrapper traverse slot. */ static int sipWrapper_traverse(sipWrapper *self, visitproc visit, void *arg) { int vret; sipSimpleWrapper *sw = (sipSimpleWrapper *)self; sipWrapper *w; if ((vret = sipSimpleWrapper_traverse(sw, visit, arg)) != 0) return vret; for (w = self->first_child; w != NULL; w = w->sibling_next) { /* * We don't traverse if the wrapper is a child of itself. We do this * so that wrapped objects returned by virtual methods with the * /Factory/ don't have those objects collected. This then means that * plugins implemented in Python have a chance of working. */ if (w != self) if ((vret = visit((PyObject *)w, arg)) != 0) return vret; } return 0; } /* * Add the slots for a class type and all its super-types. */ static void addClassSlots(sipWrapperType *wt, const sipClassTypeDef *ctd) { PyHeapTypeObject *heap_to = &wt->super; PyBufferProcs *bp = &heap_to->as_buffer; /* Add the buffer interface. */ if (ctd->ctd_getbuffer != NULL) bp->bf_getbuffer = (getbufferproc)sipSimpleWrapper_getbuffer; if (ctd->ctd_releasebuffer != NULL) bp->bf_releasebuffer = (releasebufferproc)sipSimpleWrapper_releasebuffer; /* Add the slots for this type. */ if (ctd->ctd_pyslots != NULL) sip_add_type_slots(heap_to, ctd->ctd_pyslots); } /* * Add the slot handler for each slot present in the type. */ void sip_add_type_slots(PyHeapTypeObject *heap_to, sipPySlotDef *slots) { PyTypeObject *to; PyNumberMethods *nb; PySequenceMethods *sq; PyMappingMethods *mp; PyAsyncMethods *am; void *f; to = &heap_to->ht_type; nb = &heap_to->as_number; sq = &heap_to->as_sequence; mp = &heap_to->as_mapping; am = &heap_to->as_async; while ((f = slots->psd_func) != NULL) switch (slots++->psd_type) { case str_slot: to->tp_str = (reprfunc)f; break; case int_slot: nb->nb_int = (unaryfunc)f; break; case float_slot: nb->nb_float = (unaryfunc)f; break; case len_slot: mp->mp_length = (lenfunc)f; sq->sq_length = (lenfunc)f; break; case contains_slot: sq->sq_contains = (objobjproc)f; break; case add_slot: nb->nb_add = (binaryfunc)f; break; case concat_slot: sq->sq_concat = (binaryfunc)f; break; case sub_slot: nb->nb_subtract = (binaryfunc)f; break; case mul_slot: nb->nb_multiply = (binaryfunc)f; break; case repeat_slot: sq->sq_repeat = (ssizeargfunc)f; break; case div_slot: nb->nb_true_divide = (binaryfunc)f; break; case mod_slot: nb->nb_remainder = (binaryfunc)f; break; case floordiv_slot: nb->nb_floor_divide = (binaryfunc)f; break; case truediv_slot: nb->nb_true_divide = (binaryfunc)f; break; case and_slot: nb->nb_and = (binaryfunc)f; break; case or_slot: nb->nb_or = (binaryfunc)f; break; case xor_slot: nb->nb_xor = (binaryfunc)f; break; case lshift_slot: nb->nb_lshift = (binaryfunc)f; break; case rshift_slot: nb->nb_rshift = (binaryfunc)f; break; case iadd_slot: nb->nb_inplace_add = (binaryfunc)f; break; case iconcat_slot: sq->sq_inplace_concat = (binaryfunc)f; break; case isub_slot: nb->nb_inplace_subtract = (binaryfunc)f; break; case imul_slot: nb->nb_inplace_multiply = (binaryfunc)f; break; case irepeat_slot: sq->sq_inplace_repeat = (ssizeargfunc)f; break; case idiv_slot: nb->nb_inplace_true_divide = (binaryfunc)f; break; case imod_slot: nb->nb_inplace_remainder = (binaryfunc)f; break; case ifloordiv_slot: nb->nb_inplace_floor_divide = (binaryfunc)f; break; case itruediv_slot: nb->nb_inplace_true_divide = (binaryfunc)f; break; case iand_slot: nb->nb_inplace_and = (binaryfunc)f; break; case ior_slot: nb->nb_inplace_or = (binaryfunc)f; break; case ixor_slot: nb->nb_inplace_xor = (binaryfunc)f; break; case ilshift_slot: nb->nb_inplace_lshift = (binaryfunc)f; break; case irshift_slot: nb->nb_inplace_rshift = (binaryfunc)f; break; case invert_slot: nb->nb_invert = (unaryfunc)f; break; case call_slot: to->tp_call = slot_call; break; case getitem_slot: mp->mp_subscript = (binaryfunc)f; sq->sq_item = slot_sq_item; break; case setitem_slot: case delitem_slot: mp->mp_ass_subscript = slot_mp_ass_subscript; sq->sq_ass_item = slot_sq_ass_item; break; case lt_slot: case le_slot: case eq_slot: case ne_slot: case gt_slot: case ge_slot: to->tp_richcompare = slot_richcompare; break; case bool_slot: nb->nb_bool = (inquiry)f; break; case neg_slot: nb->nb_negative = (unaryfunc)f; break; case repr_slot: to->tp_repr = (reprfunc)f; break; case hash_slot: to->tp_hash = (hashfunc)f; break; case pos_slot: nb->nb_positive = (unaryfunc)f; break; case abs_slot: nb->nb_absolute = (unaryfunc)f; break; case index_slot: nb->nb_index = (unaryfunc)f; break; case iter_slot: to->tp_iter = (getiterfunc)f; break; case next_slot: to->tp_iternext = (iternextfunc)f; break; case setattr_slot: to->tp_setattro = (setattrofunc)f; break; case matmul_slot: nb->nb_matrix_multiply = (binaryfunc)f; break; case imatmul_slot: nb->nb_inplace_matrix_multiply = (binaryfunc)f; break; case await_slot: am->am_await = (unaryfunc)f; break; case aiter_slot: am->am_aiter = (unaryfunc)f; break; case anext_slot: am->am_anext = (unaryfunc)f; break; /* Suppress a compiler warning. */ default: ; } } /* * Remove the object from the map and call the C/C++ dtor if we own the * instance. */ static void forgetObject(sipSimpleWrapper *sw) { sipEventHandler *eh; const sipClassTypeDef *ctd = (const sipClassTypeDef *)((sipWrapperType *)Py_TYPE(sw))->wt_td; /* Invoke any event handlers. */ for (eh = event_handlers[sipEventCollectingWrapper]; eh != NULL; eh = eh->next) { if (is_subtype(ctd, eh->ctd)) { sipCollectingWrapperEventHandler handler = (sipCollectingWrapperEventHandler)eh->handler; handler(sw); } } /* * This is needed because we might release the GIL when calling a C++ dtor. * Without it the cyclic garbage collector can be invoked from another * thread resulting in a crash. */ PyObject_GC_UnTrack((PyObject *)sw); /* * Remove the object from the map before calling the class specific dealloc * code. This code calls the C++ dtor and may result in further calls that * pass the instance as an argument. If this is still in the map then it's * reference count would be increased (to one) and bad things happen when * it drops back to zero again. (An example is PyQt events generated * during the dtor call being passed to an event filter implemented in * Python.) By removing it from the map first we ensure that a new Python * object is created. */ sipOMRemoveObject(&cppPyMap, sw); if (sipInterpreter != NULL) { const sipClassTypeDef *ctd; if (getPtrTypeDef(sw, &ctd) != NULL && ctd->ctd_dealloc != NULL) ctd->ctd_dealloc(sw); } clear_access_func(sw); } /* * If the given name is that of a typedef then the corresponding type is * returned. */ static const char *sip_api_resolve_typedef(const char *name) { const sipExportedModuleDef *em; /* * Note that if the same name is defined as more than one type (which is * possible if more than one completely independent modules are being * used) then we might pick the wrong one. */ for (em = moduleList; em != NULL; em = em->em_next) { if (em->em_nrtypedefs > 0) { sipTypedefDef *tdd; tdd = (sipTypedefDef *)bsearch(name, em->em_typedefs, em->em_nrtypedefs, sizeof (sipTypedefDef), compareTypedefName); if (tdd != NULL) return tdd->tdd_type_name; } } return NULL; } /* * The bsearch() helper function for searching a sorted typedef table. */ static int compareTypedefName(const void *key, const void *el) { return strcmp((const char *)key, ((const sipTypedefDef *)el)->tdd_name); } /* * Add the given Python object to the given list. Return 0 if there was no * error. */ static int addPyObjectToList(sipPyObject **head, PyObject *object) { sipPyObject *po; if ((po = sip_api_malloc(sizeof (sipPyObject))) == NULL) return -1; po->object = object; po->next = *head; *head = po; return 0; } /* * Register a symbol with a name. A negative value is returned if the name was * already registered. */ static int sip_api_export_symbol(const char *name, void *sym) { sipSymbol *ss; if (sip_api_import_symbol(name) != NULL) return -1; if ((ss = sip_api_malloc(sizeof (sipSymbol))) == NULL) return -1; ss->name = name; ss->symbol = sym; ss->next = sipSymbolList; sipSymbolList = ss; return 0; } /* * Return the symbol registered with the given name. NULL is returned if the * name was not registered. */ static void *sip_api_import_symbol(const char *name) { sipSymbol *ss; for (ss = sipSymbolList; ss != NULL; ss = ss->next) if (strcmp(ss->name, name) == 0) return ss->symbol; return NULL; } /* * Convert a Python object to a character and raise an exception if there was * an error. */ static char sip_api_bytes_as_char(PyObject *obj) { char ch; if (parseBytes_AsChar(obj, &ch) < 0) { PyErr_Format(PyExc_TypeError, "bytes of length 1 expected not '%s'", Py_TYPE(obj)->tp_name); return '\0'; } return ch; } /* * Convert a Python object to a string and raise an exception if there was * an error. */ static const char *sip_api_bytes_as_string(PyObject *obj) { const char *a; if (parseBytes_AsString(obj, &a) < 0) { PyErr_Format(PyExc_TypeError, "bytes expected not '%s'", Py_TYPE(obj)->tp_name); return NULL; } return a; } /* * Convert a Python ASCII string object to a character and raise an exception * if there was an error. */ static char sip_api_string_as_ascii_char(PyObject *obj) { char ch; if (parseString_AsASCIIChar(obj, &ch) < 0) ch = '\0'; return ch; } /* * Parse an ASCII character and return it. */ static int parseString_AsASCIIChar(PyObject *obj, char *ap) { if (parseString_AsEncodedChar(PyUnicode_AsASCIIString(obj), obj, ap) < 0) { /* Use the exception set if it was an encoding error. */ if (!PyUnicode_Check(obj) || PyUnicode_GET_LENGTH(obj) != 1) PyErr_SetString(PyExc_TypeError, "bytes or ASCII string of length 1 expected"); return -1; } return 0; } /* * Convert a Python Latin-1 string object to a character and raise an exception * if there was an error. */ static char sip_api_string_as_latin1_char(PyObject *obj) { char ch; if (parseString_AsLatin1Char(obj, &ch) < 0) ch = '\0'; return ch; } /* * Parse a Latin-1 character and return it via a pointer. */ static int parseString_AsLatin1Char(PyObject *obj, char *ap) { if (parseString_AsEncodedChar(PyUnicode_AsLatin1String(obj), obj, ap) < 0) { /* Use the exception set if it was an encoding error. */ if (!PyUnicode_Check(obj) || PyUnicode_GET_LENGTH(obj) != 1) PyErr_SetString(PyExc_TypeError, "bytes or Latin-1 string of length 1 expected"); return -1; } return 0; } /* * Convert a Python UTF-8 string object to a character and raise an exception * if there was an error. */ static char sip_api_string_as_utf8_char(PyObject *obj) { char ch; if (parseString_AsUTF8Char(obj, &ch) < 0) ch = '\0'; return ch; } /* * Parse a UTF-8 character and return it. */ static int parseString_AsUTF8Char(PyObject *obj, char *ap) { if (parseString_AsEncodedChar(PyUnicode_AsUTF8String(obj), obj, ap) < 0) { /* Use the exception set if it was an encoding error. */ if (!PyUnicode_Check(obj) || PyUnicode_GET_LENGTH(obj) != 1) PyErr_SetString(PyExc_TypeError, "bytes or UTF-8 string of length 1 expected"); return -1; } return 0; } /* * Parse an encoded character and return it. */ static int parseString_AsEncodedChar(PyObject *bytes, PyObject *obj, char *ap) { Py_ssize_t size; if (bytes == NULL) { PyErr_Clear(); return parseBytes_AsChar(obj, ap); } size = PyBytes_GET_SIZE(bytes); if (size != 1) { Py_DECREF(bytes); return -1; } if (ap != NULL) *ap = *PyBytes_AS_STRING(bytes); Py_DECREF(bytes); return 0; } /* * Convert a Python ASCII string object to a string and raise an exception if * there was an error. The object is updated with the one that owns the * string. Note that None is considered an error. */ static const char *sip_api_string_as_ascii_string(PyObject **obj) { PyObject *s = *obj; const char *a; if (s == Py_None || (*obj = parseString_AsASCIIString(s, &a)) == NULL) { /* Use the exception set if it was an encoding error. */ if (!PyUnicode_Check(s)) PyErr_Format(PyExc_TypeError, "bytes or ASCII string expected not '%s'", Py_TYPE(s)->tp_name); return NULL; } return a; } /* * Parse an ASCII string and return it and a new reference to the object that * owns the string. */ static PyObject *parseString_AsASCIIString(PyObject *obj, const char **ap) { return parseString_AsEncodedString(PyUnicode_AsASCIIString(obj), obj, ap); } /* * Convert a Python Latin-1 string object to a string and raise an exception if * there was an error. The object is updated with the one that owns the * string. Note that None is considered an error. */ static const char *sip_api_string_as_latin1_string(PyObject **obj) { PyObject *s = *obj; const char *a; if (s == Py_None || (*obj = parseString_AsLatin1String(s, &a)) == NULL) { /* Use the exception set if it was an encoding error. */ if (!PyUnicode_Check(s)) PyErr_Format(PyExc_TypeError, "bytes or Latin-1 string expected not '%s'", Py_TYPE(s)->tp_name); return NULL; } return a; } /* * Parse a Latin-1 string and return it and a new reference to the object that * owns the string. */ static PyObject *parseString_AsLatin1String(PyObject *obj, const char **ap) { return parseString_AsEncodedString(PyUnicode_AsLatin1String(obj), obj, ap); } /* * Convert a Python UTF-8 string object to a string and raise an exception if * there was an error. The object is updated with the one that owns the * string. Note that None is considered an error. */ static const char *sip_api_string_as_utf8_string(PyObject **obj) { PyObject *s = *obj; const char *a; if (s == Py_None || (*obj = parseString_AsUTF8String(s, &a)) == NULL) { /* Use the exception set if it was an encoding error. */ if (!PyUnicode_Check(s)) PyErr_Format(PyExc_TypeError, "bytes or UTF-8 string expected not '%s'", Py_TYPE(s)->tp_name); return NULL; } return a; } /* * Parse a UTF-8 string and return it and a new reference to the object that * owns the string. */ static PyObject *parseString_AsUTF8String(PyObject *obj, const char **ap) { return parseString_AsEncodedString(PyUnicode_AsUTF8String(obj), obj, ap); } /* * Parse an encoded string and return it and a new reference to the object that * owns the string. */ static PyObject *parseString_AsEncodedString(PyObject *bytes, PyObject *obj, const char **ap) { if (bytes != NULL) { *ap = PyBytes_AS_STRING(bytes); return bytes; } /* Don't try anything else if there was an encoding error. */ if (PyUnicode_Check(obj)) return NULL; PyErr_Clear(); if (parseBytes_AsString(obj, ap) < 0) return NULL; Py_INCREF(obj); return obj; } /* * Parse a character array and return it's address and length. */ static int parseBytes_AsCharArray(PyObject *obj, const char **ap, Py_ssize_t *aszp) { const char *a; Py_ssize_t asz; if (obj == Py_None) { a = NULL; asz = 0; } else if (PyBytes_Check(obj)) { a = PyBytes_AS_STRING(obj); asz = PyBytes_GET_SIZE(obj); } else { Py_buffer view; if (PyObject_GetBuffer(obj, &view, PyBUF_SIMPLE) < 0) return -1; a = view.buf; asz = view.len; PyBuffer_Release(&view); } if (ap != NULL) *ap = a; if (aszp != NULL) *aszp = asz; return 0; } /* * Parse a character and return it. */ static int parseBytes_AsChar(PyObject *obj, char *ap) { const char *chp; Py_ssize_t sz; if (PyBytes_Check(obj)) { chp = PyBytes_AS_STRING(obj); sz = PyBytes_GET_SIZE(obj); } else { Py_buffer view; if (PyObject_GetBuffer(obj, &view, PyBUF_SIMPLE) < 0) return -1; chp = view.buf; sz = view.len; PyBuffer_Release(&view); } if (sz != 1) return -1; if (ap != NULL) *ap = *chp; return 0; } /* * Parse a character string and return it. */ static int parseBytes_AsString(PyObject *obj, const char **ap) { const char *a; Py_ssize_t sz; if (parseBytes_AsCharArray(obj, &a, &sz) < 0) return -1; if (ap != NULL) *ap = a; return 0; } #if defined(HAVE_WCHAR_H) /* * Convert a Python object to a wide character. */ static wchar_t sip_api_unicode_as_wchar(PyObject *obj) { wchar_t ch; if (parseWChar(obj, &ch) < 0) { PyErr_Format(PyExc_ValueError, "string of length 1 expected, not %s", Py_TYPE(obj)->tp_name); return L'\0'; } return ch; } /* * Convert a Python object to a wide character string on the heap. */ static wchar_t *sip_api_unicode_as_wstring(PyObject *obj) { wchar_t *p; if (parseWCharString(obj, &p) < 0) { PyErr_Format(PyExc_ValueError, "string expected, not %s", Py_TYPE(obj)->tp_name); return NULL; } return p; } /* * Parse a wide character array and return it's address and length. */ static int parseWCharArray(PyObject *obj, wchar_t **ap, Py_ssize_t *aszp) { wchar_t *a; Py_ssize_t asz; if (obj == Py_None) { a = NULL; asz = 0; } else if (PyUnicode_Check(obj)) { if (convertToWCharArray(obj, &a, &asz) < 0) return -1; } else { return -1; } if (ap != NULL) *ap = a; if (aszp != NULL) *aszp = asz; return 0; } /* * Convert a Unicode object to a wide character array and return it's address * and length. */ static int convertToWCharArray(PyObject *obj, wchar_t **ap, Py_ssize_t *aszp) { Py_ssize_t ulen; wchar_t *wc; ulen = PyUnicode_GET_LENGTH(obj); if ((wc = sip_api_malloc(ulen * sizeof (wchar_t))) == NULL) return -1; if ((ulen = PyUnicode_AsWideChar(obj, wc, ulen)) < 0) { sip_api_free(wc); return -1; } *ap = wc; *aszp = ulen; return 0; } /* * Parse a wide character and return it. */ static int parseWChar(PyObject *obj, wchar_t *ap) { wchar_t a; if (PyUnicode_Check(obj)) { if (convertToWChar(obj, &a) < 0) return -1; } else { return -1; } if (ap != NULL) *ap = a; return 0; } /* * Convert a Unicode object to a wide character and return it. */ static int convertToWChar(PyObject *obj, wchar_t *ap) { if (PyUnicode_GET_LENGTH(obj) != 1) return -1; if (PyUnicode_AsWideChar(obj, ap, 1) != 1) return -1; return 0; } /* * Parse a wide character string and return a copy on the heap. */ static int parseWCharString(PyObject *obj, wchar_t **ap) { wchar_t *a; if (obj == Py_None) { a = NULL; } else if (PyUnicode_Check(obj)) { if (convertToWCharString(obj, &a) < 0) return -1; } else { return -1; } if (ap != NULL) *ap = a; return 0; } /* * Convert a Unicode object to a wide character string and return a copy on * the heap. */ static int convertToWCharString(PyObject *obj, wchar_t **ap) { Py_ssize_t ulen; wchar_t *wc; ulen = PyUnicode_GET_LENGTH(obj); if ((wc = sip_api_malloc((ulen + 1) * sizeof (wchar_t))) == NULL) return -1; if ((ulen = PyUnicode_AsWideChar(obj, wc, ulen)) < 0) { sip_api_free(wc); return -1; } wc[ulen] = L'\0'; *ap = wc; return 0; } #else /* * Convert a Python object to a wide character. */ static int sip_api_unicode_as_wchar(PyObject *obj) { raiseNoWChar(); return 0; } /* * Convert a Python object to a wide character. */ static int *sip_api_unicode_as_wstring(PyObject *obj) { raiseNoWChar(); return NULL; } /* * Report the need for absent wide character support. */ static void raiseNoWChar() { PyErr_SetString(PyExc_SystemError, _SIP_MODULE_FQ_NAME " built without wchar_t support"); } #endif /* * Check if an object is of the right type to convert to an encoded string. */ static int check_encoded_string(PyObject *obj) { Py_buffer view; if (obj == Py_None) return 0; if (PyUnicode_Check(obj)) return 0; if (PyBytes_Check(obj)) return 0; if (PyObject_GetBuffer(obj, &view, PyBUF_SIMPLE) < 0) { PyErr_Clear(); } else { PyBuffer_Release(&view); return 0; } return -1; } /* * This is called by the atexit module. */ static PyObject *sip_exit(PyObject *self, PyObject *args) { (void)self; (void)args; /* Disable all Python reimplementations of virtuals. */ sipInterpreter = NULL; Py_INCREF(Py_None); return Py_None; } /* * Register an exit notifier with the atexit module. */ static int sip_api_register_exit_notifier(PyMethodDef *md) { static PyObject *register_func = NULL; PyObject *notifier, *res; if (register_func == NULL && (register_func = import_module_attr("atexit", "register")) == NULL) return -1; if ((notifier = PyCFunction_New(md, NULL)) == NULL) return -1; res = PyObject_CallFunctionObjArgs(register_func, notifier, NULL); Py_DECREF(notifier); if (res == NULL) return -1; Py_DECREF(res); return 0; } /* * Return the function that converts a C++ instance to a Python object. */ static sipConvertFromFunc get_from_convertor(const sipTypeDef *td) { if (sipTypeIsMapped(td)) return ((const sipMappedTypeDef *)td)->mtd_cfrom; assert(sipTypeIsClass(td)); if (autoconversion_disabled(td) != NULL) return NULL; return ((const sipClassTypeDef *)td)->ctd_cfrom; } /* * Enable or disable the auto-conversion. Returns the previous enabled state * or -1 on error. */ static int sip_api_enable_autoconversion(const sipTypeDef *td, int enable) { sipPyObject **pop; assert(sipTypeIsClass(td)); pop = autoconversion_disabled(td); /* See if there is anything to do. */ if (pop == NULL && enable) return TRUE; if (pop != NULL && !enable) return FALSE; if (pop != NULL) { /* Remove it from the list. */ sipPyObject *po = *pop; *pop = po->next; sip_api_free(po); } else { /* Add it to the list. */ if (addPyObjectToList(&sipDisabledAutoconversions, (PyObject *)sipTypeAsPyTypeObject(td)) < 0) return -1; } return !enable; } /* * Return a pointer to the entry in the list of disabled auto-conversions for a * type. */ static sipPyObject **autoconversion_disabled(const sipTypeDef *td) { PyObject *type = (PyObject *)sipTypeAsPyTypeObject(td); sipPyObject **pop; for (pop = &sipDisabledAutoconversions; *pop != NULL; pop = &(*pop)->next) if ((*pop)->object == type) return pop; return NULL; } /* * Enable or disable auto-conversion of a class that supports it. */ static PyObject *enableAutoconversion(PyObject *self, PyObject *args) { sipWrapperType *wt; int enable; (void)self; if (PyArg_ParseTuple(args, "O!i:enableautoconversion", &sipWrapperType_Type, &wt, &enable)) { sipTypeDef *td = wt->wt_td; int was_enabled; PyObject *res; if (!sipTypeIsClass(td) || ((sipClassTypeDef *)td)->ctd_cfrom == NULL) { PyErr_Format(PyExc_TypeError, "%s is not a wrapped class that supports optional auto-conversion", ((PyTypeObject *)wt)->tp_name); return NULL; } if ((was_enabled = sip_api_enable_autoconversion(td, enable)) < 0) return NULL; res = (was_enabled ? Py_True : Py_False); Py_INCREF(res); return res; } return NULL; } /* * Python copies the nb_inplace_add slot to the sq_inplace_concat slot and vice * versa if either are missing. This is a bug because they don't have the same * API. We therefore reverse this. */ static void fix_slots(PyTypeObject *py_type, sipPySlotDef *psd) { while (psd->psd_func != NULL) { if (psd->psd_type == iadd_slot && py_type->tp_as_sequence != NULL) py_type->tp_as_sequence->sq_inplace_concat = NULL; if (psd->psd_type == iconcat_slot && py_type->tp_as_number != NULL) py_type->tp_as_number->nb_inplace_add = NULL; ++psd; } } /* * Return the main instance for an object if it is a mixin. */ static sipSimpleWrapper *deref_mixin(sipSimpleWrapper *w) { return w->mixin_main != NULL ? (sipSimpleWrapper *)w->mixin_main : w; } /* * Convert a new C/C++ pointer to a Python instance. */ static PyObject *wrap_simple_instance(void *cpp, const sipTypeDef *td, sipWrapper *owner, int flags) { return sipWrapInstance(cpp, sipTypeAsPyTypeObject(td), empty_tuple, owner, flags); } /* * Resolve a proxy, if applicable. */ static void *resolve_proxy(const sipTypeDef *td, void *proxy) { sipProxyResolver *pr; /* TODO: Deprecate this mechanism in favour of an event handler. */ for (pr = proxyResolvers; pr != NULL; pr = pr->next) if (pr->td == td) proxy = pr->resolver(proxy); return proxy; } /* * Clear a simple wrapper. */ static void clear_wrapper(sipSimpleWrapper *sw) { if (PyObject_TypeCheck((PyObject *)sw, (PyTypeObject *)&sipWrapper_Type)) removeFromParent((sipWrapper *)sw); /* * Transfer ownership to C++ so we don't try to release it when the * Python object is garbage collected. */ sipResetPyOwned(sw); sipOMRemoveObject(&cppPyMap, sw); clear_access_func(sw); } /* * Set the user-specific type data. */ static void sip_api_set_type_user_data(sipWrapperType *wt, void *data) { wt->wt_user_data = data; } /* * Get the user-specific type data. */ static void *sip_api_get_type_user_data(const sipWrapperType *wt) { return wt->wt_user_data; } /* * Get a borrowed reference to the dict of a Python type (on behalf of the * limited API). This is deprecated in ABI v13.6 and must not be used with * Python v3.12 and later. */ static PyObject *sip_api_py_type_dict(const PyTypeObject *py_type) { PyErr_WarnEx(PyExc_DeprecationWarning, "sipPyTypeDict() is deprecated, the extension module should use " "sipPyTypeDictRef() instead", 1); return py_type->tp_dict; } /* * Get a new reference to the dict of a Python type (on behalf of the limited * API). */ static PyObject *sip_api_py_type_dict_ref(PyTypeObject *py_type) { #if PY_VERSION_HEX >= 0x030c0000 return PyType_GetDict(py_type); #else PyObject *ref = py_type->tp_dict; Py_XINCREF(ref); return ref; #endif } /* * Get the name of a Python type (on behalf of the limited API). */ static const char *sip_api_py_type_name(const PyTypeObject *py_type) { return py_type->tp_name; } /* * Check an object is a method and return TRUE and its component parts if it * is. */ static int sip_api_get_method(PyObject *obj, sipMethodDef *method) { if (!PyMethod_Check(obj)) return FALSE; if (method != NULL) { method->pm_self = PyMethod_GET_SELF(obj); method->pm_function = PyMethod_GET_FUNCTION(obj); } return TRUE; } /* * Create a method from its component parts. */ static PyObject *sip_api_from_method(const sipMethodDef *method) { return PyMethod_New(method->pm_function, method->pm_self); } /* * Check an object is a C function and return TRUE and its component parts if * it is. */ static int sip_api_get_c_function(PyObject *obj, sipCFunctionDef *c_function) { if (!PyCFunction_Check(obj)) return FALSE; if (c_function != NULL) { c_function->cf_function = ((PyCFunctionObject *)obj)->m_ml; c_function->cf_self = PyCFunction_GET_SELF(obj); } return TRUE; } /* * Check an object is a date and return TRUE and its component parts if it is. */ static int sip_api_get_date(PyObject *obj, sipDateDef *date) { if (!PyDateTimeAPI) PyDateTime_IMPORT; if (!PyDate_Check(obj)) return FALSE; if (date != NULL) { date->pd_year = PyDateTime_GET_YEAR(obj); date->pd_month = PyDateTime_GET_MONTH(obj); date->pd_day = PyDateTime_GET_DAY(obj); } return TRUE; } /* * Create a date from its component parts. */ static PyObject *sip_api_from_date(const sipDateDef *date) { if (!PyDateTimeAPI) PyDateTime_IMPORT; return PyDate_FromDate(date->pd_year, date->pd_month, date->pd_day); } /* * Check an object is a datetime and return TRUE and its component parts if it * is. */ static int sip_api_get_datetime(PyObject *obj, sipDateDef *date, sipTimeDef *time) { if (!PyDateTimeAPI) PyDateTime_IMPORT; if (!PyDateTime_Check(obj)) return FALSE; if (date != NULL) { date->pd_year = PyDateTime_GET_YEAR(obj); date->pd_month = PyDateTime_GET_MONTH(obj); date->pd_day = PyDateTime_GET_DAY(obj); } if (time != NULL) { time->pt_hour = PyDateTime_DATE_GET_HOUR(obj); time->pt_minute = PyDateTime_DATE_GET_MINUTE(obj); time->pt_second = PyDateTime_DATE_GET_SECOND(obj); time->pt_microsecond = PyDateTime_DATE_GET_MICROSECOND(obj); } return TRUE; } /* * Create a datetime from its component parts. */ static PyObject *sip_api_from_datetime(const sipDateDef *date, const sipTimeDef *time) { if (!PyDateTimeAPI) PyDateTime_IMPORT; return PyDateTime_FromDateAndTime(date->pd_year, date->pd_month, date->pd_day, time->pt_hour, time->pt_minute, time->pt_second, time->pt_microsecond); } /* * Check an object is a time and return TRUE and its component parts if it is. */ static int sip_api_get_time(PyObject *obj, sipTimeDef *time) { if (!PyDateTimeAPI) PyDateTime_IMPORT; if (!PyTime_Check(obj)) return FALSE; if (time != NULL) { time->pt_hour = PyDateTime_TIME_GET_HOUR(obj); time->pt_minute = PyDateTime_TIME_GET_MINUTE(obj); time->pt_second = PyDateTime_TIME_GET_SECOND(obj); time->pt_microsecond = PyDateTime_TIME_GET_MICROSECOND(obj); } return TRUE; } /* * Create a time from its component parts. */ static PyObject *sip_api_from_time(const sipTimeDef *time) { if (!PyDateTimeAPI) PyDateTime_IMPORT; return PyTime_FromTime(time->pt_hour, time->pt_minute, time->pt_second, time->pt_microsecond); } /* * See if a type is user defined. */ static int sip_api_is_user_type(const sipWrapperType *wt) { return wt->wt_user_type; } /* * Check if a type was generated using the given plugin. Note that, although * this is part of the public API it is undocumented on purpose. */ static int sip_api_check_plugin_for_type(const sipTypeDef *td, const char *name) { /* * The current thinking on plugins is that SIP v7 will look for a plugin * with a name derived from the name as the current module in the same * directory as the .sip defining the module (ie. no %Plugin directive). A * module hierachy may have multiple plugins but they must co-operate. If * a plugin generates user data then it should include a void* (and a * run-time API) so that other plugins can extend it further. This * approach means that a plugin's user data structure can be opaque. */ sipExportedModuleDef *em = td->td_module; sipImportedModuleDef *im; if (strcmp(sipNameOfModule(em), name) == 0) return TRUE; if ((im = em->em_imports) == NULL) return FALSE; while (im->im_name != NULL) { if (strcmp(im->im_name, name) == 0) return TRUE; ++im; } return FALSE; } /* * Create a new Unicode object and return the character size and buffer. */ static PyObject *sip_api_unicode_new(Py_ssize_t len, unsigned maxchar, int *kind, void **data) { PyObject *obj; if ((obj = PyUnicode_New(len, maxchar)) != NULL) { *kind = PyUnicode_KIND(obj); *data = PyUnicode_DATA(obj); } return obj; } /* * Update a new Unicode object with a new character. */ static void sip_api_unicode_write(int kind, void *data, int index, unsigned value) { PyUnicode_WRITE(kind, data, index, value); } /* * Get the address of the contents of a Unicode object, the character size and * the length. */ static void *sip_api_unicode_data(PyObject *obj, int *char_size, Py_ssize_t *len) { void *data; /* Assume there will be an error. */ *char_size = -1; if (PyUnicode_READY(obj) < 0) return NULL; *len = PyUnicode_GET_LENGTH(obj); switch (PyUnicode_KIND(obj)) { case PyUnicode_1BYTE_KIND: *char_size = 1; data = PyUnicode_1BYTE_DATA(obj); break; case PyUnicode_2BYTE_KIND: *char_size = 2; data = PyUnicode_2BYTE_DATA(obj); break; case PyUnicode_4BYTE_KIND: *char_size = 4; data = PyUnicode_4BYTE_DATA(obj); break; default: data = NULL; } return data; } /* * Get the buffer information supplied by an object that supports the buffer * protocol. */ static int sip_api_get_buffer_info(PyObject *obj, sipBufferInfoDef *bi) { int rc; Py_buffer *buffer; if (!PyObject_CheckBuffer(obj)) return 0; if (bi == NULL) return 1; if ((bi->bi_internal = sip_api_malloc(sizeof (Py_buffer))) == NULL) return -1; buffer = (Py_buffer *)bi->bi_internal; if (PyObject_GetBuffer(obj, buffer, PyBUF_FORMAT) < 0) return -1; if (buffer->ndim == 1) { bi->bi_buf = buffer->buf; bi->bi_obj = buffer->obj; bi->bi_len = buffer->len; bi->bi_readonly = buffer->readonly; bi->bi_format = buffer->format; rc = 1; } else { PyErr_SetString(PyExc_TypeError, "a 1-dimensional buffer is required"); PyBuffer_Release(buffer); rc = -1; } return rc; } /* * Release the buffer information obtained from a previous call to * sipGetBufferInfo(). */ static void sip_api_release_buffer_info(sipBufferInfoDef *bi) { if (bi->bi_internal != NULL) { PyBuffer_Release((Py_buffer *)bi->bi_internal); sip_api_free(bi->bi_internal); bi->bi_internal = NULL; } } /* * Import all the required types from an imported module. */ static int importTypes(sipExportedModuleDef *client, sipImportedModuleDef *im, sipExportedModuleDef *em) { const char *name; int i, e; /* * Look for each required type in turn. Both tables are sorted so a single * pass will find them all. */ for (i = e = 0; (name = im->im_imported_types[i].it_name) != NULL; ++i) { sipTypeDef *td = NULL; do { sipTypeDef *e_td; if (e >= em->em_nrtypes) { PyErr_Format(PyExc_RuntimeError, "%s cannot import type '%s' from %s", sipNameOfModule(client), name, sipNameOfModule(em)); return -1; } e_td = em->em_types[e++]; /* Ignore unresolved external types. */ if (e_td != NULL && strcmp(name, sipTypeName(e_td)) == 0) td = e_td; } while (td == NULL); im->im_imported_types[i].it_td = td; } return 0; } /* * Import all the required virtual error handlers from an imported module. */ static int importErrorHandlers(sipExportedModuleDef *client, sipImportedModuleDef *im, sipExportedModuleDef *em) { const char *name; int i; for (i = 0; (name = im->im_imported_veh[i].iveh_name) != NULL; ++i) { sipVirtErrorHandlerDef *veh = em->em_virterrorhandlers; sipVirtErrorHandlerFunc handler = NULL; if (veh != NULL) { while (veh->veh_name != NULL) { if (strcmp(veh->veh_name, name) == 0) { handler = veh->veh_handler; break; } ++veh; } } if (handler == NULL) { PyErr_Format(PyExc_RuntimeError, "%s cannot import virtual error handler '%s' from %s", sipNameOfModule(client), name, sipNameOfModule(em)); return -1; } im->im_imported_veh[i].iveh_handler = handler; } return 0; } /* * Import all the required exceptions from an imported module. */ static int importExceptions(sipExportedModuleDef *client, sipImportedModuleDef *im, sipExportedModuleDef *em) { const char *name; int i; for (i = 0; (name = im->im_imported_exceptions[i].iexc_name) != NULL; ++i) { PyObject **exc = em->em_exceptions; PyObject *exception = NULL; if (exc != NULL) { while (*exc != NULL) { if (strcmp(((PyTypeObject *)(*exc))->tp_name, name) == 0) { exception = *exc; break; } ++exc; } } if (exception == NULL) { PyErr_Format(PyExc_RuntimeError, "%s cannot import exception '%s' from %s", sipNameOfModule(client), name, sipNameOfModule(em)); return -1; } im->im_imported_exceptions[i].iexc_object = exception; } return 0; } /* * Enable or disable the garbage collector. Return the previous state or -1 if * there was an error. */ static int sip_api_enable_gc(int enable) { static PyObject *enable_func = NULL, *disable_func, *isenabled_func; PyObject *result; int was_enabled; /* * This may be -ve in the highly unusual event that a previous call failed. */ if (enable < 0) return -1; /* Get the functions if we haven't already got them. */ if (enable_func == NULL) { PyObject *gc_module; if ((gc_module = PyImport_ImportModule("gc")) == NULL) return -1; if ((enable_func = PyObject_GetAttrString(gc_module, "enable")) == NULL) { Py_DECREF(gc_module); return -1; } if ((disable_func = PyObject_GetAttrString(gc_module, "disable")) == NULL) { Py_DECREF(enable_func); Py_DECREF(gc_module); return -1; } if ((isenabled_func = PyObject_GetAttrString(gc_module, "isenabled")) == NULL) { Py_DECREF(disable_func); Py_DECREF(enable_func); Py_DECREF(gc_module); return -1; } Py_DECREF(gc_module); } /* Get the current state. */ if ((result = PyObject_Call(isenabled_func, empty_tuple, NULL)) == NULL) return -1; was_enabled = PyObject_IsTrue(result); Py_DECREF(result); if (was_enabled < 0) return -1; /* See if the state needs changing. */ if (!was_enabled != !enable) { /* Enable or disable as required. */ result = PyObject_Call((enable ? enable_func : disable_func), empty_tuple, NULL); Py_XDECREF(result); if (result != Py_None) return -1; } return was_enabled; } /* * A thin wrapper around PyObject_Print() usually used when debugging with the * limited API. */ static void sip_api_print_object(PyObject *o) { PyObject_Print(o, stdout, 0); } /* * Register a handler for a particular event. */ static int sip_api_register_event_handler(sipEventType type, const sipTypeDef *td, void *handler) { sipEventHandler *eh; assert(sipTypeIsClass(td)); if ((eh = sip_api_malloc(sizeof (sipEventHandler))) == NULL) return -1; eh->ctd = (const sipClassTypeDef *)td; eh->handler = handler; eh->next = event_handlers[(int)type]; event_handlers[(int)type] = eh; return 0; } /* * Returns TRUE if a generated class type is a sub-class of a base generated * class type. */ static int is_subtype(const sipClassTypeDef *ctd, const sipClassTypeDef *base_ctd) { const sipEncodedTypeDef *sup; /* Handle the trivial cases. */ if (ctd == base_ctd) return TRUE; if ((sup = ctd->ctd_supers) == NULL) return FALSE; /* Search the super-types. */ do { const sipClassTypeDef *sup_ctd = sipGetGeneratedClassType(sup, ctd); if (is_subtype(sup_ctd, base_ctd)) return TRUE; } while (!sup++->sc_flag); return FALSE; } /* * Return an attribute of an imported module. */ static PyObject *import_module_attr(const char *module, const char *attr) { PyObject *mod_obj, *attr_obj; if ((mod_obj = PyImport_ImportModule(module)) == NULL) return NULL; attr_obj = PyObject_GetAttrString(mod_obj, attr); Py_DECREF(mod_obj); return attr_obj; } /* * Get the container for a generated type. */ static const sipContainerDef *get_container(const sipTypeDef *td) { if (sipTypeIsMapped(td)) return &((const sipMappedTypeDef *)td)->mtd_container; return &((const sipClassTypeDef *)td)->ctd_container; } /* * Get the __qualname__ of an object based on its enclosing scope. */ PyObject *sip_get_qualname(const sipTypeDef *td, PyObject *name) { PyTypeObject *scope_type; /* Get the type that is the scope. */ scope_type = sipTypeAsPyTypeObject(td); return PyUnicode_FromFormat("%U.%U", ((PyHeapTypeObject *)scope_type)->ht_qualname, name); } /* * Unpack a slice object. */ int sip_api_convert_from_slice_object(PyObject *slice, Py_ssize_t length, Py_ssize_t *start, Py_ssize_t *stop, Py_ssize_t *step, Py_ssize_t *slicelength) { if (PySlice_Unpack(slice, start, stop, step) < 0) return -1; *slicelength = PySlice_AdjustIndices(length, start, stop, *step); return 0; } /* * Call a visitor function for every wrapped object. */ static void sip_api_visit_wrappers(sipWrapperVisitorFunc visitor, void *closure) { const sipHashEntry *he; unsigned long i; for (he = cppPyMap.hash_array, i = 0; i < cppPyMap.size; ++i, ++he) { if (he->key != NULL) { sipSimpleWrapper *sw; for (sw = he->first; sw != NULL; sw = sw->next) visitor(sw, closure); } } } /* * Raise an exception when there is no mapped type converter to convert from * C/C++ to Python. */ static void raise_no_convert_from(const sipTypeDef *td) { PyErr_Format(PyExc_TypeError, "%s cannot be converted to a Python object", sipTypeName(td)); } /* * Raise an exception when there is no mapped type converter to convert to * C/C++ from Python. */ static void raise_no_convert_to(PyObject *py, const sipTypeDef *td) { PyErr_Format(PyExc_TypeError, "%s cannot be converted to %s", Py_TYPE(py)->tp_name, sipTypeName(td)); } /* * Check that a user state pointer has been provided if the type requires it. * This is most likely a problem with handwritten code. */ static int user_state_is_valid(const sipTypeDef *td, void **user_statep) { if (sipTypeNeedsUserState(td) && user_statep == NULL) { PyErr_Format(PyExc_RuntimeError, "%s requires user state but none is provided", sipTypeName(td)); return FALSE; } return TRUE; } /* * Return the next exception handler. The order is undefined. */ sipExceptionHandler sip_api_next_exception_handler(void **statep) { sipExportedModuleDef *em = *(sipExportedModuleDef **)statep; if (em != NULL) em = em->em_next; else em = moduleList; while (em->em_exception_handler == NULL) if ((em = em->em_next) == NULL) return NULL; *statep = em; return em->em_exception_handler; } sip-6.8.6/sipbuild/module/source/13/sip_core.h000066400000000000000000000114711464421045000211310ustar00rootroot00000000000000/* SPDX-License-Identifier: BSD-2-Clause */ /* * This file defines the core sip module internal interfaces. * * Copyright (c) 2024 Phil Thompson */ #ifndef _SIP_CORE_H #define _SIP_CORE_H /* Remove when Python v3.12 is no longer supported. */ #define PY_SSIZE_T_CLEAN #include #include #include #include "sip.h" #ifdef __cplusplus extern "C" { #endif #undef TRUE #define TRUE 1 #undef FALSE #define FALSE 0 /* * This defines a single entry in an object map's hash table. */ typedef struct { void *key; /* The C/C++ address. */ sipSimpleWrapper *first; /* The first object at this address. */ } sipHashEntry; /* * This defines the interface to a hash table class for mapping C/C++ addresses * to the corresponding wrapped Python object. */ typedef struct { int primeIdx; /* Index into table sizes. */ uintptr_t size; /* Size of hash table. */ uintptr_t unused; /* Nr. unused in hash table. */ uintptr_t stale; /* Nr. stale in hash table. */ sipHashEntry *hash_array; /* Current hash table. */ } sipObjectMap; /* * Support for the descriptors. */ extern PyTypeObject sipMethodDescr_Type; PyObject *sipMethodDescr_New(PyMethodDef *pmd); PyObject *sipMethodDescr_Copy(PyObject *orig, PyObject *mixin_name); extern PyTypeObject sipVariableDescr_Type; PyObject *sipVariableDescr_New(sipVariableDef *vd, const sipTypeDef *td, const sipContainerDef *cod); PyObject *sipVariableDescr_Copy(PyObject *orig, PyObject *mixin_name); /* * Support for void pointers. */ extern PyTypeObject sipVoidPtr_Type; void *sip_api_convert_to_void_ptr(PyObject *obj); PyObject *sip_api_convert_from_void_ptr(void *val); PyObject *sip_api_convert_from_const_void_ptr(const void *val); PyObject *sip_api_convert_from_void_ptr_and_size(void *val, Py_ssize_t size); PyObject *sip_api_convert_from_const_void_ptr_and_size(const void *val, Py_ssize_t size); /* * Support for int convertors. */ int sip_api_convert_to_bool(PyObject *o); char sip_api_long_as_char(PyObject *o); signed char sip_api_long_as_signed_char(PyObject *o); unsigned char sip_api_long_as_unsigned_char(PyObject *o); short sip_api_long_as_short(PyObject *o); unsigned short sip_api_long_as_unsigned_short(PyObject *o); int sip_api_long_as_int(PyObject *o); unsigned int sip_api_long_as_unsigned_int(PyObject *o); long sip_api_long_as_long(PyObject *o); unsigned long sip_api_long_as_unsigned_long(PyObject *o); long long sip_api_long_as_long_long(PyObject *o); unsigned long long sip_api_long_as_unsigned_long_long(PyObject *o); size_t sip_api_long_as_size_t(PyObject *o); extern PyTypeObject sipWrapperType_Type; /* The wrapper type type. */ extern sipWrapperType sipSimpleWrapper_Type; /* The simple wrapper type. */ /* * These are part of the SIP API but are also used within the SIP module. */ void *sip_api_malloc(size_t nbytes); void sip_api_free(void *mem); void *sip_api_get_address(sipSimpleWrapper *w); void *sip_api_get_cpp_ptr(sipSimpleWrapper *w, const sipTypeDef *td); PyObject *sip_api_convert_from_type(void *cppPtr, const sipTypeDef *td, PyObject *transferObj); void sip_api_instance_destroyed(sipSimpleWrapper *sipSelf); void sip_api_end_thread(void); void *sip_api_force_convert_to_type_us(PyObject *pyObj, const sipTypeDef *td, PyObject *transferObj, int flags, int *statep, void **user_statep, int *iserrp); int sip_api_convert_from_slice_object(PyObject *slice, Py_ssize_t length, Py_ssize_t *start, Py_ssize_t *stop, Py_ssize_t *step, Py_ssize_t *slicelength); int sip_api_deprecated(const char *classname, const char *method); const sipTypeDef *sip_api_type_scope(const sipTypeDef *td); /* * These are not part of the SIP API but are used within the SIP module. */ int sip_add_all_lazy_attrs(const sipTypeDef *td); void sip_add_type_slots(PyHeapTypeObject *heap_to, sipPySlotDef *slots); int sip_dict_set_and_discard(PyObject *dict, const char *name, PyObject *obj); PyObject *sip_get_qualname(const sipTypeDef *td, PyObject *name); int sip_objectify(const char *s, PyObject **objp); sipClassTypeDef *sipGetGeneratedClassType(const sipEncodedTypeDef *enc, const sipClassTypeDef *ctd); int sipGetPending(void **pp, sipWrapper **op, int *fp); int sipIsPending(void); PyObject *sipWrapInstance(void *cpp, PyTypeObject *py_type, PyObject *args, sipWrapper *owner, int flags); void sipOMInit(sipObjectMap *om); void sipOMFinalise(sipObjectMap *om); sipSimpleWrapper *sipOMFindObject(sipObjectMap *om, void *key, const sipTypeDef *td); void sipOMAddObject(sipObjectMap *om, sipSimpleWrapper *val); int sipOMRemoveObject(sipObjectMap *om, sipSimpleWrapper *val); #define sip_set_bool(p, v) (*(_Bool *)(p) = (v)) #ifdef __cplusplus } #endif #endif sip-6.8.6/sipbuild/module/source/13/sip_descriptors.c000066400000000000000000000322051464421045000225330ustar00rootroot00000000000000/* SPDX-License-Identifier: BSD-2-Clause */ /* * The implementation of the different descriptors. * * Copyright (c) 2024 Phil Thompson */ /* Remove when Python v3.12 is no longer supported. */ #define PY_SSIZE_T_CLEAN #include #include "sip_core.h" /***************************************************************************** * A method descriptor. We don't use the similar Python descriptor because it * doesn't support a method having static and non-static overloads, and we * handle mixins via a delegate. *****************************************************************************/ /* Forward declarations of slots. */ static PyObject *sipMethodDescr_descr_get(PyObject *self, PyObject *obj, PyObject *type); static PyObject *sipMethodDescr_repr(PyObject *self); static int sipMethodDescr_traverse(PyObject *self, visitproc visit, void *arg); static int sipMethodDescr_clear(PyObject *self); static void sipMethodDescr_dealloc(PyObject *self); /* * The object data structure. */ typedef struct _sipMethodDescr { PyObject_HEAD /* The method definition. */ PyMethodDef *pmd; /* The mixin name, if any. */ PyObject *mixin_name; } sipMethodDescr; /* * The type data structure. */ PyTypeObject sipMethodDescr_Type = { PyVarObject_HEAD_INIT(NULL, 0) "sip.methoddescriptor", /* tp_name */ sizeof (sipMethodDescr), /* tp_basicsize */ 0, /* tp_itemsize */ sipMethodDescr_dealloc, /* tp_dealloc */ 0, /* tp_print */ 0, /* tp_getattr */ 0, /* tp_setattr */ 0, /* tp_compare */ sipMethodDescr_repr, /* tp_repr */ 0, /* tp_as_number */ 0, /* tp_as_sequence */ 0, /* tp_as_mapping */ 0, /* tp_hash */ 0, /* tp_call */ 0, /* tp_str */ 0, /* tp_getattro */ 0, /* tp_setattro */ 0, /* tp_as_buffer */ Py_TPFLAGS_DEFAULT|Py_TPFLAGS_HAVE_GC, /* tp_flags */ 0, /* tp_doc */ sipMethodDescr_traverse,/* tp_traverse */ sipMethodDescr_clear, /* tp_clear */ 0, /* tp_richcompare */ 0, /* tp_weaklistoffset */ 0, /* tp_iter */ 0, /* tp_iternext */ 0, /* tp_methods */ 0, /* tp_members */ 0, /* tp_getset */ 0, /* tp_base */ 0, /* tp_dict */ sipMethodDescr_descr_get, /* tp_descr_get */ 0, /* tp_descr_set */ 0, /* tp_dictoffset */ 0, /* tp_init */ 0, /* tp_alloc */ 0, /* tp_new */ 0, /* tp_free */ 0, /* tp_is_gc */ 0, /* tp_bases */ 0, /* tp_mro */ 0, /* tp_cache */ 0, /* tp_subclasses */ 0, /* tp_weaklist */ 0, /* tp_del */ 0, /* tp_version_tag */ 0, /* tp_finalize */ 0, /* tp_vectorcall */ }; /* * Return a new method descriptor for the given method. */ PyObject *sipMethodDescr_New(PyMethodDef *pmd) { PyObject *descr = PyType_GenericAlloc(&sipMethodDescr_Type, 0); if (descr != NULL) { ((sipMethodDescr *)descr)->pmd = pmd; ((sipMethodDescr *)descr)->mixin_name = NULL; } return descr; } /* * Return a new method descriptor based on an existing one and a mixin name. */ PyObject *sipMethodDescr_Copy(PyObject *orig, PyObject *mixin_name) { PyObject *descr = PyType_GenericAlloc(&sipMethodDescr_Type, 0); if (descr != NULL) { ((sipMethodDescr *)descr)->pmd = ((sipMethodDescr *)orig)->pmd; ((sipMethodDescr *)descr)->mixin_name = mixin_name; Py_INCREF(mixin_name); } return descr; } /* * The descriptor's descriptor get slot. */ static PyObject *sipMethodDescr_descr_get(PyObject *self, PyObject *obj, PyObject *type) { sipMethodDescr *md = (sipMethodDescr *)self; PyObject *bind, *func; if (obj == NULL) { /* The argument parser must work out that 'self' is the type object. */ bind = type; Py_INCREF(bind); } else if (md->mixin_name != NULL) { bind = PyObject_GetAttr(obj, md->mixin_name); } else { /* * The argument parser must work out that 'self' is the instance * object. */ bind = obj; Py_INCREF(bind); } func = PyCFunction_New(md->pmd, bind); Py_DECREF(bind); return func; } /* * The descriptor's repr slot. This is for the benefit of cProfile which seems * to determine attribute names differently to the rest of Python. */ static PyObject *sipMethodDescr_repr(PyObject *self) { sipMethodDescr *md = (sipMethodDescr *)self; return PyUnicode_FromFormat("", md->pmd->ml_name); } /* * The descriptor's traverse slot. */ static int sipMethodDescr_traverse(PyObject *self, visitproc visit, void *arg) { if (((sipMethodDescr *)self)->mixin_name != NULL) { int vret = visit(((sipMethodDescr *)self)->mixin_name, arg); if (vret != 0) return vret; } return 0; } /* * The descriptor's clear slot. */ static int sipMethodDescr_clear(PyObject *self) { PyObject *tmp = ((sipMethodDescr *)self)->mixin_name; ((sipMethodDescr *)self)->mixin_name = NULL; Py_XDECREF(tmp); return 0; } /* * The descriptor's dealloc slot. */ static void sipMethodDescr_dealloc(PyObject *self) { PyObject_GC_UnTrack(self); sipMethodDescr_clear(self); Py_TYPE(self)->tp_free(self); } /***************************************************************************** * A variable descriptor. We don't use the similar Python descriptor because * it doesn't support static variables. *****************************************************************************/ /* Forward declarations of slots. */ static PyObject *sipVariableDescr_descr_get(PyObject *self, PyObject *obj, PyObject *type); static int sipVariableDescr_descr_set(PyObject *self, PyObject *obj, PyObject *value); static int sipVariableDescr_traverse(PyObject *self, visitproc visit, void *arg); static int sipVariableDescr_clear(PyObject *self); static void sipVariableDescr_dealloc(PyObject *self); /* * The object data structure. */ typedef struct _sipVariableDescr { PyObject_HEAD /* The getter/setter definition. */ sipVariableDef *vd; /* The generated type definition. */ const sipTypeDef *td; /* The generated container definition. */ const sipContainerDef *cod; /* The mixin name, if any. */ PyObject *mixin_name; } sipVariableDescr; /* * The type data structure. */ PyTypeObject sipVariableDescr_Type = { PyVarObject_HEAD_INIT(NULL, 0) "sip.variabledescriptor", /* tp_name */ sizeof (sipVariableDescr), /* tp_basicsize */ 0, /* tp_itemsize */ sipVariableDescr_dealloc, /* tp_dealloc */ 0, /* tp_print */ 0, /* tp_getattr */ 0, /* tp_setattr */ 0, /* tp_compare */ 0, /* tp_repr */ 0, /* tp_as_number */ 0, /* tp_as_sequence */ 0, /* tp_as_mapping */ 0, /* tp_hash */ 0, /* tp_call */ 0, /* tp_str */ 0, /* tp_getattro */ 0, /* tp_setattro */ 0, /* tp_as_buffer */ Py_TPFLAGS_DEFAULT|Py_TPFLAGS_HAVE_GC, /* tp_flags */ 0, /* tp_doc */ sipVariableDescr_traverse, /* tp_traverse */ sipVariableDescr_clear, /* tp_clear */ 0, /* tp_richcompare */ 0, /* tp_weaklistoffset */ 0, /* tp_iter */ 0, /* tp_iternext */ 0, /* tp_methods */ 0, /* tp_members */ 0, /* tp_getset */ 0, /* tp_base */ 0, /* tp_dict */ sipVariableDescr_descr_get, /* tp_descr_get */ sipVariableDescr_descr_set, /* tp_descr_set */ 0, /* tp_dictoffset */ 0, /* tp_init */ 0, /* tp_alloc */ 0, /* tp_new */ 0, /* tp_free */ 0, /* tp_is_gc */ 0, /* tp_bases */ 0, /* tp_mro */ 0, /* tp_cache */ 0, /* tp_subclasses */ 0, /* tp_weaklist */ 0, /* tp_del */ 0, /* tp_version_tag */ 0, /* tp_finalize */ 0, /* tp_vectorcall */ }; /* Forward declarations. */ static int get_instance_address(sipVariableDescr *vd, PyObject *obj, void **addrp); /* * Return a new method descriptor for the given getter/setter. */ PyObject *sipVariableDescr_New(sipVariableDef *vd, const sipTypeDef *td, const sipContainerDef *cod) { PyObject *descr = PyType_GenericAlloc(&sipVariableDescr_Type, 0); if (descr != NULL) { ((sipVariableDescr *)descr)->vd = vd; ((sipVariableDescr *)descr)->td = td; ((sipVariableDescr *)descr)->cod = cod; ((sipVariableDescr *)descr)->mixin_name = NULL; } return descr; } /* * Return a new variable descriptor based on an existing one and a mixin name. */ PyObject *sipVariableDescr_Copy(PyObject *orig, PyObject *mixin_name) { PyObject *descr = PyType_GenericAlloc(&sipVariableDescr_Type, 0); if (descr != NULL) { ((sipVariableDescr *)descr)->vd = ((sipVariableDescr *)orig)->vd; ((sipVariableDescr *)descr)->td = ((sipVariableDescr *)orig)->td; ((sipVariableDescr *)descr)->cod = ((sipVariableDescr *)orig)->cod; ((sipVariableDescr *)descr)->mixin_name = mixin_name; Py_INCREF(mixin_name); } return descr; } /* * The descriptor's descriptor get slot. */ static PyObject *sipVariableDescr_descr_get(PyObject *self, PyObject *obj, PyObject *type) { sipVariableDescr *vd = (sipVariableDescr *)self; void *addr; if (get_instance_address(vd, obj, &addr) < 0) return NULL; return ((sipVariableGetterFunc)vd->vd->vd_getter)(addr, obj, type); } /* * The descriptor's descriptor set slot. */ static int sipVariableDescr_descr_set(PyObject *self, PyObject *obj, PyObject *value) { sipVariableDescr *vd = (sipVariableDescr *)self; void *addr; /* Check that the value isn't const. */ if (vd->vd->vd_setter == NULL) { PyErr_Format(PyExc_AttributeError, "'%s' object attribute '%s' is read-only", sipPyNameOfContainer(vd->cod, vd->td), vd->vd->vd_name); return -1; } if (get_instance_address(vd, obj, &addr) < 0) return -1; return ((sipVariableSetterFunc)vd->vd->vd_setter)(addr, value, obj); } /* * Return the C/C++ address of any instance. */ static int get_instance_address(sipVariableDescr *vd, PyObject *obj, void **addrp) { void *addr; if (vd->vd->vd_type == ClassVariable) { addr = NULL; } else { /* Check that access was via an instance. */ if (obj == NULL || obj == Py_None) { PyErr_Format(PyExc_AttributeError, "'%s' object attribute '%s' is an instance attribute", sipPyNameOfContainer(vd->cod, vd->td), vd->vd->vd_name); return -1; } if (vd->mixin_name != NULL) obj = PyObject_GetAttr(obj, vd->mixin_name); /* Get the C++ instance. */ if ((addr = sip_api_get_cpp_ptr((sipSimpleWrapper *)obj, vd->td)) == NULL) return -1; } *addrp = addr; return 0; } /* * The descriptor's traverse slot. */ static int sipVariableDescr_traverse(PyObject *self, visitproc visit, void *arg) { if (((sipVariableDescr *)self)->mixin_name != NULL) { int vret = visit(((sipVariableDescr *)self)->mixin_name, arg); if (vret != 0) return vret; } return 0; } /* * The descriptor's clear slot. */ static int sipVariableDescr_clear(PyObject *self) { PyObject *tmp = ((sipVariableDescr *)self)->mixin_name; ((sipVariableDescr *)self)->mixin_name = NULL; Py_XDECREF(tmp); return 0; } /* * The descriptor's dealloc slot. */ static void sipVariableDescr_dealloc(PyObject *self) { PyObject_GC_UnTrack(self); sipVariableDescr_clear(self); Py_TYPE(self)->tp_free(self); } sip-6.8.6/sipbuild/module/source/13/sip_enum.c000066400000000000000000000336401464421045000211420ustar00rootroot00000000000000/* SPDX-License-Identifier: BSD-2-Clause */ /* * This file implements the enum support. * * Copyright (c) 2024 Phil Thompson */ /* Remove when Python v3.12 is no longer supported. */ #define PY_SSIZE_T_CLEAN #include #include #include "sip_core.h" #include "sip_enum.h" #define IS_UNSIGNED_ENUM(etd) ((etd)->etd_base_type == SIP_ENUM_UINT_ENUM || (etd)->etd_base_type == SIP_ENUM_INT_FLAG || (etd)->etd_base_type == SIP_ENUM_FLAG) static PyObject *int_type = NULL; /* The int type. */ static PyObject *object_type = NULL; /* The object type. */ static PyObject *enum_type = NULL; /* The enum.Enum type. */ static PyObject *int_enum_type = NULL; /* The enum.IntEnum type. */ static PyObject *flag_type = NULL; /* The enum.Flag type. */ static PyObject *int_flag_type = NULL; /* The enum.IntFlag type. */ static PyObject *str_dunder_new = NULL; /* '__new__' */ static PyObject *str_dunder_sip = NULL; /* '__sip__' */ static PyObject *str_sunder_missing = NULL; /* '_missing_' */ static PyObject *str_sunder_name = NULL; /* '_name_' */ static PyObject *str_sunder_sip_missing = NULL; /* '_sip_missing_' */ static PyObject *str_sunder_value = NULL; /* '_value_' */ static PyObject *str_module = NULL; /* 'module' */ static PyObject *str_qualname = NULL; /* 'qualname' */ static PyObject *str_value = NULL; /* 'value' */ /* Forward references. */ static PyObject *create_enum_object(sipExportedModuleDef *client, sipEnumTypeDef *etd, sipIntInstanceDef **next_int_p, PyObject *name); static void enum_expected(PyObject *obj, const sipTypeDef *td); static PyObject *get_enum_type(const sipTypeDef *td); static PyObject *missing(PyObject *cls, PyObject *value, int int_enum); static PyObject *missing_enum(PyObject *cls, PyObject *value); static PyObject *missing_int_enum(PyObject *cls, PyObject *value); /* * Create a Python object for a member of a named enum. */ PyObject *sip_api_convert_from_enum(int member, const sipTypeDef *td) { PyObject *et; assert(sipTypeIsEnum(td)); et = get_enum_type(td); return PyObject_CallFunction(et, IS_UNSIGNED_ENUM((sipEnumTypeDef *)td) ? "(I)" : "(i)", member); } /* * Convert a Python object implementing an enum to an integer value. An * exception is raised if there was an error. */ int sip_api_convert_to_enum(PyObject *obj, const sipTypeDef *td) { PyObject *val_obj, *type_obj; int val; assert(sipTypeIsEnum(td)); /* Make sure the enum object has been created. */ type_obj = get_enum_type(td); /* Check the type of the Python object. */ if (PyObject_IsInstance(obj, type_obj) <= 0) { enum_expected(obj, td); return -1; } /* Get the value from the object. */ if ((val_obj = PyObject_GetAttr(obj, str_value)) == NULL) return -1; /* Flags are implicitly unsigned. */ if (IS_UNSIGNED_ENUM((sipEnumTypeDef *)td)) val = (int)sip_api_long_as_unsigned_int(val_obj); else val = sip_api_long_as_int(val_obj); Py_DECREF(val_obj); return val; } /* * Return a non-zero value if an object is a sub-class of enum.Flag. */ int sip_api_is_enum_flag(PyObject *obj) { return (PyObject_IsSubclass(obj, flag_type) == 1); } /* * Create an enum object and add it to a dictionary. A negative value is * returned (and an exception set) if there was an error. */ int sip_enum_create(sipExportedModuleDef *client, sipEnumTypeDef *etd, sipIntInstanceDef **next_int_p, PyObject *dict) { int rc; PyObject *name, *enum_obj; /* Create an object corresponding to the type name. */ if ((name = PyUnicode_FromString(sipPyNameOfEnum(etd))) == NULL) return -1; /* Create the enum object. */ if ((enum_obj = create_enum_object(client, etd, next_int_p, name)) == NULL) { Py_DECREF(name); return -1; } /* Add the enum to the "parent" dictionary. */ rc = PyDict_SetItem(dict, name, enum_obj); /* We can now release our remaining references. */ Py_DECREF(name); Py_DECREF(enum_obj); return rc; } /* * Return the generated type structure for a Python enum object that wraps a * C/C++ enum or NULL (and no exception set) if the object is something else. */ const sipTypeDef *sip_enum_get_generated_type(PyObject *obj) { if (sip_enum_is_enum(obj)) { PyObject *etd_cap; if ((etd_cap = PyObject_GetAttr(obj, str_dunder_sip)) != NULL) { sipTypeDef *td = (sipTypeDef *)PyCapsule_GetPointer(etd_cap, NULL); Py_DECREF(etd_cap); return td; } PyErr_Clear(); } return NULL; } /* * Initialise the enum support. A negative value is returned (and an exception * set) if there was an error. */ int sip_enum_init(void) { PyObject *builtins, *enum_module; /* Get the builtin types. */ builtins = PyEval_GetBuiltins(); if ((int_type = PyDict_GetItemString(builtins, "int")) == NULL) return -1; if ((object_type = PyDict_GetItemString(builtins, "object")) == NULL) return -1; /* Get the enum types. */ if ((enum_module = PyImport_ImportModule("enum")) == NULL) return -1; enum_type = PyObject_GetAttrString(enum_module, "Enum"); int_enum_type = PyObject_GetAttrString(enum_module, "IntEnum"); flag_type = PyObject_GetAttrString(enum_module, "Flag"); int_flag_type = PyObject_GetAttrString(enum_module, "IntFlag"); Py_DECREF(enum_module); if (enum_type == NULL || int_enum_type == NULL || flag_type == NULL || int_flag_type == NULL) { Py_XDECREF(enum_type); Py_XDECREF(int_enum_type); Py_XDECREF(flag_type); Py_XDECREF(int_flag_type); return -1; } /* Objectify the strings. */ if (sip_objectify("__new__", &str_dunder_new) < 0) return -1; if (sip_objectify("__sip__", &str_dunder_sip) < 0) return -1; if (sip_objectify("_missing_", &str_sunder_missing) < 0) return -1; if (sip_objectify("_name_", &str_sunder_name) < 0) return -1; if (sip_objectify("_sip_missing_", &str_sunder_sip_missing) < 0) return -1; if (sip_objectify("_value_", &str_sunder_value) < 0) return -1; if (sip_objectify("module", &str_module) < 0) return -1; if (sip_objectify("qualname", &str_qualname) < 0) return -1; if (sip_objectify("value", &str_value) < 0) return -1; return 0; } /* * Return a non-zero value if an object is a sub-class of enum.Enum. */ int sip_enum_is_enum(PyObject *obj) { return (PyObject_IsSubclass(obj, enum_type) == 1); } /* * Create an enum object. */ static PyObject *create_enum_object(sipExportedModuleDef *client, sipEnumTypeDef *etd, sipIntInstanceDef **next_int_p, PyObject *name) { int i; PyObject *members, *enum_factory, *enum_obj, *args, *kw_args, *etd_cap; PyMethodDef *missing_md; sipIntInstanceDef *next_int; /* Create a dict of the members. */ if ((members = PyDict_New()) == NULL) goto ret_err; next_int = *next_int_p; assert(next_int != NULL); for (i = 0; i < etd->etd_nr_members; ++i) { PyObject *value_obj; assert(next_int->ii_name != NULL); /* Flags are implicitly unsigned. */ if (IS_UNSIGNED_ENUM(etd)) value_obj = PyLong_FromUnsignedLong((unsigned)next_int->ii_val); else value_obj = PyLong_FromLong(next_int->ii_val); if (sip_dict_set_and_discard(members, next_int->ii_name, value_obj) < 0) goto rel_members; ++next_int; } *next_int_p = next_int; if ((args = PyTuple_Pack(2, name, members)) == NULL) goto rel_members; if ((kw_args = PyDict_New()) == NULL) goto rel_args; if (PyDict_SetItem(kw_args, str_module, client->em_nameobj) < 0) goto rel_kw_args; /* * If the enum has a scope then the default __qualname__ will be incorrect. */ if (etd->etd_scope >= 0) { int rc; PyObject *qualname; if ((qualname = sip_get_qualname(client->em_types[etd->etd_scope], name)) == NULL) goto rel_kw_args; rc = PyDict_SetItem(kw_args, str_qualname, qualname); Py_DECREF(qualname); if (rc < 0) goto rel_kw_args; } missing_md = NULL; if (etd->etd_base_type == SIP_ENUM_INT_FLAG) { enum_factory = int_flag_type; } else if (etd->etd_base_type == SIP_ENUM_FLAG) { enum_factory = flag_type; } else if (etd->etd_base_type == SIP_ENUM_INT_ENUM || etd->etd_base_type == SIP_ENUM_UINT_ENUM) { static PyMethodDef missing_int_enum_md = { "_missing_", missing_int_enum, METH_O|METH_CLASS, NULL }; enum_factory = int_enum_type; missing_md = &missing_int_enum_md; } else { static PyMethodDef missing_enum_md = { "_missing_", missing_enum, METH_O|METH_CLASS, NULL }; enum_factory = enum_type; missing_md = &missing_enum_md; } if ((enum_obj = PyObject_Call(enum_factory, args, kw_args)) == NULL) goto rel_kw_args; Py_DECREF(kw_args); Py_DECREF(args); Py_DECREF(members); etd->etd_base.td_py_type = (PyTypeObject *)enum_obj; /* Inject _missing_. */ if (missing_md != NULL) { PyObject *missing_cfunc; if ((missing_cfunc = PyCFunction_New(missing_md, enum_obj)) == NULL) { Py_DECREF(enum_obj); return NULL; } if (PyObject_SetAttr(enum_obj, str_sunder_missing, missing_cfunc) < 0) { Py_DECREF(missing_cfunc); Py_DECREF(enum_obj); return NULL; } Py_DECREF(missing_cfunc); } /* Wrap the generated type definition in a capsule. */ if ((etd_cap = PyCapsule_New(etd, NULL, NULL)) == NULL) { Py_DECREF(enum_obj); return NULL; } if (PyObject_SetAttr(enum_obj, str_dunder_sip, etd_cap) < 0) { Py_DECREF(etd_cap); Py_DECREF(enum_obj); return NULL; } Py_DECREF(etd_cap); if (etd->etd_pyslots != NULL) sip_add_type_slots((PyHeapTypeObject *)enum_obj, etd->etd_pyslots); return enum_obj; /* Unwind on errors. */ rel_kw_args: Py_DECREF(kw_args); rel_args: Py_DECREF(args); rel_members: Py_DECREF(members); ret_err: return NULL; } /* * Raise an exception when failing to convert an enum because of its type. */ static void enum_expected(PyObject *obj, const sipTypeDef *td) { PyErr_Format(PyExc_TypeError, "a member of enum '%s' is expected not '%s'", sipPyNameOfEnum((sipEnumTypeDef *)td), Py_TYPE(obj)->tp_name); } /* * Get the Python object for an enum type. */ static PyObject *get_enum_type(const sipTypeDef *td) { PyObject *type_obj; /* Make sure the enum object has been created. */ type_obj = (PyObject *)sipTypeAsPyTypeObject(td); if (type_obj == NULL) { if (sip_add_all_lazy_attrs(sip_api_type_scope(td)) < 0) return NULL; type_obj = (PyObject *)sipTypeAsPyTypeObject(td); } return type_obj; } /* * The bulk of the implementation of _missing_ that handles missing members. */ static PyObject *missing(PyObject *cls, PyObject *value, int int_enum) { PyObject *sip_missing, *member, *value_str; /* Get the dict of previously missing members. */ if ((sip_missing = PyObject_GetAttr(cls, str_sunder_sip_missing)) != NULL) { if ((member = PyDict_GetItemWithError(sip_missing, value)) != NULL) { /* Return the already missing member. */ Py_INCREF(member); return member; } /* A missing key will not raise an exception. */ if (PyErr_Occurred()) { Py_DECREF(sip_missing); return NULL; } } else if (PyErr_ExceptionMatches(PyExc_AttributeError)) { PyErr_Clear(); /* Create the dict and save it in the class. */ if ((sip_missing = PyDict_New()) == NULL) return NULL; if (PyObject_SetAttr(cls, str_sunder_sip_missing, sip_missing) < 0) { Py_DECREF(sip_missing); return NULL; } } else { /* The exception is unexpected. */ return NULL; } /* Create a member for the missing value. */ if (int_enum) member = PyObject_CallMethodObjArgs(int_type, str_dunder_new, cls, value, NULL); else member = PyObject_CallMethodObjArgs(object_type, str_dunder_new, cls, NULL); if (member == NULL) { Py_DECREF(sip_missing); return NULL; } /* Set the member's attributes. */ if ((value_str = PyObject_Str(value)) == NULL) { Py_DECREF(member); Py_DECREF(sip_missing); return NULL; } if (PyObject_SetAttr(member, str_sunder_name, value_str) < 0) { Py_DECREF(value_str); Py_DECREF(member); Py_DECREF(sip_missing); return NULL; } Py_DECREF(value_str); if (PyObject_SetAttr(member, str_sunder_value, value) < 0) { Py_DECREF(member); Py_DECREF(sip_missing); return NULL; } /* Save the member so that it is a singleton. */ if (PyDict_SetItem(sip_missing, value, member) < 0) { Py_DECREF(member); Py_DECREF(sip_missing); return NULL; } Py_DECREF(sip_missing); return member; } /* * The replacment implementation of _missing_ that handles missing members in * Enums. */ static PyObject *missing_enum(PyObject *cls, PyObject *value) { return missing(cls, value, FALSE); } /* * The replacment implementation of _missing_ that handles missing members in * IntEnums. */ static PyObject *missing_int_enum(PyObject *cls, PyObject *value) { return missing(cls, value, TRUE); } sip-6.8.6/sipbuild/module/source/13/sip_enum.h000066400000000000000000000015161464421045000211440ustar00rootroot00000000000000/* SPDX-License-Identifier: BSD-2-Clause */ /* * This file defines the API for the enum support. * * Copyright (c) 2024 Phil Thompson */ #ifndef _SIP_ENUM_H #define _SIP_ENUM_H /* Remove when Python v3.12 is no longer supported. */ #define PY_SSIZE_T_CLEAN #include #include "sip.h" #ifdef __cplusplus extern "C" { #endif PyObject *sip_api_convert_from_enum(int member, const sipTypeDef *td); int sip_api_convert_to_enum(PyObject *obj, const sipTypeDef *td); int sip_api_is_enum_flag(PyObject *obj); int sip_enum_create(sipExportedModuleDef *client, sipEnumTypeDef *etd, sipIntInstanceDef **next_int_p, PyObject *dict); const sipTypeDef *sip_enum_get_generated_type(PyObject *obj); int sip_enum_init(void); int sip_enum_is_enum(PyObject *obj); #ifdef __cplusplus } #endif #endif sip-6.8.6/sipbuild/module/source/13/sip_int_convertors.c000066400000000000000000000121201464421045000232420ustar00rootroot00000000000000/* SPDX-License-Identifier: BSD-2-Clause */ /* * The implementation of the Python object to C/C++ integer convertors. * * Copyright (c) 2024 Phil Thompson */ /* Remove when Python v3.12 is no longer supported. */ #define PY_SSIZE_T_CLEAN #include #include #include "sip_core.h" static long long long_as_long_long(PyObject *o, long long min, long long max); static unsigned long long_as_unsigned_long(PyObject *o, unsigned long max); static void raise_signed_overflow(long long min, long long max); static void raise_unsigned_overflow(unsigned long long max); /* * Convert a Python object to a C++ bool (returned as an int). */ int sip_api_convert_to_bool(PyObject *o) { int v; /* Convert the object to an int while checking for overflow. */ v = sip_api_long_as_int(o); if (PyErr_Occurred()) { if (PyErr_ExceptionMatches(PyExc_OverflowError)) { PyErr_Clear(); /* The value must have been non-zero. */ v = 1; } else { PyErr_Format(PyExc_TypeError, "a 'bool' is expected not '%s'", Py_TYPE(o)->tp_name); v = -1; } } else if (v != 0) { v = 1; } return v; } /* * Convert a Python object to a C char. */ char sip_api_long_as_char(PyObject *o) { return (char)long_as_long_long(o, CHAR_MIN, CHAR_MAX); } /* * Convert a Python object to a C signed char. */ signed char sip_api_long_as_signed_char(PyObject *o) { return (signed char)long_as_long_long(o, SCHAR_MIN, SCHAR_MAX); } /* * Convert a Python object to a C unsigned char. */ unsigned char sip_api_long_as_unsigned_char(PyObject *o) { return (unsigned char)long_as_unsigned_long(o, UCHAR_MAX); } /* * Convert a Python object to a C short. */ short sip_api_long_as_short(PyObject *o) { return (short)long_as_long_long(o, SHRT_MIN, SHRT_MAX); } /* * Convert a Python object to a C unsigned short. */ unsigned short sip_api_long_as_unsigned_short(PyObject *o) { return (unsigned short)long_as_unsigned_long(o, USHRT_MAX); } /* * Convert a Python object to a C int. */ int sip_api_long_as_int(PyObject *o) { return (int)long_as_long_long(o, INT_MIN, INT_MAX); } /* * Convert a Python object to a C unsigned int. */ unsigned sip_api_long_as_unsigned_int(PyObject *o) { return (unsigned)long_as_unsigned_long(o, UINT_MAX); } /* * Convert a Python object to a C size_t. */ size_t sip_api_long_as_size_t(PyObject *o) { return (size_t)long_as_unsigned_long(o, SIZE_MAX); } /* * Convert a Python object to a C long. */ long sip_api_long_as_long(PyObject *o) { return (long)long_as_long_long(o, LONG_MIN, LONG_MAX); } /* * Convert a Python object to a C unsigned long. */ unsigned long sip_api_long_as_unsigned_long(PyObject *o) { return long_as_unsigned_long(o, ULONG_MAX); } /* * Convert a Python object to a C long long. */ long long sip_api_long_as_long_long(PyObject *o) { return long_as_long_long(o, LLONG_MIN, LLONG_MAX); } /* * Convert a Python object to a C unsigned long long. */ unsigned long long sip_api_long_as_unsigned_long_long(PyObject *o) { unsigned long long value; PyErr_Clear(); value = PyLong_AsUnsignedLongLong(o); if (PyErr_Occurred()) { /* Provide a better exception message. */ if (PyErr_ExceptionMatches(PyExc_OverflowError)) raise_unsigned_overflow(ULLONG_MAX); } return value; } /* * Convert a Python object to a long long checking that the value is within a * range if overflow checking is enabled. */ static long long long_as_long_long(PyObject *o, long long min, long long max) { long long value; PyErr_Clear(); value = PyLong_AsLongLong(o); if (PyErr_Occurred()) { /* Provide a better exception message. */ if (PyErr_ExceptionMatches(PyExc_OverflowError)) raise_signed_overflow(min, max); } else if (value < min || value > max) { raise_signed_overflow(min, max); } return value; } /* * Convert a Python object to an unsigned long checking that the value is * within a range if overflow checking is enabled. */ static unsigned long long_as_unsigned_long(PyObject *o, unsigned long max) { unsigned long value; PyErr_Clear(); value = PyLong_AsUnsignedLong(o); if (PyErr_Occurred()) { /* Provide a better exception message. */ if (PyErr_ExceptionMatches(PyExc_OverflowError)) raise_unsigned_overflow(max); } else if (value > max) { raise_unsigned_overflow(max); } return value; } /* * Raise an overflow exception if a signed value is out of range. */ static void raise_signed_overflow(long long min, long long max) { PyErr_Format(PyExc_OverflowError, "value must be in the range %lld to %lld", min, max); } /* * Raise an overflow exception if an unsigned value is out of range. */ static void raise_unsigned_overflow(unsigned long long max) { PyErr_Format(PyExc_OverflowError, "value must be in the range 0 to %llu", max); } sip-6.8.6/sipbuild/module/source/13/sip_object_map.c000066400000000000000000000317461464421045000223060ustar00rootroot00000000000000/* SPDX-License-Identifier: BSD-2-Clause */ /* * This module implements a hash table class for mapping C/C++ addresses to the * corresponding wrapped Python object. * * Copyright (c) 2024 Phil Thompson */ #include #include #include "sip_core.h" #define hash_1(k,s) (((uintptr_t)(k)) % (s)) #define hash_2(k,s) ((s) - 2 - (hash_1((k),(s)) % ((s) - 2))) /* Prime numbers to use as hash table sizes. */ static uintptr_t hash_primes[] = { 521, 1031, 2053, 4099, 8209, 16411, 32771, 65537, 131101, 262147, 524309, 1048583, 2097169, 4194319, 8388617, 16777259, 33554467, 67108879, 134217757, 268435459, 536870923, 1073741827, 2147483659U,0 }; static sipHashEntry *newHashTable(uintptr_t); static sipHashEntry *findHashEntry(sipObjectMap *,void *); static void add_object(sipObjectMap *om, void *addr, sipSimpleWrapper *val); static void add_aliases(sipObjectMap *om, void *addr, sipSimpleWrapper *val, const sipClassTypeDef *base_ctd, const sipClassTypeDef *ctd); static int remove_object(sipObjectMap *om, void *addr, sipSimpleWrapper *val); static void remove_aliases(sipObjectMap *om, void *addr, sipSimpleWrapper *val, const sipClassTypeDef *base_ctd, const sipClassTypeDef *ctd); static void reorganiseMap(sipObjectMap *om); static void *getUnguardedPointer(sipSimpleWrapper *w); /* * Initialise an object map. */ void sipOMInit(sipObjectMap *om) { om -> primeIdx = 0; om -> unused = om -> size = hash_primes[om -> primeIdx]; om -> stale = 0; om -> hash_array = newHashTable(om -> size); } /* * Finalise an object map. */ void sipOMFinalise(sipObjectMap *om) { sip_api_free(om -> hash_array); } /* * Allocate and initialise a new hash table. */ static sipHashEntry *newHashTable(uintptr_t size) { size_t nbytes; sipHashEntry *hashtab; nbytes = sizeof (sipHashEntry) * size; if ((hashtab = (sipHashEntry *)sip_api_malloc(nbytes)) != NULL) memset(hashtab,0,nbytes); return hashtab; } /* * Return a pointer to the hash entry that is used, or should be used, for the * given C/C++ address. */ static sipHashEntry *findHashEntry(sipObjectMap *om,void *key) { uintptr_t hash, inc; void *hek; hash = hash_1(key,om -> size); inc = hash_2(key,om -> size); while ((hek = om -> hash_array[hash].key) != NULL && hek != key) hash = (hash + inc) % om -> size; return &om -> hash_array[hash]; } /* * Return the wrapped Python object of a specific type for a C/C++ address or * NULL if it wasn't found. */ sipSimpleWrapper *sipOMFindObject(sipObjectMap *om, void *key, const sipTypeDef *td) { sipHashEntry *he = findHashEntry(om, key); sipSimpleWrapper *sw; PyTypeObject *py_type = sipTypeAsPyTypeObject(td); /* Go through each wrapped object at this address. */ for (sw = he->first; sw != NULL; sw = sw->next) { sipSimpleWrapper *unaliased; unaliased = (sipIsAlias(sw) ? (sipSimpleWrapper *)sw->data : sw); /* * If the reference count is 0 then it is in the process of being * deleted, so ignore it. It's not completely clear how this can * happen (but it can) because it implies that the garbage collection * code is being re-entered (and there are guards in place to prevent * this). */ if (Py_REFCNT(unaliased) == 0) continue; /* Ignore it if the C/C++ address is no longer valid. */ if (sip_api_get_address(unaliased) == NULL) continue; /* * If this wrapped object is of the given type, or a sub-type of it, * then we assume it is the same C++ object. */ if (PyObject_TypeCheck(unaliased, py_type)) return unaliased; } return NULL; } /* * Add a C/C++ address and the corresponding wrapped Python object to the map. */ void sipOMAddObject(sipObjectMap *om, sipSimpleWrapper *val) { void *addr = getUnguardedPointer(val); const sipClassTypeDef *base_ctd; /* Add the object. */ add_object(om, addr, val); /* Add any aliases. */ base_ctd = (const sipClassTypeDef *)((sipWrapperType *)Py_TYPE(val))->wt_td; add_aliases(om, addr, val, base_ctd, base_ctd); } /* * Add an alias for any address that is different when cast to a super-type. */ static void add_aliases(sipObjectMap *om, void *addr, sipSimpleWrapper *val, const sipClassTypeDef *base_ctd, const sipClassTypeDef *ctd) { const sipEncodedTypeDef *sup; /* See if there are any super-classes. */ if ((sup = ctd->ctd_supers) != NULL) { sipClassTypeDef *sup_ctd = sipGetGeneratedClassType(sup, ctd); /* Recurse up the hierachy for the first super-class. */ add_aliases(om, addr, val, base_ctd, sup_ctd); /* * We only check for aliases for subsequent super-classes because the * first one can never need one. */ while (!sup++->sc_flag) { void *sup_addr; sup_ctd = sipGetGeneratedClassType(sup, ctd); /* Recurse up the hierachy for the remaining super-classes. */ add_aliases(om, addr, val, base_ctd, sup_ctd); sup_addr = (*base_ctd->ctd_cast)(addr, (sipTypeDef *)sup_ctd); if (sup_addr != addr) { sipSimpleWrapper *alias; /* Note that we silently ignore errors. */ if ((alias = sip_api_malloc(sizeof (sipSimpleWrapper))) != NULL) { /* * An alias is basically a bit-wise copy of the Python * object but only to ensure the fields we are subverting * are in the right place. An alias should never be passed * to the Python API. */ *alias = *val; alias->sw_flags = (val->sw_flags & SIP_SHARE_MAP) | SIP_ALIAS; alias->data = val; alias->next = NULL; add_object(om, sup_addr, alias); } } } } } /* * Add a wrapper (which may be an alias) to the map. */ static void add_object(sipObjectMap *om, void *addr, sipSimpleWrapper *val) { sipHashEntry *he = findHashEntry(om, addr); /* * If the bucket is in use then we appear to have several objects at the * same address. */ if (he->first != NULL) { /* * This can happen for three reasons. A variable of one class can be * declared at the start of another class. Therefore there are two * objects, of different classes, with the same address. The second * reason is that the old C/C++ object has been deleted by C/C++ but we * didn't get to find out for some reason, and a new C/C++ instance has * been created at the same address. The third reason is if we are in * the process of deleting a Python object but the C++ object gets * wrapped again because the C++ dtor called a method that has been * re-implemented in Python. The absence of the SIP_SHARE_MAP flag * tells us that a new C++ instance has just been created and so we * know the second reason is the correct one so we mark the old * pointers as invalid and reuse the entry. Otherwise we just add this * one to the existing list of objects at this address. */ if (!(val->sw_flags & SIP_SHARE_MAP)) { sipSimpleWrapper *sw = he->first; he->first = NULL; while (sw != NULL) { sipSimpleWrapper *next = sw->next; if (sipIsAlias(sw)) { sip_api_free(sw); } else { /* * We are removing it from the map here. We first have to * call the destructor as the destructor itself might end * up trying to remove the wrapper and its aliases from the * map. */ sip_api_instance_destroyed(sw); } sw = next; } } val->next = he->first; he->first = val; return; } /* See if the bucket was unused or stale. */ if (he->key == NULL) { he->key = addr; om->unused--; } else { om->stale--; } /* Add the rest of the new value. */ he->first = val; val->next = NULL; reorganiseMap(om); } /* * Reorganise a map if it is running short of space. */ static void reorganiseMap(sipObjectMap *om) { uintptr_t old_size, i; sipHashEntry *ohe, *old_tab; /* Don't bother if it still has more than 12% available. */ if (om -> unused > om -> size >> 3) return; /* * If reorganising (ie. making the stale buckets unused) using the same * sized table would make 25% available then do that. Otherwise use a * bigger table (if possible). */ if (om -> unused + om -> stale < om -> size >> 2 && hash_primes[om -> primeIdx + 1] != 0) om -> primeIdx++; old_size = om -> size; old_tab = om -> hash_array; om -> unused = om -> size = hash_primes[om -> primeIdx]; om -> stale = 0; om -> hash_array = newHashTable(om -> size); /* Transfer the entries from the old table to the new one. */ ohe = old_tab; for (i = 0; i < old_size; ++i) { if (ohe -> key != NULL && ohe -> first != NULL) { *findHashEntry(om,ohe -> key) = *ohe; om -> unused--; } ++ohe; } sip_api_free(old_tab); } /* * Remove a C/C++ object from the table. Return 0 if it was removed * successfully. */ int sipOMRemoveObject(sipObjectMap *om, sipSimpleWrapper *val) { void *addr; const sipClassTypeDef *base_ctd; /* Handle the trivial case. */ if (sipNotInMap(val)) return 0; if ((addr = getUnguardedPointer(val)) == NULL) return 0; /* Remove any aliases. */ base_ctd = (const sipClassTypeDef *)((sipWrapperType *)Py_TYPE(val))->wt_td; remove_aliases(om, addr, val, base_ctd, base_ctd); /* Remove the object. */ return remove_object(om, addr, val); } /* * Remove an alias for any address that is different when cast to a super-type. */ static void remove_aliases(sipObjectMap *om, void *addr, sipSimpleWrapper *val, const sipClassTypeDef *base_ctd, const sipClassTypeDef *ctd) { const sipEncodedTypeDef *sup; /* See if there are any super-classes. */ if ((sup = ctd->ctd_supers) != NULL) { sipClassTypeDef *sup_ctd = sipGetGeneratedClassType(sup, ctd); /* Recurse up the hierachy for the first super-class. */ remove_aliases(om, addr, val, base_ctd, sup_ctd); /* * We only check for aliases for subsequent super-classes because the * first one can never need one. */ while (!sup++->sc_flag) { void *sup_addr; sup_ctd = sipGetGeneratedClassType(sup, ctd); /* Recurse up the hierachy for the remaining super-classes. */ remove_aliases(om, addr, val, base_ctd, sup_ctd); sup_addr = (*base_ctd->ctd_cast)(addr, (sipTypeDef *)sup_ctd); if (sup_addr != addr) remove_object(om, sup_addr, val); } } } /* * Remove a wrapper from the map. */ static int remove_object(sipObjectMap *om, void *addr, sipSimpleWrapper *val) { sipHashEntry *he = findHashEntry(om, addr); sipSimpleWrapper **swp; for (swp = &he->first; *swp != NULL; swp = &(*swp)->next) { sipSimpleWrapper *sw, *next; int do_remove; sw = *swp; next = sw->next; if (sipIsAlias(sw)) { if (sw->data == val) { sip_api_free(sw); do_remove = TRUE; } else { do_remove = FALSE; } } else { do_remove = (sw == val); } if (do_remove) { *swp = next; /* * If the bucket is now empty then count it as stale. Note that we * do not NULL the key and count it as unused because that might * throw out the search for another entry that wanted to go here, * found it already occupied, and was put somewhere else. In other * words, searches must be repeatable until we reorganise the * table. */ if (he->first == NULL) om->stale++; return 0; } } return -1; } /* * Return the unguarded pointer to a C/C++ instance, ie. the pointer was valid * but may longer be. */ static void *getUnguardedPointer(sipSimpleWrapper *w) { return (w->access_func != NULL) ? w->access_func(w, UnguardedPointer) : w->data; } sip-6.8.6/sipbuild/module/source/13/sip_threads.c000066400000000000000000000102731464421045000216250ustar00rootroot00000000000000/* SPDX-License-Identifier: BSD-2-Clause */ /* * Thread support for the SIP library. This module provides the hooks for * C++ classes that provide a thread interface to interact properly with the * Python threading infrastructure. * * Copyright (c) 2024 Phil Thompson */ #include "sip_core.h" /* * The data associated with pending request to wrap an object. */ typedef struct _pendingDef { void *cpp; /* The C/C++ object ot be wrapped. */ sipWrapper *owner; /* The owner of the object. */ int flags; /* The flags. */ } pendingDef; #ifdef WITH_THREAD #include /* * The per thread data we need to maintain. */ typedef struct _threadDef { long thr_ident; /* The thread identifier. */ pendingDef pending; /* An object waiting to be wrapped. */ struct _threadDef *next; /* Next in the list. */ } threadDef; static threadDef *threads = NULL; /* Linked list of threads. */ static threadDef *currentThreadDef(int auto_alloc); #endif static pendingDef *get_pending(int auto_alloc); /* * Get the address etc. of any C/C++ object waiting to be wrapped. */ int sipGetPending(void **pp, sipWrapper **op, int *fp) { pendingDef *pd; if ((pd = get_pending(TRUE)) == NULL) return -1; *pp = pd->cpp; *op = pd->owner; *fp = pd->flags; /* Clear in case we execute Python code before finishing this wrapping. */ pd->cpp = NULL; return 0; } /* * Return TRUE if anything is pending. */ int sipIsPending(void) { pendingDef *pd; if ((pd = get_pending(FALSE)) == NULL) return FALSE; return (pd->cpp != NULL); } /* * Convert a new C/C++ pointer to a Python instance. */ PyObject *sipWrapInstance(void *cpp, PyTypeObject *py_type, PyObject *args, sipWrapper *owner, int flags) { pendingDef old_pending, *pd; PyObject *self; if (cpp == NULL) { Py_INCREF(Py_None); return Py_None; } /* * Object creation can trigger the Python garbage collector which in turn * can execute arbitrary Python code which can then call this function * recursively. Therefore we save any existing pending object before * setting the new one. */ if ((pd = get_pending(TRUE)) == NULL) return NULL; old_pending = *pd; pd->cpp = cpp; pd->owner = owner; pd->flags = flags; self = PyObject_Call((PyObject *)py_type, args, NULL); *pd = old_pending; return self; } /* * Handle the termination of a thread. */ void sip_api_end_thread(void) { #ifdef WITH_THREAD threadDef *thread; PyGILState_STATE gil = PyGILState_Ensure(); if ((thread = currentThreadDef(FALSE)) != NULL) thread->thr_ident = 0; PyGILState_Release(gil); #endif } /* * Return the pending data for the current thread, allocating it if necessary, * or NULL if there was an error. */ static pendingDef *get_pending(int auto_alloc) { #ifdef WITH_THREAD threadDef *thread; if ((thread = currentThreadDef(auto_alloc)) == NULL) return NULL; return &thread->pending; #else static pendingDef pending; return &pending; #endif } #ifdef WITH_THREAD /* * Return the thread data for the current thread, allocating it if necessary, * or NULL if there was an error. */ static threadDef *currentThreadDef(int auto_alloc) { threadDef *thread, *empty = NULL; long ident = PyThread_get_thread_ident(); /* See if we already know about the thread. */ for (thread = threads; thread != NULL; thread = thread->next) { if (thread->thr_ident == ident) return thread; if (thread->thr_ident == 0) empty = thread; } if (!auto_alloc) { /* This is not an error. */ return NULL; } if (empty != NULL) { /* Use an empty entry in the list. */ thread = empty; } else if ((thread = sip_api_malloc(sizeof (threadDef))) == NULL) { return NULL; } else { thread->next = threads; threads = thread; } thread->thr_ident = ident; thread->pending.cpp = NULL; return thread; } #endif sip-6.8.6/sipbuild/module/source/13/sip_voidptr.c000066400000000000000000000450621464421045000216660ustar00rootroot00000000000000/* SPDX-License-Identifier: BSD-2-Clause */ /* * This file implements the API for the voidptr type. * * Copyright (c) 2024 Phil Thompson */ /* Remove when Python v3.12 is no longer supported. */ #define PY_SSIZE_T_CLEAN #include #include #include #include "sip_array.h" #include "sip_core.h" /* The object data structure. */ typedef struct { PyObject_HEAD void *voidptr; Py_ssize_t size; int rw; } sipVoidPtrObject; /* The structure used to hold the results of a voidptr conversion. */ struct vp_values { void *voidptr; Py_ssize_t size; int rw; }; static int check_size(PyObject *self); static int check_rw(PyObject *self); static int check_index(PyObject *self, Py_ssize_t idx); static void bad_key(PyObject *key); static int check_slice_size(Py_ssize_t size, Py_ssize_t value_size); static PyObject *make_voidptr(void *voidptr, Py_ssize_t size, int rw); static int vp_convertor(PyObject *arg, struct vp_values *vp); static Py_ssize_t get_size_from_arg(sipVoidPtrObject *v, Py_ssize_t size); /* * Implement ascapsule() for the type. */ static PyObject *sipVoidPtr_ascapsule(sipVoidPtrObject *v, PyObject *arg) { (void)arg; return PyCapsule_New(v->voidptr, NULL, NULL); } /* * Implement asarray() for the type. */ static PyObject *sipVoidPtr_asarray(sipVoidPtrObject *v, PyObject *args, PyObject *kw) { #if PY_VERSION_HEX >= 0x030d0000 static char * const kwlist[] = {"size", NULL}; #else static char *kwlist[] = {"size", NULL}; #endif Py_ssize_t size = -1; if (!PyArg_ParseTupleAndKeywords(args, kw, "|n:asarray", kwlist, &size)) return NULL; if ((size = get_size_from_arg(v, size)) < 0) return NULL; return sip_api_convert_to_array(v->voidptr, "B", size, (v->rw ? 0 : SIP_READ_ONLY)); } /* * Implement asstring() for the type. */ static PyObject *sipVoidPtr_asstring(sipVoidPtrObject *v, PyObject *args, PyObject *kw) { #if PY_VERSION_HEX >= 0x030d0000 static char * const kwlist[] = {"size", NULL}; #else static char *kwlist[] = {"size", NULL}; #endif Py_ssize_t size = -1; if (!PyArg_ParseTupleAndKeywords(args, kw, "|n:asstring", kwlist, &size)) return NULL; if ((size = get_size_from_arg(v, size)) < 0) return NULL; return PyBytes_FromStringAndSize(v->voidptr, size); } /* * Implement getsize() for the type. */ static PyObject *sipVoidPtr_getsize(sipVoidPtrObject *v, PyObject *arg) { (void)arg; return PyLong_FromSsize_t(v->size); } /* * Implement setsize() for the type. */ static PyObject *sipVoidPtr_setsize(sipVoidPtrObject *v, PyObject *arg) { Py_ssize_t size = PyLong_AsSsize_t(arg); if (PyErr_Occurred()) return NULL; v->size = size; Py_INCREF(Py_None); return Py_None; } /* * Implement getwriteable() for the type. */ static PyObject *sipVoidPtr_getwriteable(sipVoidPtrObject *v, PyObject *arg) { (void)arg; return PyBool_FromLong(v->rw); } /* * Implement setwriteable() for the type. */ static PyObject *sipVoidPtr_setwriteable(sipVoidPtrObject *v, PyObject *arg) { int rw; if ((rw = PyObject_IsTrue(arg)) < 0) return NULL; v->rw = rw; Py_INCREF(Py_None); return Py_None; } /* The methods data structure. */ static PyMethodDef sipVoidPtr_Methods[] = { {"asarray", (PyCFunction)sipVoidPtr_asarray, METH_VARARGS|METH_KEYWORDS, NULL}, {"ascapsule", (PyCFunction)sipVoidPtr_ascapsule, METH_NOARGS, NULL}, {"asstring", (PyCFunction)sipVoidPtr_asstring, METH_VARARGS|METH_KEYWORDS, NULL}, {"getsize", (PyCFunction)sipVoidPtr_getsize, METH_NOARGS, NULL}, {"setsize", (PyCFunction)sipVoidPtr_setsize, METH_O, NULL}, {"getwriteable", (PyCFunction)sipVoidPtr_getwriteable, METH_NOARGS, NULL}, {"setwriteable", (PyCFunction)sipVoidPtr_setwriteable, METH_O, NULL}, {NULL, NULL, 0, NULL} }; /* * Implement bool() for the type. */ static int sipVoidPtr_bool(PyObject *self) { return (((sipVoidPtrObject *)self)->voidptr != NULL); } /* * Implement int() for the type. */ static PyObject *sipVoidPtr_int(PyObject *self) { return PyLong_FromVoidPtr(((sipVoidPtrObject *)self)->voidptr); } /* The number methods data structure. */ static PyNumberMethods sipVoidPtr_NumberMethods = { 0, /* nb_add */ 0, /* nb_subtract */ 0, /* nb_multiply */ 0, /* nb_remainder */ 0, /* nb_divmod */ 0, /* nb_power */ 0, /* nb_negative */ 0, /* nb_positive */ 0, /* nb_absolute */ sipVoidPtr_bool, /* nb_bool */ 0, /* nb_invert */ 0, /* nb_lshift */ 0, /* nb_rshift */ 0, /* nb_and */ 0, /* nb_xor */ 0, /* nb_or */ sipVoidPtr_int, /* nb_int */ 0, /* nb_reserved */ 0, /* nb_float */ 0, /* nb_inplace_add */ 0, /* nb_inplace_subtract */ 0, /* nb_inplace_multiply */ 0, /* nb_inplace_remainder */ 0, /* nb_inplace_power */ 0, /* nb_inplace_lshift */ 0, /* nb_inplace_rshift */ 0, /* nb_inplace_and */ 0, /* nb_inplace_xor */ 0, /* nb_inplace_or */ 0, /* nb_floor_divide */ 0, /* nb_true_divide */ 0, /* nb_inplace_floor_divide */ 0, /* nb_inplace_true_divide */ 0, /* nb_index */ 0, /* nb_matrix_multiply */ 0, /* nb_inplace_matrix_multiply */ }; /* * Implement len() for the type. */ static Py_ssize_t sipVoidPtr_length(PyObject *self) { if (check_size(self) < 0) return -1; return ((sipVoidPtrObject *)self)->size; } /* * Implement sequence item sub-script for the type. */ static PyObject *sipVoidPtr_item(PyObject *self, Py_ssize_t idx) { if (check_size(self) < 0 || check_index(self, idx) < 0) return NULL; return PyBytes_FromStringAndSize( (char *)((sipVoidPtrObject *)self)->voidptr + idx, 1); } /* The sequence methods data structure. */ static PySequenceMethods sipVoidPtr_SequenceMethods = { sipVoidPtr_length, /* sq_length */ 0, /* sq_concat */ 0, /* sq_repeat */ sipVoidPtr_item, /* sq_item */ 0, /* sq_slice */ 0, /* sq_ass_item */ 0, /* sq_ass_slice */ 0, /* sq_contains */ 0, /* sq_inplace_concat */ 0, /* sq_inplace_repeat */ }; /* * Implement mapping sub-script for the type. */ static PyObject *sipVoidPtr_subscript(PyObject *self, PyObject *key) { sipVoidPtrObject *v; if (check_size(self) < 0) return NULL; v = (sipVoidPtrObject *)self; if (PyIndex_Check(key)) { Py_ssize_t idx = PyNumber_AsSsize_t(key, PyExc_IndexError); if (idx == -1 && PyErr_Occurred()) return NULL; if (idx < 0) idx += v->size; return sipVoidPtr_item(self, idx); } if (PySlice_Check(key)) { Py_ssize_t start, stop, step, slicelength; if (sip_api_convert_from_slice_object(key, v->size, &start, &stop, &step, &slicelength) < 0) return NULL; if (step != 1) { PyErr_SetNone(PyExc_NotImplementedError); return NULL; } return make_voidptr((char *)v->voidptr + start, slicelength, v->rw); } bad_key(key); return NULL; } /* * Implement mapping assignment sub-script for the type. */ static int sipVoidPtr_ass_subscript(PyObject *self, PyObject *key, PyObject *value) { sipVoidPtrObject *v; Py_ssize_t start, size; Py_buffer value_view; if (check_rw(self) < 0 || check_size(self) < 0) return -1; v = (sipVoidPtrObject *)self; if (PyIndex_Check(key)) { start = PyNumber_AsSsize_t(key, PyExc_IndexError); if (start == -1 && PyErr_Occurred()) return -1; if (start < 0) start += v->size; if (check_index(self, start) < 0) return -1; size = 1; } else if (PySlice_Check(key)) { Py_ssize_t stop, step; if (sip_api_convert_from_slice_object(key, v->size, &start, &stop, &step, &size) < 0) return -1; if (step != 1) { PyErr_SetNone(PyExc_NotImplementedError); return -1; } } else { bad_key(key); return -1; } if (PyObject_GetBuffer(value, &value_view, PyBUF_CONTIG_RO) < 0) return -1; /* We could allow any item size... */ if (value_view.itemsize != 1) { PyErr_Format(PyExc_TypeError, "'%s' must have an item size of 1", Py_TYPE(value_view.obj)->tp_name); PyBuffer_Release(&value_view); return -1; } if (check_slice_size(size, value_view.len) < 0) { PyBuffer_Release(&value_view); return -1; } memmove((char *)v->voidptr + start, value_view.buf, size); PyBuffer_Release(&value_view); return 0; } /* The mapping methods data structure. */ static PyMappingMethods sipVoidPtr_MappingMethods = { sipVoidPtr_length, /* mp_length */ sipVoidPtr_subscript, /* mp_subscript */ sipVoidPtr_ass_subscript, /* mp_ass_subscript */ }; /* * The buffer implementation for Python v2.6.3 and later. */ static int sipVoidPtr_getbuffer(PyObject *self, Py_buffer *buf, int flags) { sipVoidPtrObject *v; if (check_size(self) < 0) return -1; v = (sipVoidPtrObject *)self; return PyBuffer_FillInfo(buf, self, v->voidptr, v->size, !v->rw, flags); } /* The buffer methods data structure. */ static PyBufferProcs sipVoidPtr_BufferProcs = { sipVoidPtr_getbuffer, /* bf_getbuffer */ 0 /* bf_releasebuffer */ }; /* * Implement __new__ for the type. */ static PyObject *sipVoidPtr_new(PyTypeObject *cls, PyObject *args, PyObject *kw) { #if PY_VERSION_HEX >= 0x030d0000 static char * const kwlist[] = {"address", "size", "writeable", NULL}; #else static char *kwlist[] = {"address", "size", "writeable", NULL}; #endif struct vp_values vp_conversion; Py_ssize_t size = -1; int rw = -1; PyObject *obj; if (!PyArg_ParseTupleAndKeywords(args, kw, "O&|ni:voidptr", kwlist, vp_convertor, &vp_conversion, &size, &rw)) return NULL; /* Use the explicit size if one was given. */ if (size >= 0) vp_conversion.size = size; /* Use the explicit writeable flag if one was given. */ if (rw >= 0) vp_conversion.rw = rw; /* Create the instance. */ if ((obj = cls->tp_alloc(cls, 0)) == NULL) return NULL; /* Save the values. */ ((sipVoidPtrObject *)obj)->voidptr = vp_conversion.voidptr; ((sipVoidPtrObject *)obj)->size = vp_conversion.size; ((sipVoidPtrObject *)obj)->rw = vp_conversion.rw; return obj; } /* The type data structure. */ PyTypeObject sipVoidPtr_Type = { PyVarObject_HEAD_INIT(NULL, 0) _SIP_MODULE_FQ_NAME ".voidptr", /* tp_name */ sizeof (sipVoidPtrObject), /* tp_basicsize */ 0, /* tp_itemsize */ 0, /* tp_dealloc */ 0, /* tp_print */ 0, /* tp_getattr */ 0, /* tp_setattr */ 0, /* tp_reserved (Python v3), tp_compare (Python v2) */ 0, /* tp_repr */ &sipVoidPtr_NumberMethods, /* tp_as_number */ &sipVoidPtr_SequenceMethods, /* tp_as_sequence */ &sipVoidPtr_MappingMethods, /* tp_as_mapping */ 0, /* tp_hash */ 0, /* tp_call */ 0, /* tp_str */ 0, /* tp_getattro */ 0, /* tp_setattro */ &sipVoidPtr_BufferProcs, /* tp_as_buffer */ #if defined(Py_TPFLAGS_HAVE_NEWBUFFER) Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_NEWBUFFER, /* tp_flags */ #else Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /* tp_flags */ #endif 0, /* tp_doc */ 0, /* tp_traverse */ 0, /* tp_clear */ 0, /* tp_richcompare */ 0, /* tp_weaklistoffset */ 0, /* tp_iter */ 0, /* tp_iternext */ sipVoidPtr_Methods, /* tp_methods */ 0, /* tp_members */ 0, /* tp_getset */ 0, /* tp_base */ 0, /* tp_dict */ 0, /* tp_descr_get */ 0, /* tp_descr_set */ 0, /* tp_dictoffset */ 0, /* tp_init */ 0, /* tp_alloc */ sipVoidPtr_new, /* tp_new */ 0, /* tp_free */ 0, /* tp_is_gc */ 0, /* tp_bases */ 0, /* tp_mro */ 0, /* tp_cache */ 0, /* tp_subclasses */ 0, /* tp_weaklist */ 0, /* tp_del */ 0, /* tp_version_tag */ 0, /* tp_finalize */ 0, /* tp_vectorcall */ }; /* * A convenience function to convert a C/C++ void pointer from a Python object. */ void *sip_api_convert_to_void_ptr(PyObject *obj) { struct vp_values vp; if (obj == NULL) { PyErr_SetString(PyExc_TypeError, _SIP_MODULE_FQ_NAME ".voidptr is NULL"); return NULL; } if (vp_convertor(obj, &vp)) return vp.voidptr; return PyLong_AsVoidPtr(obj); } /* * Convert a C/C++ void pointer to a sip.voidptr object. */ PyObject *sip_api_convert_from_void_ptr(void *val) { return make_voidptr(val, -1, TRUE); } /* * Convert a C/C++ void pointer to a sip.voidptr object. */ PyObject *sip_api_convert_from_const_void_ptr(const void *val) { return make_voidptr((void *)val, -1, FALSE); } /* * Convert a sized C/C++ void pointer to a sip.voidptr object. */ PyObject *sip_api_convert_from_void_ptr_and_size(void *val, Py_ssize_t size) { return make_voidptr(val, size, TRUE); } /* * Convert a sized C/C++ const void pointer to a sip.voidptr object. */ PyObject *sip_api_convert_from_const_void_ptr_and_size(const void *val, Py_ssize_t size) { return make_voidptr((void *)val, size, FALSE); } /* * Check that a void pointer has an explicit size and raise an exception if it * hasn't. */ static int check_size(PyObject *self) { if (((sipVoidPtrObject *)self)->size >= 0) return 0; PyErr_SetString(PyExc_IndexError, _SIP_MODULE_FQ_NAME ".voidptr object has an unknown size"); return -1; } /* * Check that a void pointer is writable. */ static int check_rw(PyObject *self) { if (((sipVoidPtrObject *)self)->rw) return 0; PyErr_SetString(PyExc_TypeError, "cannot modify a read-only " _SIP_MODULE_FQ_NAME ".voidptr object"); return -1; } /* * Check that an index is valid for a void pointer. */ static int check_index(PyObject *self, Py_ssize_t idx) { if (idx >= 0 && idx < ((sipVoidPtrObject *)self)->size) return 0; PyErr_SetString(PyExc_IndexError, "index out of bounds"); return -1; } /* * Raise an exception about a bad sub-script key. */ static void bad_key(PyObject *key) { PyErr_Format(PyExc_TypeError, "cannot index a " _SIP_MODULE_FQ_NAME ".voidptr object using '%s'", Py_TYPE(key)->tp_name); } /* * Check that the size of a value is the same as the size of the slice it is * replacing. */ static int check_slice_size(Py_ssize_t size, Py_ssize_t value_size) { if (value_size == size) return 0; PyErr_SetString(PyExc_ValueError, "cannot modify the size of a " _SIP_MODULE_FQ_NAME ".voidptr object"); return -1; } /* * Do the work of converting a void pointer. */ static PyObject *make_voidptr(void *voidptr, Py_ssize_t size, int rw) { sipVoidPtrObject *self; if (voidptr == NULL) { Py_INCREF(Py_None); return Py_None; } if ((self = PyObject_NEW(sipVoidPtrObject, &sipVoidPtr_Type)) == NULL) return NULL; self->voidptr = voidptr; self->size = size; self->rw = rw; return (PyObject *)self; } /* * Convert a Python object to the values needed to create a voidptr. */ static int vp_convertor(PyObject *arg, struct vp_values *vp) { void *ptr; Py_ssize_t size = -1; int rw = TRUE; if (arg == Py_None) { ptr = NULL; } else if (PyCapsule_CheckExact(arg)) { ptr = PyCapsule_GetPointer(arg, NULL); } else if (PyObject_TypeCheck(arg, &sipVoidPtr_Type)) { ptr = ((sipVoidPtrObject *)arg)->voidptr; size = ((sipVoidPtrObject *)arg)->size; rw = ((sipVoidPtrObject *)arg)->rw; } else if (PyObject_CheckBuffer(arg)) { Py_buffer view; if (PyObject_GetBuffer(arg, &view, PyBUF_SIMPLE) < 0) return 0; ptr = view.buf; size = view.len; rw = !view.readonly; PyBuffer_Release(&view); } else { PyErr_Clear(); ptr = PyLong_AsVoidPtr(arg); if (PyErr_Occurred()) { PyErr_SetString(PyExc_TypeError, "a single integer, Capsule, None, bytes-like object or another " _SIP_MODULE_FQ_NAME ".voidptr object is required"); return 0; } } vp->voidptr = ptr; vp->size = size; vp->rw = rw; return 1; } /* * Get a size possibly supplied as an argument, otherwise get it from the * object. Raise an exception if there was no size specified. */ static Py_ssize_t get_size_from_arg(sipVoidPtrObject *v, Py_ssize_t size) { /* Use the current size if one wasn't explicitly given. */ if (size < 0) size = v->size; if (size < 0) PyErr_SetString(PyExc_ValueError, "a size must be given or the " _SIP_MODULE_FQ_NAME ".voidptr object must have a size"); return size; } sip-6.8.6/sipbuild/project.py000066400000000000000000001003631464421045000161640ustar00rootroot00000000000000# SPDX-License-Identifier: BSD-2-Clause # Copyright (c) 2024 Phil Thompson import collections import os import packaging import shutil import subprocess import sys import sysconfig import tempfile import warnings from .abstract_builder import AbstractBuilder from .abstract_project import AbstractProject from .bindings import Bindings from .buildable import BuildableFromSources from .configurable import Configurable, Option from .exceptions import deprecated, UserException from .module import resolve_abi_version from .py_versions import OLDEST_SUPPORTED_MINOR from .pyproject import PyProjectException, PyProjectOptionException # The macOS minimum version of the latest supported version of Python. v10.13 # is the minimum version required by Python v3.13. MACOS_MIN_OS_MAJOR = 10 MACOS_MIN_OS_MINOR = 13 class Project(AbstractProject, Configurable): """ Encapsulate a project containing one or more sets of bindings. """ # The configurable options. _options = ( # The minimum required ABI version of the sip module. Option('abi_version'), # The callable that will return an Bindings instance. This is used for # bindings implicitly defined in the .toml file. Option('bindings_factory'), # The callable that will return an AbstractBuilder instance. Option('builder_factory'), # The list of console script entry points. Option('console_scripts', option_type=list), # Set if an __init__.py should be installed. Option('dunder_init', option_type=bool, default=False), # The list of GUI script entry points. Option('gui_scripts', option_type=list), # The minimum macOS version required by the project. This is used to # determine the correct platform tag to use for macOS wheels. Option('minimum_macos_version'), # Set if building for a debug version of Python. Option('py_debug', option_type=bool), # The name of the directory containing Python.h. Option('py_include_dir', default=sysconfig.get_path('include')), # The name of the target Python platform. Option('py_platform'), # The major version number of the target Python installation. Option('py_major_version', option_type=int), # The minor version number of the target Python installation. Option('py_minor_version', option_type=int), # The name of the directory containing the .sip files. If the sip # module is shared then each set of bindings is in its own # sub-directory. Option('sip_files_dir', default='.'), # The list of files and directories, specified as glob patterns # relative to the project directory, that should be excluded from an # sdist. Option('sdist_excludes', option_type=list), # The list of additional directories to search for .sip files. Option('sip_include_dirs', option_type=list), # The fully qualified name of the sip module. Option('sip_module'), # The list of files and directories, specified as glob patterns, that # should be included in a wheel. If a pattern is relative then it is # taken as being relative to the project directory. If an element of # the list is a string then it is a pattern and files and directories # are installed in the target directory. If an element is a 2-tuple # then the first part is the pattern and the second part is the name of # a sub-directory relative to the target directory where the files and # directories are installed. Option('wheel_includes', option_type=list), # The user-configurable options. Option('quiet', option_type=bool, help="disable all progress messages"), Option('verbose', option_type=bool, help="enable verbose progress messages"), Option('name', help="the name used in sdist and wheel file names", metavar="NAME", tools=['sdist', 'wheel']), Option('build_dir', help="the build directory", metavar="DIR"), Option('build_tag', help="the build tag to be used in the wheel name", metavar="TAG", tools=['wheel']), Option('distinfo', option_type=bool, inverted=True, help="don't create a .dist-info directory", tools=['install']), Option('manylinux', option_type=bool, inverted=True, help="disable the use of manylinux in the platform tag used " "in the wheel name", tools=['wheel']), Option('minimum_glibc_version', help="the minimum GLIBC version to be used in the platform " "tag of Linux wheels", metavar="M.N", tools=['wheel']), Option('scripts_dir', default=os.path.dirname(sys.executable), help="the scripts installation directory", metavar="DIR", tools=['build', 'install']), Option('target_dir', default=sysconfig.get_path('platlib'), help="the target installation directory", metavar="DIR", tools=['build', 'install']), Option('api_dir', help="generate a QScintilla .api file in DIR", metavar="DIR"), Option('compile', option_type=bool, inverted=True, help="disable the compilation of the generated code", tools=['build']), Option('version_info', option_type=bool, inverted=True, help="disable any reference to the SIP version number in " "generated code", tools=['build']), ) # The configurable options for multiple bindings. _multibindings_options = ( Option('disable', option_type=list, help="disable the NAME bindings", metavar="NAME"), Option('enable', option_type=list, help="enable the NAME bindings", metavar="NAME"), ) def __init__(self, **kwargs): """ Initialise the project. """ super().__init__() # The current directory should contain the .toml file. self.root_dir = os.getcwd() self.arguments = None self.bindings = collections.OrderedDict() self.bindings_factories = [] self.builder = None self.buildables = [] self.installables = [] self._metadata_overrides = None self._temp_build_dir = None self.initialise_options(kwargs) @property def all_modules_use_limited_abi(self): """ True if all modules in the project use the limited ABI. """ for buildable in self.buildables: if isinstance(buildable, BuildableFromSources): if not buildable.uses_limited_api: return False return True def apply_nonuser_defaults(self, tool): """ Set default values for non-user options that haven't been set yet. """ if self.bindings_factory is None: self.bindings_factory = Bindings elif isinstance(self.bindings_factory, str): # Convert the name to a callable. self.bindings_factory = self.import_callable(self.bindings_factory, Bindings) if self.py_major_version is None or self.py_minor_version is None: self.py_major_version = sys.hexversion >> 24 self.py_minor_version = (sys.hexversion >> 16) & 0x0ff if self.builder_factory is None: if (self.py_major_version, self.py_minor_version) >= (3, 10): from .setuptools_builder import SetuptoolsBuilder self.builder_factory = SetuptoolsBuilder else: from .distutils_builder import DistutilsBuilder self.builder_factory = DistutilsBuilder elif isinstance(self.builder_factory, str): # Convert the name to a callable. self.builder_factory = self.import_callable(self.builder_factory, AbstractBuilder) if self.py_platform is None: self.py_platform = sys.platform if self.py_debug is None: self.py_debug = hasattr(sys, 'gettotalrefcount') super().apply_nonuser_defaults(tool) def apply_user_defaults(self, tool): """ Set default values for user options that haven't been set yet. """ # This is only used when creating sdist and wheel files. if self.name is None: self.name = self.metadata['name'] # For the build tool we want build_dir to default to a local 'build' # directory (which we won't remove). However, for other tools (and for # PEP 517 frontends) we want to use a temporary directory in case the # current directory is read-only. if self.build_dir is None: if tool == 'build': self.build_dir = 'build' else: self._temp_build_dir = tempfile.TemporaryDirectory() self.build_dir = self._temp_build_dir.name super().apply_user_defaults(tool) # Adjust the list of bindings according to what has been explicitly # enabled and disabled. self._enable_disable_bindings() # Set the user defaults for the builder and bindings. self.builder.apply_user_defaults(tool) for bindings in self.bindings.values(): bindings.apply_user_defaults(tool) def build(self): """ Build the project in-situ. """ self.builder.build() def build_sdist(self, sdist_directory): """ Build an sdist for the project and return the name of the sdist file. """ sdist_file = self.builder.build_sdist(sdist_directory) self._remove_build_dir() return sdist_file def build_wheel(self, wheel_directory): """ Build a wheel for the project and return the name of the wheel file. """ wheel_file = self.builder.build_wheel(wheel_directory) self._remove_build_dir() return wheel_file def get_bindings_dir(self): """ Return the name of the 'bindings' directory relative to the eventual target directory. """ return os.path.join(self.get_package_dir(), 'bindings') def get_distinfo_dir(self, target_dir): """ Return the name of the .dist-info directory for a target directory. """ return os.path.join(target_dir, '{}-{}.dist-info'.format(self.name.replace('-', '_'), self.version_str)) def get_dunder_init(self): """ Return the contents of the __init__.py to install. """ # This default implementation will create an empty file. return '' def get_metadata_overrides(self): """ Return a mapping of PEP 566 metadata names and values that will override any corresponding values defined in the pyproject.toml file. A typical use is to determine a project's version dynamically. """ # This default implementation does not override any metadata. return {} def get_options(self): """ Return the list of configurable options. """ options = super().get_options() options.extend(self._options) options.extend(self._multibindings_options) return options def get_package_dir(self): """ Return the name of the package directory relative to the eventual target directory. This is the directory containing the shared sip module (if there is one) or the target directory (if not). It will normally be where the individual bindings are installed. """ if self.sip_module: name_parts = self.sip_module.split('.') del name_parts[-1] return os.path.join(*name_parts) return '' def get_platform_tag(self): """ Return the platform tag to use in a wheel name. This default implementation uses the platform name and applies PEP defined conventions depending on OS version and GLIBC version as appropriate. """ platform_tag = sysconfig.get_platform() if self.py_platform == 'darwin': # We expect a three part tag so leave anything else unchanged. parts = platform_tag.split('-') if len(parts) == 3: version = parts[1] if '.' in version: min_major, min_minor = version.split('.') min_major = int(min_major) min_minor = int(min_minor) else: min_major = int(version) min_minor = 0 # Use the user supplied value if it is later than the platform # value. if self.minimum_macos_version: user_min_major = int(self.minimum_macos_version[0]) user_min_minor = int(self.minimum_macos_version[1]) if (min_major, min_minor) < (user_min_major, user_min_minor): min_major = user_min_major min_minor = user_min_minor # Enforce a minimum macOS version for limited ABI projects. if self.all_modules_use_limited_abi and (min_major, min_minor) < (MACOS_MIN_OS_MAJOR, MACOS_MIN_OS_MINOR): min_major = MACOS_MIN_OS_MAJOR min_minor = MACOS_MIN_OS_MINOR # Enforce a minimum macOS version for arm64 binaries. if parts[2] == 'arm64' and min_major < 11: min_major = 11 min_minor = 0 # Reassemble the tag. parts[1] = f'{min_major}.{min_minor}' platform_tag = '-'.join(parts) elif self.py_platform == 'linux' and self.manylinux: # We expect a two part tag so leave anything else unchanged. parts = platform_tag.split('-') if len(parts) == 2: if self.minimum_glibc_version: major, minor = self.minimum_glibc_version else: major, minor = 2, 5 parts[0] = 'manylinux' parts.insert(1, f'{major}.{minor}') platform_tag = '-'.join(parts) return platform_tag.replace('.', '_').replace('-', '_') def get_requires_dists(self): """ Return any 'Requires-Dist' to add to the project's meta-data. """ # The only requirement is for the sip module. if not self.sip_module: return [] requires_dist = self.metadata.get('requires-dist') if requires_dist is None: requires_dist = [] elif isinstance(requires_dist, str): requires_dist = [requires_dist] # Ignore if the module is already defined. sip_project_name = self.sip_module.replace('.', '-') for rd in requires_dist: if rd.split()[0] == sip_project_name: return [] next_abi_major = int(self.abi_version.split('.')[0]) + 1 return [f'{sip_project_name} (>={self.abi_version}, <{next_abi_major})'] def get_sip_distinfo_command_line(self, sip_distinfo, inventory, generator=None, wheel_tag=None, generator_version=None): """ Return a sequence of command line arguments to invoke sip-distinfo. """ args = [ sip_distinfo, '--inventory', inventory, '--project-root', self.root_dir, '--prefix', '\\"$(INSTALL_ROOT)\\"', ] if generator is not None: args.append('--generator') args.append(generator) if generator_version is not None: args.append('--generator-version') args.append(generator_version) if wheel_tag is not None: args.append('--wheel-tag') args.append(wheel_tag) for ep in self.console_scripts: args.append('--console-script') args.append(ep.replace(' ', '')) for ep in self.gui_scripts: args.append('--gui-script') args.append(ep.replace(' ', '')) for rd in self.get_requires_dists(): args.append('--requires-dist') args.append('\\"{}\\"'.format(rd)) for metadata, value in self._metadata_overrides.items(): if value: metadata += '=' + value if ' ' in metadata: metadata = '\\"' + metadata + '\\"' args.append('--metadata') args.append(metadata) return args def install(self): """ Install the project. """ self.builder.install() self._remove_build_dir() @property def minimum_glibc_version(self): """ The getter for the minimum GLIBC version. """ return self._minimum_glibc_version @minimum_glibc_version.setter def minimum_glibc_version(self, value): """ The setter for the minimum GLIBC version. """ # Handle the initial creation of the option value. if value is None and not hasattr(self, '_minimum_glibc_version'): self._minimum_glibc_version = None return # Make sure any minimum GLIBC version is valid and convert it to a # 2-tuple. if value: try: value = self._convert_major_minor(value) except ValueError: raise PyProjectOptionException('minimum-glibc-version', "'{0}' is an invalid GLIBC version number".format( value), section_name='tool.sip.project') self._minimum_glibc_version = value @property def minimum_macos_version(self): """ The getter for the minimum macOS version. """ return self._minimum_macos_version @minimum_macos_version.setter def minimum_macos_version(self, value): """ The setter for the minimum macOS version. """ # Handle the initial creation of the option value. if value is None and not hasattr(self, '_minimum_macos_version'): self._minimum_macos_version = None return # Make sure any minimum macOS version is valid and convert it to a # 2-tuple. if value: try: value = self._convert_major_minor(value) except ValueError: raise PyProjectOptionException('minimum-macos-version', "'{0}' is an invalid macOS version number".format( value), section_name='tool.sip.project') self._minimum_macos_version = value @staticmethod def open_for_writing(fname): """ Open a file for writing while handling any errors. """ try: return open(fname, 'w') except IOError as e: raise UserException( "There was an error creating '{0}' - make sure you have " " write permission on the parent directory".format(fname), detail=str(e)) def progress(self, message): """ Print a progress message unless they are disabled. """ if not self.quiet: if message[-1] != '.': message += '...' print(message, flush=True) def project_path(self, path, relative_to=None): """ Return a normalised version of a path. A relative path is assumed to be relate to the project directory or some other provided directory. """ path = os.path.normpath(path) if os.path.isabs(path): return path if relative_to is None: relative_to = self.root_dir return os.path.normpath(os.path.join(relative_to, path)) def read_command_pipe(self, args, *, and_stderr=False, fatal=True): """ A generator for each line of a pipe from a command's stdout. """ cmd = ' '.join(args) if self.verbose: print(cmd, flush=True) stderr = subprocess.STDOUT if and_stderr else subprocess.PIPE with subprocess.Popen(cmd, shell=True, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=stderr) as pipe: for line in pipe.stdout: yield str(line, encoding=sys.stdout.encoding) if pipe.returncode != 0 and fatal: raise UserException( "'{0}' failed returning {1}".format(cmd, pipe.returncode)) def run_command(self, args, *, fatal=True): """ Run a command and display the output if requested. """ # Read stdout and stderr until there is no more output. for line in self.read_command_pipe(args, and_stderr=True, fatal=fatal): if self.verbose: sys.stdout.write(line) def setup(self, pyproject, tool, tool_description): """ Complete the configuration of the project. """ # Create any programmatically defined bindings. for bindings_factory in self.bindings_factories: bindings = bindings_factory(self) self.bindings[bindings.name] = bindings # Set the initial configuration from the pyproject.toml file. self._set_initial_configuration(pyproject, tool) # Add any tool-specific command line arguments for (so far unspecified) # parts of the configuration. self._configure_from_arguments(tool, tool_description) # Now that any help has been given we can report a missing # pyproject.toml file. if pyproject.pyproject is None: raise PyProjectException( "there is no such file in the current directory") # Make sure the configuration is complete. self.apply_user_defaults(tool) # Configure the warnings module. if not self.verbose: warnings.simplefilter('ignore', UserWarning) # Make sure we have a clean build directory and make it current. if self._temp_build_dir is None: self.build_dir = os.path.abspath(self.build_dir) shutil.rmtree(self.build_dir, ignore_errors=True) os.mkdir(self.build_dir) os.chdir(self.build_dir) # Allow a sub-class (in a user supplied script) to make any updates to # the configuration. self.update(tool) os.chdir(self.root_dir) # Make sure the configuration is correct after any user supplied script # has messed with it. self.verify_configuration(tool) if tool in Option.BUILD_TOOLS and self.bindings: self.progress( "These bindings will be built: {}.".format( ', '.join(self.bindings.keys()))) def update(self, tool): """ This should be re-implemented by any user supplied sub-class to carry out any updates to the configuration as required. The current directory will be the temporary build directory. """ # This default implementation calls update_buildable_bindings(). if tool in Option.BUILD_TOOLS: self.update_buildable_bindings() def update_buildable_bindings(self): """ Update the list of bindings to ensure they are either buildable or have been explicitly enabled. """ # Explicitly enabled bindings are assumed to be buildable. if self.enable: return for b in list(self.bindings.values()): if not b.is_buildable(): del self.bindings[b.name] if len(self.bindings) == 0: raise UserException("There are no bindings that can be built") def verify_configuration(self, tool): """ Verify that the configuration is complete and consistent. """ # Make sure any build tag is valid. if self.build_tag and not self.build_tag[0].isdigit(): raise PyProjectOptionException('build-tag', "'{0}' must begin with a digit".format(self.build_tag), section_name='tool.sip.project') # Make sure relevent paths are absolute and use native separators. self.sip_files_dir = self.project_path(self.sip_files_dir) self.sip_include_dirs = [self.project_path(d) for d in self.sip_include_dirs] # Check that the targeted version of Python isn't too old. We hope # that we will support newer versions automatically, but it's not # guaranteed. py_version = (self.py_major_version, self.py_minor_version) first_version = (3, OLDEST_SUPPORTED_MINOR) if py_version < first_version or self.py_major_version > 3: raise UserException( "Python v{}.{} is not supported".format( self.py_major_version, self.py_minor_version)) # Get the supported ABI version. (The actual version may have a later # minor version.) self.abi_version = resolve_abi_version(self.abi_version, module=False) # These ABI versions are deprecated because we have deprecated any # arguments to 'throw()' which these versions rely on. abi_version = tuple([int(v) for v in self.abi_version.split('.')]) if abi_version < (12, 9): self._deprecated_abi_version('12.9') if abi_version == (13, 0): self._deprecated_abi_version('13.1') # Checks for standalone projects. if tool in Option.BUILD_TOOLS and not self.sip_module: # Check there is only one set of bindings. if len(self.bindings) > 1: raise PyProjectOptionException('sip-module', "must be defined when the project contains multiple " "sets of bindings") # Make sure __init__.py is disabled. self.dunder_init = False # Check any wheel includes are valid and make sure all elements are # 2-tuples. normalised = [] for wheel_include in self.wheel_includes: if isinstance(wheel_include, str): normalised.append((wheel_include, None)) else: try: wheel_include, subdir = wheel_include except TypeError: wheel_include = subdir = None if isinstance(wheel_include, str) and isinstance(subdir, str): normalised.append((wheel_include, subdir)) else: raise PyProjectOptionException('wheel-includes', "elements must be strings or 2-tuples of strings") self.wheel_includes = normalised # Make sure that any .api directory is relative when building a wheel. if tool == 'wheel' and self.api_dir: if os.path.isabs(self.api_dir) or os.path.dirname(self.api_dir) == '..': raise PyProjectOptionException('api-dir', "must be relative when building a wheel") # Verify the configuration of the builder and bindings. self.builder.verify_configuration(tool) for bindings in self.bindings.values(): bindings.verify_configuration(tool) def _configure_from_arguments(self, tool, tool_description): """ Update the configuration from any user supplied arguments. """ from argparse import SUPPRESS from .argument_parser import ArgumentParser parser = ArgumentParser(tool_description, build_tool=True, argument_default=SUPPRESS) # Add the user configurable options to the parser. all_options = {} options = self.get_options() if len(self.bindings) < 2: # Remove the options that only make sense where the project has # multiple bindings. for multi in self._multibindings_options: options.remove(multi) self.add_command_line_options(parser, tool, all_options, options=options) self.builder.add_command_line_options(parser, tool, all_options) for bindings in self.bindings.values(): bindings.add_command_line_options(parser, tool, all_options) # Parse the arguments and update the corresponding configurables. args = parser.parse_args(self.arguments) for option, configurables in all_options.items(): for configurable in configurables: if hasattr(args, option.dest): setattr(configurable, option.name, getattr(args, option.dest)) @staticmethod def _convert_major_minor(value): """ Convert a 'major.minor' version number to a 2-tuple of integers. Raise a ValueError exception if it is invalid. """ parts = value.split('.') if len(parts) != 2: raise ValueError() return int(parts[0]), int(parts[1]) def _deprecated_abi_version(self, instead): """ Issue a deprecation warning about an old ABI version. """ deprecated(f"ABI v{self.abi_version}", instead=f"v{instead} or later") def _enable_disable_bindings(self): """ Check the enabled bindings are valid and remove any disabled ones. """ names = list(self.bindings.keys()) # Check that any explicitly enabled bindings are valid. if self.enable: for enabled in self.enable: if enabled not in names: raise UserException( "Unknown enabled bindings '{0}'".format(enabled)) # Only include explicitly enabled bindings. for b in list(self.bindings.values()): if b.name not in self.enable: del self.bindings[b.name] # Check that any explicitly disabled bindings are valid. if self.disable: for disabled in self.disable: if disabled not in names: raise UserException( "Unknown disabled bindings '{0}'".format(disabled)) # Remove any explicitly disabled bindings. for b in list(self.bindings.values()): if b.name in self.disable: del self.bindings[b.name] def _remove_build_dir(self): """ Remove the build directory. """ self._temp_build_dir = None def _set_initial_configuration(self, pyproject, tool): """ Set the project's initial configuration. """ # Get the metadata and extract the version. self.metadata = pyproject.get_metadata() self._metadata_overrides = self.get_metadata_overrides() self.metadata.update(self._metadata_overrides) self.version_str = self.metadata['version'] # Convert the version as a string to number. base_version = packaging.version.parse(self.version_str).base_version base_version = base_version.split('.') while len(base_version) < 3: base_version.append('0') version = 0 for part in base_version: version <<= 8 try: version += int(part) except ValueError: raise PyProjectOptionException('version', "'{0}' is an invalid version number".format( self.version_str), section_name='tool.sip.metadata') self.version = version # Configure the project. self.configure(pyproject, 'tool.sip.project', tool) # Create and configure the builder. self.builder = self.builder_factory(self) self.builder.configure(pyproject, 'tool.sip.builder', tool) # For each set of bindings configuration make sure a bindings object # exists, creating it if necessary. bindings_sections = pyproject.get_section('tool.sip.bindings') if bindings_sections is not None: for name in bindings_sections.keys(): if name not in self.bindings: bindings = self.bindings_factory(self, name) self.bindings[bindings.name] = bindings # Add a default set of bindings if none were defined. if not self.bindings: bindings = self.bindings_factory(self, self.metadata['name']) self.bindings[bindings.name] = bindings # Now configure each set of bindings. for bindings in self.bindings.values(): bindings.configure(pyproject, 'tool.sip.bindings.' + bindings.name, tool) sip-6.8.6/sipbuild/py_versions.py000066400000000000000000000002721464421045000170740ustar00rootroot00000000000000# SPDX-License-Identifier: BSD-2-Clause # Copyright (c) 2024 Phil Thompson # The oldest supported minor version of Python v3. OLDEST_SUPPORTED_MINOR = 8 sip-6.8.6/sipbuild/pyproject.py000066400000000000000000000375141464421045000165440ustar00rootroot00000000000000# SPDX-License-Identifier: BSD-2-Clause # Copyright (c) 2024 Phil Thompson from .exceptions import deprecated, UserFileException from .py_versions import OLDEST_SUPPORTED_MINOR from .toml import toml_loads class PyProjectException(UserFileException): """ An exception related to a pyproject.toml file. """ def __init__(self, text, *, detail=None): """ Initialise the exception. """ super().__init__("pyproject.toml", text, detail=detail) class PyProjectOptionException(PyProjectException): """ An exception related to a specific option of a pyproject.toml file. """ def __init__(self, name, text, *, section_name=None, detail=None): """ Initialise the exception. """ if section_name is None: section_name = 'tool.sip.project' super().__init__("'{0}.{1}': {2}".format(section_name, name, text), detail=detail) class PyProjectTypeOptionException(PyProjectOptionException): """ An exception related to a badly typed option of a pyproject.toml file. """ def __init__(self, name, types, *, section_name=None): """ Initialise the exception. """ super().__init__(name, "value must be " + types, section_name=section_name) class PyProjectUndefinedOptionException(PyProjectOptionException): """ An exception related to an undefined option of a pyproject.toml file. """ def __init__(self, name, *, section_name=None): """ Initialise the exception. """ super().__init__(name, "must be defined", section_name=section_name) class PyProject: """ Encapsulate a parsed pyproject.toml file. """ # The mapping of PEP 621 metadata to core metadata. _PEP621_CORE_MAP = { 'name': 'name', 'version': 'version', 'description': 'summary', 'requires-python': 'requires-python', 'classifiers': 'classifier', } def __init__(self): """ Initialise the object. """ try: with open('pyproject.toml', encoding='UTF-8') as f: self._raw_toml = f.read() self.pyproject = toml_loads(self._raw_toml) except FileNotFoundError: # Delay the exception in case the user is asking for help. self.pyproject = None except UnicodeDecodeError as e: raise PyProjectException("is not a UTF-8 encoded file", details=str(e)) except Exception as e: # Raise the exception now about a possibly badly formed file. raise PyProjectException(str(e)) def get_metadata(self): """ Return a dict containing the PEP 566 metadata. """ if self.pyproject is None: # Provide a minimal default. return dict(name='unknown', version='0.1') # See if PEP 621 project metadata is supplied. Make this required when # support for the legacy metadata is removed. metadata = self.get_section('project') if metadata is None: core_metadata = self._get_legacy_metadata() else: # The main validation we do is to check that the keys are defined # by PEP 621. core_metadata = {'metadata-version': '2.1'} for md_name, md_value in metadata.items(): md_name = md_name.lower() # See if special handling is required. if md_name == 'readme': self._handle_readme(md_value, core_metadata) elif md_name == 'license': self._handle_license(md_value, core_metadata) elif md_name == 'authors': self._handle_people(md_value, 'author', core_metadata) elif md_name == 'maintainers': self._handle_people(md_value, 'maintainer', core_metadata) elif md_name == 'keywords': self._handle_keywords(md_value, core_metadata) elif md_name == 'urls': self._handle_urls(md_value, core_metadata) elif md_name == 'dependencies': self._handle_dependencies(md_value, core_metadata) elif md_name == 'optional-dependencies': self._handle_optional_dependencies(md_value, core_metadata) elif md_name in ('scripts', 'gui-scripts', 'entry-points'): # We ignore these for the moment and stick with using # [tool.sip.project]. pass else: core_name = self._PEP621_CORE_MAP.get(md_name) if core_name is None: raise PyProjectOptionException(md_name, "is an unsupported project key", section_name='project') core_metadata[core_name] = md_value # Check that the name has been specified. if 'name' not in core_metadata: raise PyProjectUndefinedOptionException('name', section_name='project') if 'version' not in core_metadata: core_metadata['version'] = '0.1' if 'requires-python' not in core_metadata: # The minimal version of Python we support. core_metadata['requires-python'] = '>=3.{}'.format( OLDEST_SUPPORTED_MINOR) return core_metadata def get_section(self, section_name, *, required=False): """ Return a sub-section with a dotted name. """ if self.pyproject is None: return None section = self.pyproject for part in section_name.split('.'): try: section = section[part] except KeyError: if required: raise PyProjectException( "the '[{0}]' section is missing".format( section_name)) return None if not self._is_section(section): raise PyProjectException( "'{0}' is not a section".format(section_name)) return section def _get_legacy_metadata(self): """ Return the pre-PEP 621 metadata. """ line_nr = self._get_line_nr('[tool.sip.metadata]') if line_nr is None: # There is no metadata specified. raise PyProjectException("the '[project]' section is missing") deprecated( "using '[tool.sip.metadata]' to specify the project metadata", instead="'[project]'", filename='pyproject.toml', line_nr=line_nr) core_metadata = dict() name = None version = None core_metadata_version = None for md_name, md_value in self.get_section('tool.sip.metadata', required=True).items(): md_name = md_name.lower() # Extract specific string values. if md_name in ('name', 'metadata-version'): if not isinstance(md_value, str): raise PyProjectTypeOptionException(md_name, "a string", section_name='tool.sip') if md_name == 'name': if not md_value.replace('-', '_').isidentifier(): raise PyProjectOptionException('name', "'{0}' is an invalid project name".format( md_value), section_name='tool.sip') name = md_value elif md_name == 'metadata-version': core_metadata_version = md_value else: # Any other value may be a string or a list of strings. value_list = md_value if isinstance(md_value, list) else [md_value] for value in value_list: if not isinstance(value, str): raise PyProjectTypeOptionException(md_name, "a string or a list of strings", section_name='tool.sip') core_metadata[md_name] = md_value if name is None: raise PyProjectUndefinedOptionException('name', section_name='tool.sip') if core_metadata_version is None: # Default to PEP 566. core_metadata['metadata-version'] = '2.1' return core_metadata def _get_line_nr(self, target): """ Return the number of the line in pyproject.toml containing a target string. """ for line_nr, line in enumerate(self._raw_toml.split('\n')): line = line.strip() # Ignore comments. if line.startswith('#'): continue if target in line: return line_nr + 1 # The target wasn't present. return None @classmethod def _handle_dependencies(cls, value, core_metadata): """ Handle the 'dependencies' key. """ if not isinstance(value, list): raise PyProjectTypeOptionException('dependencies', "an array of strings", section_name='project') cls._update_requires_dist(value, core_metadata) @staticmethod def _handle_keywords(value, core_metadata): """ Handle the 'keywords' key. """ if not isinstance(value, list): raise PyProjectTypeOptionException('keywords', "an array of strings", section_name='project') core_metadata['keywords'] = ', '.join(value) @staticmethod def _handle_license(value, core_metadata): """ Handle the 'license' key. """ if not isinstance(value, dict): raise PyProjectTypeOptionException('license', "a table", section_name='project') text = value.get('text') file = value.get('file') if text is not None: if file is not None: raise PyProjectOptionException('license', "'file' and 'text' cannot both be specified", section_name='project') elif file is not None: try: with open(file, encoding='UTF-8') as f: text = f.read() except FileNotFoundError: raise PyProjectOptionException('file', f"unable to read '{file}'", section_name='project.license') else: raise PyProjectOptionException('readme', "either 'file' or 'text' must be specified", section_name='project') core_metadata['license'] = text @classmethod def _handle_optional_dependencies(cls, value, core_metadata): """ Handle the 'optional-dependencies' key. """ if not isinstance(value, dict): raise PyProjectTypeOptionException('optional-dependencies', "a table", section_name='project') provides_extra = [] requires_dist = [] for extra, extra_deps in value.items(): if not isinstance(extra_deps, list): raise PyProjectOptionException('optional-dependencies', "each table value must be a list of strings", section_name='project') provides_extra.append(extra) for dep in extra_deps: # Note that this is broken if the dependency is not a simple # name. requires_dist.append(f'{dep}[{extra}]') core_metadata['provides-extra'] = provides_extra cls._update_requires_dist(value, core_metadata) @staticmethod def _handle_people(value, role, core_metadata): """ Handle the people-related keys. """ key = role + 's' if not isinstance(value, list): raise PyProjectTypeOptionException(key, "an array of tables", section_name='project') all_names = [] all_emails = [] for table in value: if not isinstance(table, dict): raise PyProjectTypeOptionException(key, "an array of tables", section_name='project') name = table.get('name') email = table.get('email') if name is None and email is None: raise PyProjectOptionException(key, "each table must contain 'name' and/or 'email'", section_name='project') if name is not None and not isinstance(name, str): raise PyProjectOptionException(key, "'name' must be a string", section_name='project') if email is not None and not isinstance(email, str): raise PyProjectOptionException(key, "'email' must be a string", section_name='project') if email is not None: if name is not None: all_emails.append(f'{name} <{email}>') else: all_emails.append(email) else: all_names.append(name) if all_names: core_metadata[role] = ', '.join(all_names) if all_emails: core_metadata[role + '-email'] = ', '.join(all_emails) @staticmethod def _handle_readme(value, core_metadata): """ Handle the 'readme' key. """ if isinstance(value, str): readme = value.lower() if readme.endswith('.md'): content_type = 'text/markdown' elif readme.endswith('.rst'): content_type = 'text/x-rst' else: raise PyProjectOptionException('readme', f"'{value}' has an unsupported file type", section_name='project') description_file = value elif isinstance(value, dict): content_type = value.get('content-type') if content_type is None: raise PyProjectUndefinedOptionException('content-type', section_name='project.readme') description_text = value.get('text') description_file = value.get('file') if description_text is not None: if description_file is not None: raise PyProjectOptionException('readme', "'file' and 'text' cannot both be specified", section_name='project') core_metadata['description'] = text elif description_file is None: raise PyProjectOptionException('readme', "either 'file' or 'text' must be specified", section_name='project') else: raise PyProjectTypeOptionException('readme', "a string or a table", section_name='project') core_metadata['description-content-type'] = content_type if description_file is not None: # This is a local, internal extension. core_metadata['description-file'] = description_file @staticmethod def _handle_urls(value, core_metadata): """ Handle the 'urls' key. """ if not isinstance(value, dict): raise PyProjectTypeOptionException(key, "a table", section_name='project') core_metadata['project-url'] = [f'{label}, {url}' for label, url in value.items()] @staticmethod def _is_section(value): """ Returns True if a section value is itself a section. """ return isinstance(value, (dict, list)) @staticmethod def _update_requires_dist(value, core_metadata): """ Update 'requires-dist' in the core metadata. """ try: requires_dist = core_metadata['requires-dist'] except KeyError: requires_dist = [] requires_dist.extend(value) core_metadata['requires-dist'] = requires_dist sip-6.8.6/sipbuild/setuptools_builder.py000066400000000000000000000125211464421045000204430ustar00rootroot00000000000000# SPDX-License-Identifier: BSD-2-Clause # Copyright (c) 2024 Phil Thompson import os from setuptools import Extension, setup from .buildable import BuildableModule from .builder import Builder from .exceptions import UserException from .installable import Installable class SetuptoolsBuilder(Builder): """ The implementation of a setuptools-based project builder. """ def build_executable(self, buildable, *, fatal=True): """ Build an executable from a BuildableExecutable object and return the relative pathname of the executable. """ raise UserException("SetuptoolsBuilder cannot build executables") def build_project(self, target_dir, *, wheel_tag=None): """ Build the project. """ project = self.project # On macOS respect the minimum macOS version. remove_macos_target = False if project.py_platform == 'darwin': # If the target version has already been set then assume the user # knows what they are doing and leave it as it is. if 'MACOSX_DEPLOYMENT_TARGET' not in os.environ: if project.minimum_macos_version: macos_target = '.'.join( [str(v) for v in project.minimum_macos_version]) os.environ['MACOSX_DEPLOYMENT_TARGET'] = macos_target remove_macos_target = True # Build the buildables. for buildable in project.buildables: if isinstance(buildable, BuildableModule): if buildable.static: raise UserException( "SetuptoolsBuilder cannot build static modules") self._build_extension_module(buildable) else: raise UserException( "SetuptoolsBuilder cannot build '{0}' buildables".format( type(buildable).__name__)) # Tidy up. if remove_macos_target: del os.environ['MACOSX_DEPLOYMENT_TARGET'] def install_project(self, target_dir, *, wheel_tag=None): """ Install the project into a target directory. """ project = self.project installed = [] # Install any project-level installables. for installable in project.installables: installable.install(target_dir, installed) # Install any installables from built buildables. for buildable in project.buildables: for installable in buildable.installables: installable.install(target_dir, installed) if project.distinfo: from .distinfo import create_distinfo create_distinfo(project.get_distinfo_dir(target_dir), wheel_tag, installed, project.metadata, project.get_requires_dists(), project.root_dir, project.console_scripts, project.gui_scripts) def _build_extension_module(self, buildable): """ Build an extension module from the sources. """ project = self.project # The arguments to setup(). setup_args = {} # Build the script arguments. script_args = [] if project.verbose: script_args.append('--verbose') else: script_args.append('--quiet') script_args.append('--no-user-cfg') script_args.append('build_ext') if buildable.debug: script_args.append('--debug') setup_args['script_args'] = script_args # Handle preprocessor macros. define_macros = [] for macro in buildable.define_macros: parts = macro.split('=', maxsplit=1) name = parts[0] try: value = parts[1] except IndexError: value = None define_macros.append((name, value)) buildable.make_names_relative() setup_args['ext_modules'] = [ Extension(buildable.fq_name, buildable.sources, define_macros=define_macros, extra_compile_args=buildable.extra_compile_args, extra_link_args=buildable.extra_link_args, extra_objects=buildable.extra_objects, include_dirs=buildable.include_dirs, libraries=buildable.libraries, library_dirs=buildable.library_dirs, py_limited_api=buildable.uses_limited_api)] project.progress( "Compiling the '{0}' module".format(buildable.fq_name)) saved_cwd = os.getcwd() os.chdir(buildable.build_dir) try: distribution = setup(**setup_args) except Exception as e: raise UserException( "Unable to compile the '{0}' module".format( buildable.fq_name), detail=str(e)) # Add the extension module to the buildable's list of installables. build_command = distribution.get_command_obj('build_ext') installable = Installable('module', target_subdir=buildable.get_install_subdir()) installable.files.append( os.path.abspath( build_command.get_ext_fullpath(buildable.fq_name))) buildable.installables.append(installable) os.chdir(saved_cwd) sip-6.8.6/sipbuild/toml.py000066400000000000000000000007531464421045000154730ustar00rootroot00000000000000# SPDX-License-Identifier: BSD-2-Clause # Copyright (c) 2024 Phil Thompson try: import tomllib except ImportError: import tomli as tomllib def toml_load(toml_file): """ Return a dict containing the decoded contents of a TOML file. """ with open(toml_file, 'rb') as f: return tomllib.load(f) def toml_loads(toml_str): """ Return a dict containing the decoded contents of a TOML string. """ return tomllib.loads(toml_str) sip-6.8.6/sipbuild/tools/000077500000000000000000000000001464421045000153015ustar00rootroot00000000000000sip-6.8.6/sipbuild/tools/__init__.py000066400000000000000000000001521464421045000174100ustar00rootroot00000000000000# SPDX-License-Identifier: BSD-2-Clause # Copyright (c) 2024 Phil Thompson sip-6.8.6/sipbuild/tools/build.py000066400000000000000000000011241464421045000167500ustar00rootroot00000000000000# SPDX-License-Identifier: BSD-2-Clause # Copyright (c) 2024 Phil Thompson from ..abstract_project import AbstractProject from ..exceptions import handle_exception def main(): """ Build the project in-situ from the command line. """ try: project = AbstractProject.bootstrap('build', "Build the project in-situ.") project.build() project.progress("The project has been built.") except Exception as e: handle_exception(e) return 0 if __name__ == '__main__': import sys sys.exit(main()) sip-6.8.6/sipbuild/tools/distinfo.py000066400000000000000000000051151464421045000174740ustar00rootroot00000000000000# SPDX-License-Identifier: BSD-2-Clause # Copyright (c) 2024 Phil Thompson from ..argument_parser import ArgumentParser from ..distinfo import distinfo from ..exceptions import handle_exception def main(): """ Create and populate a .dist-info directory. """ # Parse the command line. parser = ArgumentParser("Create and populate a .dist-info directory.") parser.add_argument('--console-script', dest='console_scripts', action='append', help="the entry point of a console script", metavar='ENTRY-POINT') parser.add_argument('--generator', help="the name of the program generating the directory", metavar="NAME") parser.add_argument('--generator-version', help="the version of the program generating the directory", metavar="VERSION") parser.add_argument('--gui-script', dest='gui_scripts', action='append', help="the entry point of a GUI script", metavar='ENTRY-POINT') parser.add_argument('--inventory', required=True, help="the file containing the names of the files in the project", metavar="FILE") parser.add_argument('--metadata', dest='metadata_overrides', action='append', help="a name/value to override any pyproject.toml metadata", metavar='NAME[=VALUE]') parser.add_argument('--prefix', help="the installation prefix directory", metavar="DIR") parser.add_argument('--project-root', required=True, help="the directory containing pyproject.toml", metavar="DIR") parser.add_argument('--requires-dist', dest='requires_dists', action='append', help="additional Requires-Dist", metavar="EXPR") parser.add_argument('--wheel-tag', help="the tag if a wheel is being created", metavar="TAG") parser.add_argument(dest='names', nargs=1, help="the name of the .dist-info directory", metavar='directory') args = parser.parse_args() try: distinfo(name=args.names[0], console_scripts=args.console_scripts, gui_scripts=args.gui_scripts, generator=args.generator, generator_version=args.generator_version, inventory=args.inventory, metadata_overrides=args.metadata_overrides, prefix=args.prefix, project_root=args.project_root, requires_dists=args.requires_dists, wheel_tag=args.wheel_tag) except Exception as e: handle_exception(e) return 0 if __name__ == '__main__': import sys sys.exit(main()) sip-6.8.6/sipbuild/tools/install.py000066400000000000000000000011321464421045000173160ustar00rootroot00000000000000# SPDX-License-Identifier: BSD-2-Clause # Copyright (c) 2024 Phil Thompson from ..abstract_project import AbstractProject from ..exceptions import handle_exception def main(): """ Install the project from the command line. """ try: project = AbstractProject.bootstrap('install', "Build and install the project.") project.install() project.progress("The project has been installed.") except Exception as e: handle_exception(e) return 0 if __name__ == '__main__': import sys sys.exit(main()) sip-6.8.6/sipbuild/tools/module.py000066400000000000000000000033251464421045000171430ustar00rootroot00000000000000# SPDX-License-Identifier: BSD-2-Clause # Copyright (c) 2024 Phil Thompson from ..argument_parser import ArgumentParser from ..exceptions import handle_exception from ..module import module def main(): """ Create the source, interface and documentation of a sip module. """ # Parse the command line. parser = ArgumentParser("Generate a sip extension module.") parser.add_argument('--abi-version', help="the ABI version", metavar="MAJOR[.MINOR]") parser.add_argument('--project', help="the PyPI project name", metavar="NAME") parser.add_argument('--sdist', action='store_true', default=False, help="generate an sdist file") parser.add_argument('--setup-cfg', help="the name of the setup.cfg file to use", metavar="FILE") parser.add_argument('--sip-h', action='store_true', default=False, help="generate a sip.h file") parser.add_argument('--sip-rst', action='store_true', default=False, help="generate a sip.rst file") parser.add_argument('--target-dir', help="generate files in DIR", metavar="DIR") parser.add_argument(dest='sip_modules', nargs=1, help="the fully qualified name of the sip module", metavar="module") args = parser.parse_args() try: module(sip_module=args.sip_modules[0], abi_version=args.abi_version, project=args.project, sdist=args.sdist, setup_cfg=args.setup_cfg, sip_h=args.sip_h, sip_rst=args.sip_rst, target_dir=args.target_dir) except Exception as e: handle_exception(e) return 0 if __name__ == '__main__': import sys sys.exit(main()) sip-6.8.6/sipbuild/tools/sdist.py000066400000000000000000000011451464421045000170020ustar00rootroot00000000000000# SPDX-License-Identifier: BSD-2-Clause # Copyright (c) 2024 Phil Thompson from ..abstract_project import AbstractProject from ..exceptions import handle_exception def main(): """ Build an sdist for the project from the command line. """ try: project = AbstractProject.bootstrap('sdist', "Build an sdist for the project.") project.build_sdist('.') project.progress("The sdist has been built.") except Exception as e: handle_exception(e) return 0 if __name__ == '__main__': import sys sys.exit(main()) sip-6.8.6/sipbuild/tools/wheel.py000066400000000000000000000011431464421045000167560ustar00rootroot00000000000000# SPDX-License-Identifier: BSD-2-Clause # Copyright (c) 2024 Phil Thompson from ..abstract_project import AbstractProject from ..exceptions import handle_exception def main(): """ Build a wheel for the project from the command line. """ try: project = AbstractProject.bootstrap('wheel', "Build a wheel for the project.") project.build_wheel('.') project.progress("The wheel has been built.") except Exception as e: handle_exception(e) return 0 if __name__ == '__main__': import sys sys.exit(main()) sip-6.8.6/sipbuild/version.py000066400000000000000000000005451464421045000162040ustar00rootroot00000000000000# SPDX-License-Identifier: BSD-2-Clause # Copyright (c) 2024 Phil Thompson from ._version import version, version_tuple # Convert the setuptools_scm generated version number to our historic names and # formats. SIP_VERSION_STR = version SIP_VERSION = (version_tuple[0] << 16) + (version_tuple[1] << 8) + version_tuple[2] sip-6.8.6/test/000077500000000000000000000000001464421045000133055ustar00rootroot00000000000000sip-6.8.6/test/enums/000077500000000000000000000000001464421045000144345ustar00rootroot00000000000000sip-6.8.6/test/enums/__init__.py000066400000000000000000000001521464421045000165430ustar00rootroot00000000000000# SPDX-License-Identifier: BSD-2-Clause # Copyright (c) 2024 Phil Thompson sip-6.8.6/test/enums/enums.sip000066400000000000000000000046541464421045000163110ustar00rootroot00000000000000// The bindings for testing support for enums. %Module(name=enums) %ModuleHeaderCode enum { AnonMember = 10 }; enum NamedEnum { NamedMember = 20 }; enum class ScopedEnum { ScopedMember = 30 }; enum EnumBase { EnumBaseMember = 100 }; enum FlagBase { FlagBaseMember = 110 }; enum IntEnumBase { IntEnumBaseMember = 120 }; enum IntFlagBase { IntFlagBaseMember = 130 }; class EnumClass { public: EnumClass() : named_overload(false) {} virtual ~EnumClass() {} enum { ClassAnonMember = 40 }; enum ClassNamedEnum { ClassNamedMember = 50 }; ClassNamedEnum named_get() { return named_virt(); } virtual ClassNamedEnum named_virt() { return ClassNamedMember; } static void named_set(ClassNamedEnum) {} ClassNamedEnum named_var; enum ClassNamedEnum2 { ClassNamedMember2 = 60 }; void named_overload_set(ClassNamedEnum2) {named_overload = false;} void named_overload_set(ClassNamedEnum) {named_overload = true;} bool named_overload; enum class ClassScopedEnum { ClassScopedMember = 70 }; ClassScopedEnum scoped_get() { return scoped_virt(); } virtual ClassScopedEnum scoped_virt() { return ClassScopedEnum::ClassScopedMember; } static void scoped_set(ClassScopedEnum) {} ClassScopedEnum scoped_var; }; %End enum { AnonMember }; enum NamedEnum { NamedMember }; enum class ScopedEnum { ScopedMember }; enum EnumBase /BaseType=Enum/ { EnumBaseMember }; enum FlagBase /BaseType=Flag/ { FlagBaseMember }; enum IntEnumBase /BaseType=IntEnum/ { IntEnumBaseMember }; enum IntFlagBase /BaseType=IntFlag/ { IntFlagBaseMember }; class EnumClass { public: EnumClass(); virtual ~EnumClass(); enum { ClassAnonMember }; enum ClassNamedEnum { ClassNamedMember }; ClassNamedEnum named_get(); virtual ClassNamedEnum named_virt(); static void named_set(ClassNamedEnum); ClassNamedEnum named_var; enum ClassNamedEnum2 { ClassNamedMember2 }; void named_overload_set(ClassNamedEnum2); void named_overload_set(ClassNamedEnum); bool named_overload; enum class ClassScopedEnum { ClassScopedMember }; ClassScopedEnum scoped_get(); virtual ClassScopedEnum scoped_virt(); static void scoped_set(ClassScopedEnum); ClassScopedEnum scoped_var; }; sip-6.8.6/test/enums/test_enums.py000066400000000000000000000164671464421045000172120ustar00rootroot00000000000000# SPDX-License-Identifier: BSD-2-Clause # Copyright (c) 2024 Phil Thompson from enum import Enum, Flag, IntEnum, IntFlag from utils import SIPTestCase class EnumsTestCase(SIPTestCase): """ Test the support for enums in ABI v13 and later. """ @classmethod def setUpClass(cls): """ Set up the test case. """ super().setUpClass() from .enums import EnumClass class NamedEnumFixture(EnumClass): """ A fixture for testing named enum values. """ def __init__(self, value): """ Initialise the object. """ super().__init__() self._value = value def named_virt(self): return self._value cls.member_fixture = NamedEnumFixture( EnumClass.ClassNamedEnum.ClassNamedMember) cls.int_fixture = NamedEnumFixture(0) class ScopedEnumFixture(EnumClass): """ A fixture for testing scoped enum values. """ def scoped_virt(self): return EnumClass.ClassScopedEnum.ClassScopedMember cls.scoped_enum_fixture = ScopedEnumFixture() @classmethod def tearDownClass(cls): """ Tear down the test case. """ # Remove all references to the extension module so that the superclass # can unload it. del cls.member_fixture del cls.int_fixture del cls.scoped_enum_fixture super().tearDownClass() ########################################################################### # The following test anonymous enums. ########################################################################### def test_ModuleAnon(self): """ Test a module level anonymous enum. """ from .enums import AnonMember self.assertIsInstance(AnonMember, int) self.assertEqual(AnonMember, 10) def test_ClassAnon(self): """ Test a class level anonymous enum. """ from .enums import EnumClass self.assertIsInstance(EnumClass.ClassAnonMember, int) self.assertEqual(EnumClass.ClassAnonMember, 40) ########################################################################### # The following test the /BaseType/ annotation. ########################################################################### def test_Enum_BaseType(self): """ Test /BaseType=Enum/. """ from .enums import EnumBase self.assertTrue(issubclass(EnumBase, Enum)) self.assertFalse(issubclass(EnumBase, Flag)) self.assertFalse(issubclass(EnumBase, IntEnum)) self.assertFalse(issubclass(EnumBase, IntFlag)) def test_Flag_BaseType(self): """ Test /BaseType=Flag/. """ from .enums import FlagBase self.assertTrue(issubclass(FlagBase, Flag)) self.assertFalse(issubclass(FlagBase, IntEnum)) self.assertFalse(issubclass(FlagBase, IntFlag)) def test_IntEnum_BaseType(self): """ Test /BaseType=IntEnum/. """ from .enums import IntEnumBase self.assertFalse(issubclass(IntEnumBase, Flag)) self.assertTrue(issubclass(IntEnumBase, IntEnum)) self.assertFalse(issubclass(IntEnumBase, IntFlag)) def test_IntFlag_BaseType(self): """ Test /BaseType=IntFlag/. """ from .enums import IntFlagBase self.assertFalse(issubclass(IntFlagBase, IntEnum)) self.assertTrue(issubclass(IntFlagBase, IntFlag)) ########################################################################### # The following test named enums. ########################################################################### def test_ModuleNamed(self): """ Test a module level named enum. """ from .enums import NamedEnum self.assertTrue(issubclass(NamedEnum, Enum)) self.assertEqual(NamedEnum.NamedMember.value, 20) def test_ClassNamed(self): """ Test a class level named enum. """ from .enums import EnumClass self.assertTrue(issubclass(EnumClass.ClassNamedEnum, Enum)) self.assertEqual(EnumClass.ClassNamedEnum.ClassNamedMember.value, 50) def test_named_get_member(self): """ named enum virtual result with a member value. """ from .enums import EnumClass self.install_hook() self.assertEqual(self.member_fixture.named_get(), EnumClass.ClassNamedEnum.ClassNamedMember) self.uninstall_hook() def test_named_set_member(self): """ named enum function argument with a member value. """ from .enums import EnumClass self.member_fixture.named_set( EnumClass.ClassNamedEnum.ClassNamedMember) def test_named_var_member(self): """ named enum instance variable with a member value. """ from .enums import EnumClass self.member_fixture.named_var = EnumClass.ClassNamedEnum.ClassNamedMember def test_named_overload_set(self): """ overloaded named enum function argument. """ from .enums import EnumClass self.member_fixture.named_overload_set( EnumClass.ClassNamedEnum.ClassNamedMember) self.assertTrue(self.member_fixture.named_overload) def test_named_get_int(self): """ named enum virtual result with an integer value. """ from .enums import EnumClass with self.assertRaises(TypeError): self.install_hook() self.int_fixture.named_get() self.uninstall_hook() def test_named_set_int(self): """ named enum function argument with an integer value. """ with self.assertRaises(TypeError): self.int_fixture.named_set(50) def test_named_var_int(self): """ named enum instance variable with an integer value. """ with self.assertRaises(TypeError): self.int_fixture.named_var = 50 ########################################################################### # The following test scoped enums. ########################################################################### def test_ModuleScoped(self): """ Test a module level C++11 scoped enum. """ from .enums import ScopedEnum self.assertTrue(issubclass(ScopedEnum, Enum)) self.assertEqual(ScopedEnum.ScopedMember.value, 30) def test_ClassScoped(self): """ Test a class level C++11 scoped enum. """ from .enums import EnumClass self.assertTrue(issubclass(EnumClass.ClassScopedEnum, Enum)) self.assertEqual(EnumClass.ClassScopedEnum.ClassScopedMember.value, 70) def test_scoped_get_member(self): """ scoped enum virtual result with a member value. """ from .enums import EnumClass self.install_hook() self.assertIs(self.scoped_enum_fixture.scoped_get(), EnumClass.ClassScopedEnum.ClassScopedMember) self.uninstall_hook() def test_scoped_set_member(self): """ scoped enum function argument with a member value. """ from .enums import EnumClass self.scoped_enum_fixture.scoped_set( EnumClass.ClassScopedEnum.ClassScopedMember) def test_scoped_var_member(self): """ scoped enum instance variable with a member value. """ from .enums import EnumClass self.scoped_enum_fixture.scoped_var = EnumClass.ClassScopedEnum.ClassScopedMember sip-6.8.6/test/int_convertors/000077500000000000000000000000001464421045000163635ustar00rootroot00000000000000sip-6.8.6/test/int_convertors/__init__.py000066400000000000000000000001521464421045000204720ustar00rootroot00000000000000# SPDX-License-Identifier: BSD-2-Clause # Copyright (c) 2024 Phil Thompson sip-6.8.6/test/int_convertors/int_convertors.sip000066400000000000000000000130011464421045000221510ustar00rootroot00000000000000%Module(name=int_convertors) %PostInitialisationCode #if SIP_API_MAJOR_NR == 12 sipEnableOverflowChecking(1); #endif %End %ModuleHeaderCode #include class IntConvertors { public: virtual ~IntConvertors() {} bool bool_get() {return bool_virt();} virtual bool bool_virt() {return false;} static void bool_set(bool) {} bool bool_var; static int char_lower() {return CHAR_MIN;} static int char_upper() {return CHAR_MAX;} char char_get() {return char_virt();} virtual char char_virt() {return 0;} static void char_set(char) {} char char_var; static unsigned signed_char_sizeof() {return sizeof (signed char);} signed char signed_char_get() {return signed_char_virt();} virtual signed char signed_char_virt() {return 0;} static void signed_char_set(signed char) {} signed char signed_char_var; static unsigned short_sizeof() {return sizeof (short);} short short_get() {return short_virt();} virtual short short_virt() {return 0;} static void short_set(short) {} short short_var; static unsigned int_sizeof() {return sizeof (int);} int int_get() {return int_virt();} virtual int int_virt() {return 0;} static void int_set(int) {} int int_var; static unsigned long_sizeof() {return sizeof (long);} long long_get() {return long_virt();} virtual long long_virt() {return 0;} static void long_set(long) {} long long_var; static unsigned long_long_sizeof() {return sizeof (long long);} long long long_long_get() {return long_long_virt();} virtual long long long_long_virt() {return 0;} static void long_long_set(long long) {} long long long_long_var; static unsigned unsigned_char_sizeof() {return sizeof (unsigned char);} unsigned char unsigned_char_get() {return unsigned_char_virt();} virtual unsigned char unsigned_char_virt() {return 0;} static void unsigned_char_set(unsigned char) {} unsigned char unsigned_char_var; static unsigned unsigned_short_sizeof() {return sizeof (unsigned short);} unsigned short unsigned_short_get() {return unsigned_short_virt();} virtual unsigned short unsigned_short_virt() {return 0;} static void unsigned_short_set(unsigned short) {} unsigned short unsigned_short_var; static unsigned unsigned_int_sizeof() {return sizeof (unsigned int);} unsigned int unsigned_int_get() {return unsigned_int_virt();} virtual unsigned int unsigned_int_virt() {return 0;} static void unsigned_int_set(unsigned int) {} unsigned int unsigned_int_var; static unsigned unsigned_long_sizeof() {return sizeof (unsigned long);} unsigned long unsigned_long_get() {return unsigned_long_virt();} virtual unsigned long unsigned_long_virt() {return 0;} static void unsigned_long_set(unsigned long) {} unsigned long unsigned_long_var; static unsigned unsigned_long_long_sizeof() { return sizeof (unsigned long long); } unsigned long long unsigned_long_long_get() { return unsigned_long_long_virt(); } virtual unsigned long long unsigned_long_long_virt() {return 0;} static void unsigned_long_long_set(unsigned long long) {} unsigned long long unsigned_long_long_var; }; %End class IntConvertors { public: virtual ~IntConvertors(); bool bool_get(); virtual bool bool_virt(); static void bool_set(bool); bool bool_var; static int char_lower(); static int char_upper(); char char_get() /PyInt/; virtual char char_virt() /PyInt/; static void char_set(char /PyInt/); char char_var /PyInt/; static unsigned signed_char_sizeof(); signed char signed_char_get() /PyInt/; virtual signed char signed_char_virt() /PyInt/; static void signed_char_set(signed char /PyInt/); signed char signed_char_var /PyInt/; static unsigned short_sizeof(); short short_get(); virtual short short_virt(); static void short_set(short); short short_var; static unsigned int_sizeof(); int int_get(); virtual int int_virt(); static void int_set(int); int int_var; static unsigned long_sizeof(); long long_get(); virtual long long_virt(); static void long_set(long); long long_var; static unsigned long_long_sizeof(); long long long_long_get(); virtual long long long_long_virt(); static void long_long_set(long long); long long long_long_var; static unsigned unsigned_char_sizeof(); unsigned char unsigned_char_get() /PyInt/; virtual unsigned char unsigned_char_virt() /PyInt/; static void unsigned_char_set(unsigned char /PyInt/); unsigned char unsigned_char_var /PyInt/; static unsigned unsigned_short_sizeof(); unsigned short unsigned_short_get(); virtual unsigned short unsigned_short_virt(); static void unsigned_short_set(unsigned short); unsigned short unsigned_short_var; static unsigned unsigned_int_sizeof(); unsigned int unsigned_int_get(); virtual unsigned int unsigned_int_virt(); static void unsigned_int_set(unsigned int); unsigned int unsigned_int_var; static unsigned unsigned_long_sizeof(); unsigned long unsigned_long_get(); virtual unsigned long unsigned_long_virt(); static void unsigned_long_set(unsigned long); unsigned long unsigned_long_var; static unsigned unsigned_long_long_sizeof(); unsigned long long unsigned_long_long_get(); virtual unsigned long long unsigned_long_long_virt(); static void unsigned_long_long_set(unsigned long long); unsigned long long unsigned_long_long_var; }; sip-6.8.6/test/int_convertors/test_int_convertors.py000066400000000000000000001164741464421045000230670ustar00rootroot00000000000000# SPDX-License-Identifier: BSD-2-Clause # Copyright (c) 2024 Phil Thompson from utils import SIPTestCase class IntConvertorsTestCase(SIPTestCase): """ Test the integer convertors. """ @classmethod def setUpClass(cls): """ Set up the test case. """ super().setUpClass() from .int_convertors import IntConvertors # Compute the various test values based on the native sizes. cls.CHAR_LOWER = IntConvertors.char_lower() cls.CHAR_UPPER = IntConvertors.char_upper() cls.SIGNED_CHAR_LOWER, cls.SIGNED_CHAR_UPPER = cls._signed_bounds( IntConvertors.signed_char_sizeof()) cls.SHORT_LOWER, cls.SHORT_UPPER = cls._signed_bounds( IntConvertors.short_sizeof()) cls.INT_LOWER, cls.INT_UPPER = cls._signed_bounds( IntConvertors.int_sizeof()) cls.LONG_LOWER, cls.LONG_UPPER = cls._signed_bounds( IntConvertors.long_sizeof()) cls.LONG_LONG_LOWER, cls.LONG_LONG_UPPER = cls._signed_bounds( IntConvertors.long_long_sizeof()) cls.UNSIGNED_CHAR_UPPER = cls._unsigned_upper_bound( IntConvertors.unsigned_char_sizeof()) cls.UNSIGNED_SHORT_UPPER = cls._unsigned_upper_bound( IntConvertors.unsigned_short_sizeof()) cls.UNSIGNED_INT_UPPER = cls._unsigned_upper_bound( IntConvertors.unsigned_int_sizeof()) cls.UNSIGNED_LONG_UPPER = cls._unsigned_upper_bound( IntConvertors.unsigned_long_sizeof()) cls.UNSIGNED_LONG_LONG_UPPER = cls._unsigned_upper_bound( IntConvertors.unsigned_long_long_sizeof()) class LimitsFixture(IntConvertors): """ The base test fixture for those implementing a range of values. """ def __init__(self, limits): """ Initialise the object. """ super().__init__() self.limits = limits class ValidLowerFixture(LimitsFixture): """ A fixture for testing the lower bound of non-overflowing signed values. """ def char_virt(self): return self.limits.CHAR_LOWER def signed_char_virt(self): return self.limits.SIGNED_CHAR_LOWER def short_virt(self): return self.limits.SHORT_LOWER def int_virt(self): return self.limits.INT_LOWER def long_virt(self): return self.limits.LONG_LOWER def long_long_virt(self): return self.limits.LONG_LONG_LOWER cls.valid_lower_fixture = ValidLowerFixture(cls) class ValidUpperFixture(LimitsFixture): """ A fixture for testing the upper bound of non-overflowing values. """ def char_virt(self): return self.limits.CHAR_UPPER def signed_char_virt(self): return self.limits.SIGNED_CHAR_UPPER def short_virt(self): return self.limits.SHORT_UPPER def int_virt(self): return self.limits.INT_UPPER def long_virt(self): return self.limits.LONG_UPPER def long_long_virt(self): return self.limits.LONG_LONG_UPPER def unsigned_char_virt(self): return self.limits.UNSIGNED_CHAR_UPPER def unsigned_short_virt(self): return self.limits.UNSIGNED_SHORT_UPPER def unsigned_int_virt(self): return self.limits.UNSIGNED_INT_UPPER def unsigned_long_virt(self): return self.limits.UNSIGNED_LONG_UPPER def unsigned_long_long_virt(self): return self.limits.UNSIGNED_LONG_LONG_UPPER cls.valid_upper_fixture = ValidUpperFixture(cls) class InvalidFixture(IntConvertors): """ A fixture for testing invalid values. """ def bool_virt(self): return '0' def char_virt(self): return '0' def signed_char_virt(self): return '0' def short_virt(self): return '0' def int_virt(self): return '0' def long_virt(self): return '0' def long_long_virt(self): return '0' def unsigned_char_virt(self): return '0' def unsigned_short_virt(self): return '0' def unsigned_int_virt(self): return '0' def unsigned_long_virt(self): return '0' def unsigned_long_long_virt(self): return '0' cls.invalid_fixture = InvalidFixture() class OverflowLowerFixture(LimitsFixture): """ A fixture for testing the lower bound of overflowing signed values. """ def char_virt(self): return self.limits.CHAR_LOWER - 1 def signed_char_virt(self): return self.limits.SIGNED_CHAR_LOWER - 1 def short_virt(self): return self.limits.SHORT_LOWER - 1 def int_virt(self): return self.limits.INT_LOWER - 1 def long_virt(self): return self.limits.LONG_LOWER - 1 def long_long_virt(self): return self.limits.LONG_LONG_LOWER - 1 cls.overflow_lower_fixture = OverflowLowerFixture(cls) class OverflowUpperFixture(LimitsFixture): """ A fixture for testing the upper bound of overflowing values. """ def char_virt(self): return self.limits.CHAR_UPPER + 1 def signed_char_virt(self): return self.limits.SIGNED_CHAR_UPPER + 1 def short_virt(self): return self.limits.SHORT_UPPER + 1 def int_virt(self): return self.limits.INT_UPPER + 1 def long_virt(self): return self.limits.LONG_UPPER + 1 def long_long_virt(self): return self.limits.LONG_LONG_UPPER + 1 def unsigned_char_virt(self): return self.limits.UNSIGNED_CHAR_UPPER + 1 def unsigned_short_virt(self): return self.limits.UNSIGNED_SHORT_UPPER + 1 def unsigned_int_virt(self): return self.limits.UNSIGNED_INT_UPPER + 1 def unsigned_long_virt(self): return self.limits.UNSIGNED_LONG_UPPER + 1 def unsigned_long_long_virt(self): return self.limits.UNSIGNED_LONG_LONG_UPPER + 1 cls.overflow_upper_fixture = OverflowUpperFixture(cls) class BoolFixture(IntConvertors): """ A fixture for testing valid boolean values. """ def __init__(self, value): """ Initialise the object. """ super().__init__() self._value = value def bool_virt(self): return self._value cls.true_fixture = BoolFixture(True) cls.false_fixture = BoolFixture(False) cls.nonzero_fixture = BoolFixture(-1) cls.zero_fixture = BoolFixture(0) @staticmethod def _signed_bounds(nrbytes): """ Return the range of values for a number of bytes representing a signed value. """ v = 1 << ((nrbytes * 8) - 1) return -v, v - 1 @staticmethod def _unsigned_upper_bound(nrbytes): """ Return the upper bound for a number of bytes representing an unsigned value. """ return (1 << (nrbytes * 8)) - 1 @classmethod def tearDownClass(cls): """ Tear down the test case. """ # Remove all references to the extension module so that the superclass # can unload it. del cls.valid_lower_fixture del cls.valid_upper_fixture del cls.invalid_fixture del cls.overflow_lower_fixture del cls.overflow_upper_fixture del cls.true_fixture del cls.false_fixture del cls.nonzero_fixture del cls.zero_fixture super().tearDownClass() ########################################################################### # The following test for valid values. ########################################################################### def test_char_get_lower_valid(self): """ char virtual result lower bound. """ self.assertEqual(self.valid_lower_fixture.char_get(), self.CHAR_LOWER) def test_char_get_upper_valid(self): """ char virtual result upper bound. """ self.assertEqual(self.valid_upper_fixture.char_get(), self.CHAR_UPPER) def test_char_set_lower_valid(self): """ char function argument lower bound. """ self.valid_lower_fixture.char_set(self.CHAR_LOWER) def test_char_set_upper_valid(self): """ char function argument upper bound. """ self.valid_upper_fixture.char_set(self.CHAR_UPPER) def test_char_var_lower_valid(self): """ char instance variable lower bound. """ self.valid_lower_fixture.char_var = self.CHAR_LOWER def test_char_var_upper_valid(self): """ char instance variable upper bound. """ self.valid_upper_fixture.char_var = self.CHAR_UPPER def test_signed_char_get_lower_valid(self): """ signed char virtual result lower bound. """ self.assertEqual(self.valid_lower_fixture.signed_char_get(), self.SIGNED_CHAR_LOWER) def test_signed_char_get_upper_valid(self): """ signed char virtual result upper bound. """ self.assertEqual(self.valid_upper_fixture.signed_char_get(), self.SIGNED_CHAR_UPPER) def test_signed_char_set_lower_valid(self): """ signed char function argument lower bound. """ self.valid_lower_fixture.signed_char_set(self.SIGNED_CHAR_LOWER) def test_signed_char_set_upper_valid(self): """ signed char function argument upper bound. """ self.valid_upper_fixture.signed_char_set(self.SIGNED_CHAR_UPPER) def test_signed_char_var_lower_valid(self): """ signed char instance variable lower bound. """ self.valid_lower_fixture.signed_char_var = self.SIGNED_CHAR_LOWER def test_signed_char_var_upper_valid(self): """ signed char instance variable upper bound. """ self.valid_upper_fixture.signed_char_var = self.SIGNED_CHAR_UPPER def test_short_get_lower_valid(self): """ short virtual result lower bound. """ self.assertEqual(self.valid_lower_fixture.short_get(), self.SHORT_LOWER) def test_short_get_upper_valid(self): """ short virtual result upper bound. """ self.assertEqual(self.valid_upper_fixture.short_get(), self.SHORT_UPPER) def test_short_set_lower_valid(self): """ short function argument lower bound. """ self.valid_lower_fixture.short_set(self.SHORT_LOWER) def test_short_set_upper_valid(self): """ short function argument upper bound. """ self.valid_upper_fixture.short_set(self.SHORT_UPPER) def test_short_var_lower_valid(self): """ short instance variable lower bound. """ self.valid_lower_fixture.short_var = self.SHORT_LOWER def test_short_var_upper_valid(self): """ short instance variable upper bound. """ self.valid_upper_fixture.short_var = self.SHORT_UPPER def test_int_get_lower_valid(self): """ int virtual result lower bound. """ self.assertEqual(self.valid_lower_fixture.int_get(), self.INT_LOWER) def test_int_get_upper_valid(self): """ int virtual result upper bound. """ self.assertEqual(self.valid_upper_fixture.int_get(), self.INT_UPPER) def test_int_set_lower_valid(self): """ int function argument lower bound. """ self.valid_lower_fixture.int_set(self.INT_LOWER) def test_int_set_upper_valid(self): """ int function argument upper bound. """ self.valid_upper_fixture.int_set(self.INT_UPPER) def test_int_var_lower_valid(self): """ int instance variable lower bound. """ self.valid_lower_fixture.int_var = self.INT_LOWER def test_int_var_upper_valid(self): """ int instance variable upper bound. """ self.valid_upper_fixture.int_var = self.INT_UPPER def test_long_get_lower_valid(self): """ long virtual result lower bound. """ self.assertEqual(self.valid_lower_fixture.long_get(), self.LONG_LOWER) def test_long_get_upper_valid(self): """ long virtual result upper bound. """ self.assertEqual(self.valid_upper_fixture.long_get(), self.LONG_UPPER) def test_long_set_lower_valid(self): """ long function argument lower bound. """ self.valid_lower_fixture.long_set(self.LONG_LOWER) def test_long_set_upper_valid(self): """ long function argument upper bound. """ self.valid_upper_fixture.long_set(self.LONG_UPPER) def test_long_var_lower_valid(self): """ long instance variable lower bound. """ self.valid_lower_fixture.long_var = self.LONG_LOWER def test_long_var_upper_valid(self): """ long instance variable upper bound. """ self.valid_upper_fixture.long_var = self.LONG_UPPER def test_long_long_get_lower_valid(self): """ long long virtual result lower bound. """ self.assertEqual(self.valid_lower_fixture.long_long_get(), self.LONG_LONG_LOWER) def test_long_long_get_upper_valid(self): """ long long virtual result upper bound. """ self.assertEqual(self.valid_upper_fixture.long_long_get(), self.LONG_LONG_UPPER) def test_long_long_set_lower_valid(self): """ long long function argument lower bound. """ self.valid_lower_fixture.long_long_set(self.LONG_LONG_LOWER) def test_long_long_set_upper_valid(self): """ long long function argument upper bound. """ self.valid_upper_fixture.long_long_set(self.LONG_LONG_UPPER) def test_long_long_var_lower_valid(self): """ long long instance variable lower bound. """ self.valid_lower_fixture.long_long_var = self.LONG_LONG_LOWER def test_long_long_var_upper_valid(self): """ long long instance variable upper bound. """ self.valid_upper_fixture.long_long_var = self.LONG_LONG_UPPER def test_unsigned_char_get_upper_valid(self): """ unsigned char virtual result upper bound. """ self.assertEqual(self.valid_upper_fixture.unsigned_char_get(), self.UNSIGNED_CHAR_UPPER) def test_unsigned_char_set_upper_valid(self): """ unsigned char function argument upper bound. """ self.valid_upper_fixture.unsigned_char_set(self.UNSIGNED_CHAR_UPPER) def test_unsigned_char_var_upper_valid(self): """ unsigned char instance variable upper bound. """ self.valid_upper_fixture.unsigned_char_var = self.UNSIGNED_CHAR_UPPER def test_unsigned_short_get_upper_valid(self): """ unsigned short virtual result upper bound. """ self.assertEqual(self.valid_upper_fixture.unsigned_short_get(), self.UNSIGNED_SHORT_UPPER) def test_unsigned_short_set_upper_valid(self): """ unsigned short function argument upper bound. """ self.valid_upper_fixture.unsigned_short_set(self.UNSIGNED_SHORT_UPPER) def test_unsigned_short_var_upper_valid(self): """ unsigned short instance variable upper bound. """ self.valid_upper_fixture.unsigned_short_var = self.UNSIGNED_SHORT_UPPER def test_unsigned_int_get_upper_valid(self): """ unsigned int virtual result upper bound. """ self.assertEqual(self.valid_upper_fixture.unsigned_int_get(), self.UNSIGNED_INT_UPPER) def test_unsigned_int_set_upper_valid(self): """ unsigned int function argument upper bound. """ self.valid_upper_fixture.unsigned_int_set(self.UNSIGNED_INT_UPPER) def test_unsigned_int_var_upper_valid(self): """ unsigned int instance variable upper bound. """ self.valid_upper_fixture.unsigned_int_var = self.UNSIGNED_INT_UPPER def test_unsigned_long_get_upper_valid(self): """ unsigned long virtual result upper bound. """ self.assertEqual(self.valid_upper_fixture.unsigned_long_get(), self.UNSIGNED_LONG_UPPER) def test_unsigned_long_set_upper_valid(self): """ unsigned long function argument upper bound. """ self.valid_upper_fixture.unsigned_long_set(self.UNSIGNED_LONG_UPPER) def test_unsigned_long_var_upper_valid(self): """ unsigned long instance variable upper bound. """ self.valid_upper_fixture.unsigned_long_var = self.UNSIGNED_LONG_UPPER def test_unsigned_long_long_get_upper_valid(self): """ unsigned long long virtual result upper bound. """ self.assertEqual(self.valid_upper_fixture.unsigned_long_long_get(), self.UNSIGNED_LONG_LONG_UPPER) def test_unsigned_long_long_set_upper_valid(self): """ unsigned long long function argument upper bound. """ self.valid_upper_fixture.unsigned_long_long_set( self.UNSIGNED_LONG_LONG_UPPER) def test_unsigned_long_long_var_upper_valid(self): """ unsigned long long instance variable upper bound. """ self.valid_upper_fixture.unsigned_long_long_var = self.UNSIGNED_LONG_LONG_UPPER ########################################################################### # The following test for invalid values. ########################################################################### def test_bool_get_invalid(self): """ bool virtual result. """ with self.assertRaises(TypeError): self.install_hook() self.invalid_fixture.bool_get() self.uninstall_hook() def test_bool_set_invalid(self): """ bool function argument. """ with self.assertRaises(TypeError): self.invalid_fixture.bool_set('0') def test_bool_var_invalid(self): """ bool instance variable. """ with self.assertRaises(TypeError): self.invalid_fixture.bool_var = '0' def test_char_get_invalid(self): """ char virtual result. """ with self.assertRaises(TypeError): self.install_hook() self.invalid_fixture.char_get() self.uninstall_hook() def test_char_set_invalid(self): """ char function argument. """ with self.assertRaises(TypeError): self.invalid_fixture.char_set('0') def test_char_var_invalid(self): """ char instance variable. """ with self.assertRaises(TypeError): self.invalid_fixture.char_var = '0' def test_signed_char_get_invalid(self): """ signed char virtual result. """ with self.assertRaises(TypeError): self.install_hook() self.invalid_fixture.signed_char_get() self.uninstall_hook() def test_signed_char_set_invalid(self): """ signed char function argument. """ with self.assertRaises(TypeError): self.invalid_fixture.signed_char_set('0') def test_signed_char_var_invalid(self): """ signed char instance variable. """ with self.assertRaises(TypeError): self.invalid_fixture.signed_char_var = '0' def test_short_get_invalid(self): """ short virtual result. """ with self.assertRaises(TypeError): self.install_hook() self.invalid_fixture.short_get() self.uninstall_hook() def test_short_set_invalid(self): """ short function argument. """ with self.assertRaises(TypeError): self.invalid_fixture.short_set('0') def test_short_var_invalid(self): """ short instance variable. """ with self.assertRaises(TypeError): self.invalid_fixture.short_var = '0' def test_int_get_invalid(self): """ int virtual result. """ with self.assertRaises(TypeError): self.install_hook() self.invalid_fixture.int_get() self.uninstall_hook() def test_int_set_invalid(self): """ int function argument. """ with self.assertRaises(TypeError): self.invalid_fixture.int_set('0') def test_int_var_invalid(self): """ int instance variable. """ with self.assertRaises(TypeError): self.invalid_fixture.int_var = '0' def test_long_get_invalid(self): """ long virtual result. """ with self.assertRaises(TypeError): self.install_hook() self.invalid_fixture.long_get() self.uninstall_hook() def test_long_set_invalid(self): """ long function argument. """ with self.assertRaises(TypeError): self.invalid_fixture.long_set('0') def test_long_var_invalid(self): """ long instance variable. """ with self.assertRaises(TypeError): self.invalid_fixture.long_var = '0' def test_long_long_get_invalid(self): """ long long virtual result. """ with self.assertRaises(TypeError): self.install_hook() self.invalid_fixture.long_long_get() self.uninstall_hook() def test_long_long_set_invalid(self): """ long long function argument. """ with self.assertRaises(TypeError): self.invalid_fixture.long_long_set('0') def test_long_long_var_invalid(self): """ long long instance variable. """ with self.assertRaises(TypeError): self.invalid_fixture.long_long_var = '0' def test_unsigned_char_get_invalid(self): """ unsigned char virtual result. """ with self.assertRaises(TypeError): self.install_hook() self.invalid_fixture.unsigned_char_get() self.uninstall_hook() def test_unsigned_char_set_invalid(self): """ unsigned char function argument. """ with self.assertRaises(TypeError): self.invalid_fixture.unsigned_char_set('0') def test_unsigned_char_var_invalid(self): """ unsigned char instance variable. """ with self.assertRaises(TypeError): self.invalid_fixture.unsigned_char_var = '0' def test_unsigned_short_get_invalid(self): """ unsigned short virtual result. """ with self.assertRaises(TypeError): self.install_hook() self.invalid_fixture.unsigned_short_get() self.uninstall_hook() def test_unsigned_short_set_invalid(self): """ unsigned short function argument. """ with self.assertRaises(TypeError): self.invalid_fixture.unsigned_short_set('0') def test_unsigned_short_var_invalid(self): """ unsigned short instance variable. """ with self.assertRaises(TypeError): self.invalid_fixture.unsigned_short_var = '0' def test_unsigned_int_get_invalid(self): """ unsigned int virtual result. """ with self.assertRaises(TypeError): self.install_hook() self.invalid_fixture.unsigned_int_get() self.uninstall_hook() def test_unsigned_int_set_invalid(self): """ unsigned int function argument. """ with self.assertRaises(TypeError): self.invalid_fixture.unsigned_int_set('0') def test_unsigned_int_var_invalid(self): """ unsigned int instance variable. """ with self.assertRaises(TypeError): self.invalid_fixture.unsigned_int_var = '0' def test_unsigned_long_get_invalid(self): """ unsigned long virtual result. """ with self.assertRaises(TypeError): self.install_hook() self.invalid_fixture.unsigned_long_get() self.uninstall_hook() def test_unsigned_long_set_invalid(self): """ unsigned long function argument. """ with self.assertRaises(TypeError): self.invalid_fixture.unsigned_long_set('0') def test_unsigned_long_var_invalid(self): """ unsigned long instance variable. """ with self.assertRaises(TypeError): self.invalid_fixture.unsigned_long_var = '0' def test_unsigned_long_long_get_invalid(self): """ unsigned long long virtual result. """ with self.assertRaises(TypeError): self.install_hook() self.invalid_fixture.unsigned_long_long_get() self.uninstall_hook() def test_unsigned_long_long_set_invalid(self): """ unsigned long long function argument. """ with self.assertRaises(TypeError): self.invalid_fixture.unsigned_long_long_set('0') def test_unsigned_long_long_var_invalid(self): """ unsigned long long instance variable. """ with self.assertRaises(TypeError): self.invalid_fixture.unsigned_long_long_var = '0' ########################################################################### # The following test for under/overlowing values. ########################################################################### def test_char_get_lower_overflow(self): """ char virtual result lower bound. """ with self.assertRaises(OverflowError): self.install_hook() self.overflow_lower_fixture.char_get() self.uninstall_hook() def test_char_get_upper_overflow(self): """ char virtual result upper bound. """ with self.assertRaises(OverflowError): self.install_hook() self.overflow_upper_fixture.char_get() self.uninstall_hook() def test_char_set_lower_overflow(self): """ char function argument lower bound. """ with self.assertRaises(OverflowError): self.overflow_lower_fixture.char_set(self.CHAR_LOWER - 1) def test_char_set_upper_overflow(self): """ char function argument upper bound. """ with self.assertRaises(OverflowError): self.overflow_upper_fixture.char_set(self.CHAR_UPPER + 1) def test_char_var_lower_overflow(self): """ char instance variable lower bound. """ with self.assertRaises(OverflowError): self.overflow_lower_fixture.char_var = self.CHAR_LOWER - 1 def test_char_var_upper_overflow(self): """ char instance variable upper bound. """ with self.assertRaises(OverflowError): self.overflow_upper_fixture.char_var = self.CHAR_UPPER + 1 def test_signed_char_get_lower_overflow(self): """ signed char virtual result lower bound. """ with self.assertRaises(OverflowError): self.install_hook() self.overflow_lower_fixture.signed_char_get() self.uninstall_hook() def test_signed_char_get_upper_overflow(self): """ signed char virtual result upper bound. """ with self.assertRaises(OverflowError): self.install_hook() self.overflow_upper_fixture.signed_char_get() self.uninstall_hook() def test_signed_char_set_lower_overflow(self): """ signed char function argument lower bound. """ with self.assertRaises(OverflowError): self.overflow_lower_fixture.signed_char_set( self.SIGNED_CHAR_LOWER - 1) def test_signed_char_set_upper_overflow(self): """ signed char function argument upper bound. """ with self.assertRaises(OverflowError): self.overflow_upper_fixture.signed_char_set( self.SIGNED_CHAR_UPPER + 1) def test_signed_char_var_lower_overflow(self): """ signed char instance variable lower bound. """ with self.assertRaises(OverflowError): self.overflow_lower_fixture.signed_char_var = self.SIGNED_CHAR_LOWER - 1 def test_signed_char_var_upper_overflow(self): """ signed char instance variable upper bound. """ with self.assertRaises(OverflowError): self.overflow_upper_fixture.signed_char_var = self.SIGNED_CHAR_UPPER + 1 def test_short_get_lower_overflow(self): """ short virtual result lower bound. """ with self.assertRaises(OverflowError): self.install_hook() self.overflow_lower_fixture.short_get() self.uninstall_hook() def test_short_get_upper_overflow(self): """ short virtual result upper bound. """ with self.assertRaises(OverflowError): self.install_hook() self.overflow_upper_fixture.short_get() self.uninstall_hook() def test_short_set_lower_overflow(self): """ short function argument lower bound. """ with self.assertRaises(OverflowError): self.overflow_lower_fixture.short_set(self.SHORT_LOWER - 1) def test_short_set_upper_overflow(self): """ short function argument upper bound. """ with self.assertRaises(OverflowError): self.overflow_upper_fixture.short_set(self.SHORT_UPPER + 1) def test_short_var_lower_overflow(self): """ short instance variable lower bound. """ with self.assertRaises(OverflowError): self.overflow_lower_fixture.short_var = self.SHORT_LOWER - 1 def test_short_var_upper_overflow(self): """ short instance variable upper bound. """ with self.assertRaises(OverflowError): self.overflow_upper_fixture.short_var = self.SHORT_UPPER + 1 def test_int_get_lower_overflow(self): """ int virtual result lower bound. """ with self.assertRaises(OverflowError): self.install_hook() self.overflow_lower_fixture.int_get() self.uninstall_hook() def test_int_get_upper_overflow(self): """ int virtual result upper bound. """ with self.assertRaises(OverflowError): self.install_hook() self.overflow_upper_fixture.int_get() self.uninstall_hook() def test_int_set_lower_overflow(self): """ int function argument lower bound. """ with self.assertRaises(OverflowError): self.overflow_lower_fixture.int_set(self.INT_LOWER - 1) def test_int_set_upper_overflow(self): """ int function argument upper bound. """ with self.assertRaises(OverflowError): self.overflow_upper_fixture.int_set(self.INT_UPPER + 1) def test_int_var_lower_overflow(self): """ int instance variable lower bound. """ with self.assertRaises(OverflowError): self.overflow_lower_fixture.int_var = self.INT_LOWER - 1 def test_int_var_upper_overflow(self): """ int instance variable upper bound. """ with self.assertRaises(OverflowError): self.overflow_upper_fixture.int_var = self.INT_UPPER + 1 def test_long_get_lower_overflow(self): """ long virtual result lower bound. """ with self.assertRaises(OverflowError): self.install_hook() self.overflow_lower_fixture.long_get() self.uninstall_hook() def test_long_get_upper_overflow(self): """ long virtual result upper bound. """ with self.assertRaises(OverflowError): self.install_hook() self.overflow_upper_fixture.long_get() self.uninstall_hook() def test_long_set_lower_overflow(self): """ long function argument lower bound. """ with self.assertRaises(OverflowError): self.overflow_lower_fixture.long_set(self.LONG_LOWER - 1) def test_long_set_upper_overflow(self): """ long function argument upper bound. """ with self.assertRaises(OverflowError): self.overflow_upper_fixture.long_set(self.LONG_UPPER + 1) def test_long_var_lower_overflow(self): """ long instance variable lower bound. """ with self.assertRaises(OverflowError): self.overflow_lower_fixture.long_var = self.LONG_LOWER - 1 def test_long_var_upper_overflow(self): """ long instance variable upper bound. """ with self.assertRaises(OverflowError): self.overflow_upper_fixture.long_var = self.LONG_UPPER + 1 def test_long_long_get_lower_overflow(self): """ long long virtual result lower bound. """ with self.assertRaises(OverflowError): self.install_hook() self.overflow_lower_fixture.long_long_get() self.uninstall_hook() def test_long_long_get_upper_overflow(self): """ long long virtual result upper bound. """ with self.assertRaises(OverflowError): self.install_hook() self.overflow_upper_fixture.long_long_get() self.uninstall_hook() def test_long_long_set_lower_overflow(self): """ long long function argument lower bound. """ with self.assertRaises(OverflowError): self.overflow_lower_fixture.long_long_set(self.LONG_LONG_LOWER - 1) def test_long_long_set_upper_overflow(self): """ long long function argument upper bound. """ with self.assertRaises(OverflowError): self.overflow_upper_fixture.long_long_set(self.LONG_LONG_UPPER + 1) def test_long_long_var_lower_overflow(self): """ long long instance variable lower bound. """ with self.assertRaises(OverflowError): self.overflow_lower_fixture.long_long_var = self.LONG_LONG_LOWER - 1 def test_long_long_var_upper_overflow(self): """ long long instance variable upper bound. """ with self.assertRaises(OverflowError): self.overflow_upper_fixture.long_long_var = self.LONG_LONG_UPPER + 1 def test_unsigned_char_get_upper_overflow(self): """ unsigned char virtual result upper bound. """ with self.assertRaises(OverflowError): self.install_hook() self.overflow_upper_fixture.unsigned_char_get() self.uninstall_hook() def test_unsigned_char_set_upper_overflow(self): """ unsigned char function argument upper bound. """ with self.assertRaises(OverflowError): self.overflow_upper_fixture.unsigned_char_set( self.UNSIGNED_CHAR_UPPER + 1) def test_unsigned_char_var_upper_overflow(self): """ unsigned char instance variable upper bound. """ with self.assertRaises(OverflowError): self.overflow_upper_fixture.unsigned_char_var = self.UNSIGNED_CHAR_UPPER + 1 def test_unsigned_short_get_upper_overflow(self): """ unsigned short virtual result upper bound. """ with self.assertRaises(OverflowError): self.install_hook() self.overflow_upper_fixture.unsigned_short_get() self.uninstall_hook() def test_unsigned_short_set_upper_overflow(self): """ unsigned short function argument upper bound. """ with self.assertRaises(OverflowError): self.overflow_upper_fixture.unsigned_short_set( self.UNSIGNED_SHORT_UPPER + 1) def test_unsigned_short_var_upper_overflow(self): """ unsigned short instance variable upper bound. """ with self.assertRaises(OverflowError): self.overflow_upper_fixture.unsigned_short_var = self.UNSIGNED_SHORT_UPPER + 1 def test_unsigned_int_get_upper_overflow(self): """ unsigned int virtual result upper bound. """ with self.assertRaises(OverflowError): self.install_hook() self.overflow_upper_fixture.unsigned_int_get() self.uninstall_hook() def test_unsigned_int_set_upper_overflow(self): """ unsigned int function argument upper bound. """ with self.assertRaises(OverflowError): self.overflow_upper_fixture.unsigned_int_set( self.UNSIGNED_INT_UPPER + 1) def test_unsigned_int_var_upper_overflow(self): """ unsigned int instance variable upper bound. """ with self.assertRaises(OverflowError): self.overflow_upper_fixture.unsigned_int_var = self.UNSIGNED_INT_UPPER + 1 def test_unsigned_long_get_upper_overflow(self): """ unsigned long virtual result upper bound. """ with self.assertRaises(OverflowError): self.install_hook() self.overflow_upper_fixture.unsigned_long_get() self.uninstall_hook() def test_unsigned_long_set_upper_overflow(self): """ unsigned long function argument upper bound. """ with self.assertRaises(OverflowError): self.overflow_upper_fixture.unsigned_long_set( self.UNSIGNED_LONG_UPPER + 1) def test_unsigned_long_var_upper_overflow(self): """ unsigned long instance variable upper bound. """ with self.assertRaises(OverflowError): self.overflow_upper_fixture.unsigned_long_var = self.UNSIGNED_LONG_UPPER + 1 def test_unsigned_long_long_get_upper_overflow(self): """ unsigned long long virtual result upper bound. """ with self.assertRaises(OverflowError): self.install_hook() self.overflow_upper_fixture.unsigned_long_long_get() self.uninstall_hook() def test_unsigned_long_long_set_upper_overflow(self): """ unsigned long long function argument upper bound. """ with self.assertRaises(OverflowError): self.overflow_upper_fixture.unsigned_long_long_set( self.UNSIGNED_LONG_LONG_UPPER + 1) def test_unsigned_long_long_var_upper_overflow(self): """ unsigned long long instance variable upper bound. """ with self.assertRaises(OverflowError): self.overflow_upper_fixture.unsigned_long_long_var = self.UNSIGNED_LONG_LONG_UPPER + 1 ########################################################################### # The following test bool convertors for valid values. ########################################################################### def test_bool_get_true(self): """ bool virtual result with a True value. """ self.assertIs(self.true_fixture.bool_get(), True) def test_bool_set_true(self): """ bool function argument with a True value. """ self.true_fixture.bool_set(True) def test_bool_var_true(self): """ bool instance variable with a True value. """ self.true_fixture.bool_var = True def test_bool_get_false(self): """ bool virtual result with a True value. """ self.assertIs(self.false_fixture.bool_get(), False) def test_bool_set_false(self): """ bool function argument with a False value. """ self.false_fixture.bool_set(False) def test_bool_var_false(self): """ bool instance variable with a False value. """ self.false_fixture.bool_var = False def test_bool_get_nonzero(self): """ bool virtual result with a non-zero value. """ self.assertIs(self.nonzero_fixture.bool_get(), True) def test_bool_set_nonzero(self): """ bool function argument with a non-zero value. """ self.nonzero_fixture.bool_set(-1) def test_bool_var_nonzero(self): """ bool instance variable with a non-zero value. """ self.nonzero_fixture.bool_var = -1 def test_bool_get_zero(self): """ bool virtual result with a zero value. """ self.assertIs(self.zero_fixture.bool_get(), False) def test_bool_set_zero(self): """ bool function argument with a zero value. """ self.zero_fixture.bool_set(0) def test_bool_var_zero(self): """ bool instance variable with a zero value. """ self.zero_fixture.bool_var = 0 sip-6.8.6/test/utils/000077500000000000000000000000001464421045000144455ustar00rootroot00000000000000sip-6.8.6/test/utils/__init__.py000066400000000000000000000002231464421045000165530ustar00rootroot00000000000000# SPDX-License-Identifier: BSD-2-Clause # Copyright (c) 2024 Phil Thompson from .sip_test_case import SIPTestCase sip-6.8.6/test/utils/sip_test_case.py000066400000000000000000000107561464421045000176550ustar00rootroot00000000000000# SPDX-License-Identifier: BSD-2-Clause # Copyright (c) 2024 Phil Thompson import glob import inspect import os import shutil import subprocess import sys import unittest class SIPTestCase(unittest.TestCase): """ Encapsulate a test case that tests a set of standalone bindings. """ # The ABI version to use. None implies the latest major version. Note # that the enum tests are specific to ABI v13. #abi_version = None abi_version = '13.8' #abi_version = '12.15' @classmethod def setUpClass(cls): """ Build a test extension module. """ # Get the name of the test directory from the file implementing the # test case. test_dir = os.path.dirname(inspect.getfile(cls)) # Look for a .sip file in the test directory. sip_files = glob.glob(os.path.join(test_dir, '*.sip')) if len(sip_files) != 1: raise Exception( "expected to find a single .sip file in '{0}'".format( test_dir)) module_name = os.path.basename(sip_files[0])[:-4] # Create a pyproject.toml file. pyproject_toml = os.path.join(test_dir, 'pyproject.toml') with open(pyproject_toml, 'w') as f: f.write(_PYPROJECT_TOML.format(module_name=module_name)) if cls.abi_version is not None: f.write(_ABI_VERSION.format(abi_version=cls.abi_version)) # Build the extension module. cwd = os.getcwd() subprocess.run([sys.executable, '-m', 'sipbuild.tools.build', '--verbose'], cwd=test_dir).check_returncode() os.chdir(cwd) # Move the extension module to the test directory. build_dir = os.path.join(test_dir, 'build') # The distutils and setuptools builders leave the module in different # places. for subdirs in ((module_name, ), (module_name, 'build', 'lib*')): module_pattern = [build_dir] module_pattern.extend(subdirs) module_pattern.append( module_name + '*.pyd' if sys.platform == 'win32' else '*.so') module_path = glob.glob(os.path.join(*module_pattern)) if len(module_path) != 0: break else: raise Exception( "no '{0}' extension module was built".format(module_name)) if len(module_path) != 1: raise Exception( "unable to determine file name of the '{0}' extension module".format(module_name)) module_path = module_path[0] module_impl = os.path.join(test_dir, os.path.basename(module_path)) # On Windows the module may be lying around from a previous run. try: os.remove(module_impl) except: pass os.rename(module_path, module_impl) # Remove the build artifacts. os.remove(pyproject_toml) shutil.rmtree(build_dir, ignore_errors=True) # Provide tearDownClass() with the values it needs to tidy up. cls._module_name = module_name cls._module_impl = module_impl @classmethod def tearDownClass(cls): """ Unload the module and remove the extension module. """ try: del sys.modules[cls._module_name] except KeyError: pass # This will fail on Windows (probably because the module is loaded). try: os.remove(cls._module_impl) except: pass def install_hook(self): """ Install a hook that will catch any exceptions raised by a Python reimplementation of a C++ virtual. """ # Clear the saved exception. self._exc = None # Save the old hook and install the new one. self._old_hook = sys.excepthook sys.excepthook = self._hook def uninstall_hook(self): """ Restore the original exception hook and re-raise any exception raised by a virtual reimplementation. """ sys.excepthook = self._old_hook if self._exc is not None: raise self._exc def _hook(self, xtype, xvalue, xtb): """ The replacement exceptionhook. """ # Save the exception for later. self._exc = xvalue # The prototype pyproject.toml file. _PYPROJECT_TOML = """ [build-system] requires = ["sip >=6"] build-backend = "sipbuild.api" [project] name = "{module_name}" [tool.sip.project] minimum-macos-version = "10.9" """ _ABI_VERSION = """ abi-version = "{abi_version}" """